using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using InControl;
using MidGameJoin.Core;
using MidGameJoin.Runtime;
using MidGameJoin.UI;
using Photon.Pun;
using Photon.Realtime;
using RWF;
using RWF.GameModes;
using UnboundLib;
using UnboundLib.GameModes;
using UnboundLib.Networking;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("MidGameJoin")]
[assembly: AssemblyDescription("Late-join spectator add-on for RoundsWithFriends")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MidGameJoin")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.1.3.0")]
namespace MidGameJoin
{
[BepInPlugin("com.thunderstore.midgamejoin", "MidGameJoin", "0.1.3")]
[BepInDependency("com.willis.rounds.unbound", "3.2.14")]
[BepInDependency("io.olavim.rounds.rwf", "2.2.2")]
public sealed class MidGameJoinPlugin : BaseUnityPlugin
{
private Harmony harmony;
private void Awake()
{
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00c4: Expected O, but got Unknown
RWFBridge rWFBridge = new RWFBridge();
if (RWFBridge.ValidateCompatibility(((BaseUnityPlugin)this).Logger))
{
LateJoinManager lateJoinManager = ((Component)this).gameObject.AddComponent<LateJoinManager>();
lateJoinManager.Initialize(rWFBridge, ((BaseUnityPlugin)this).Logger);
((Component)this).gameObject.AddComponent<LateJoinPhotonCallbacks>();
((Component)this).gameObject.AddComponent<JoinabilityController>();
LateJoinUI lateJoinUI = ((Component)this).gameObject.AddComponent<LateJoinUI>();
SessionSyncController sessionSyncController = ((Component)this).gameObject.AddComponent<SessionSyncController>();
sessionSyncController.Initialize(lateJoinManager, rWFBridge, lateJoinUI);
CatchUpCardController catchUpCardController = ((Component)this).gameObject.AddComponent<CatchUpCardController>();
catchUpCardController.Initialize(lateJoinUI);
SpectatorController spectatorController = ((Component)this).gameObject.AddComponent<SpectatorController>();
spectatorController.Manager = lateJoinManager;
spectatorController.UI = lateJoinUI;
lateJoinManager.AttachRuntimeControllers(sessionSyncController, lateJoinUI, catchUpCardController);
GameModeManager.AddHook("PickEnd", (Func<IGameModeHandler, IEnumerator>)lateJoinManager.OnPickEnd, 0);
harmony = new Harmony("com.thunderstore.midgamejoin");
harmony.PatchAll();
((BaseUnityPlugin)this).Logger.LogInfo((object)("MidGameJoin 0.1.3 protocol " + 100 + " loaded for RoundsWithFriends 2.2.2."));
}
}
private void OnDestroy()
{
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
}
namespace MidGameJoin.UI
{
public sealed class LateJoinUI : MonoBehaviour
{
private GameObject root;
private Text statusText;
private Text catchUpText;
private RectTransform teamContainer;
private readonly List<GameObject> teamButtons = new List<GameObject>();
public void ShowSpectating(string status)
{
EnsureCreated();
root.SetActive(true);
statusText.text = (string.IsNullOrEmpty(status) ? "Waiting for card picks to finish" : status);
catchUpText.text = string.Empty;
}
public void SetTeams(IReadOnlyList<TeamOption> teams, int? selectedTeamId)
{
//IL_005b: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
//IL_0131: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Expected O, but got Unknown
//IL_0170: Unknown result type (might be due to invalid IL or missing references)
//IL_018c: Unknown result type (might be due to invalid IL or missing references)
//IL_0197: Unknown result type (might be due to invalid IL or missing references)
//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
//IL_0109: Unknown result type (might be due to invalid IL or missing references)
EnsureCreated();
ClearTeamButtons();
if (teams == null)
{
return;
}
for (int i = 0; i < teams.Count; i++)
{
TeamOption teamOption = teams[i];
GameObject val = CreateUiObject("Team " + teamOption.TeamId, (Transform)(object)teamContainer);
RectTransform component = val.GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 1f);
component.anchorMax = new Vector2(0.5f, 1f);
component.pivot = new Vector2(0.5f, 1f);
component.anchoredPosition = new Vector2(0f, (float)(-i) * 42f);
component.sizeDelta = new Vector2(260f, 36f);
((Graphic)val.AddComponent<Image>()).color = ((selectedTeamId.HasValue && selectedTeamId.Value == teamOption.TeamId) ? new Color(0.3f, 0.75f, 0.35f, 0.95f) : new Color(0.15f, 0.15f, 0.15f, 0.9f));
Button obj = val.AddComponent<Button>();
int capturedTeam = teamOption.TeamId;
((UnityEvent)obj.onClick).AddListener((UnityAction)delegate
{
LateJoinNetwork.RequestTeam(capturedTeam);
});
Text obj2 = CreateUiObject("Label", val.transform).AddComponent<Text>();
obj2.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
obj2.fontSize = 20;
obj2.alignment = (TextAnchor)4;
((Graphic)obj2).color = Color.white;
obj2.text = teamOption.Label;
RectTransform rectTransform = ((Graphic)obj2).rectTransform;
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
teamButtons.Add(val);
}
}
public void ShowCatchUp(int remaining, int total)
{
EnsureCreated();
ClearTeamButtons();
root.SetActive(true);
statusText.text = "Joining next fight";
catchUpText.text = "Choose " + remaining + " catch-up cards" + ((total > 0) ? (" (" + total + " total)") : string.Empty);
}
public void Hide()
{
if ((Object)(object)root != (Object)null)
{
root.SetActive(false);
}
}
private void EnsureCreated()
{
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_012c: Unknown result type (might be due to invalid IL or missing references)
//IL_0159: Unknown result type (might be due to invalid IL or missing references)
//IL_0168: Unknown result type (might be due to invalid IL or missing references)
//IL_0194: Unknown result type (might be due to invalid IL or missing references)
//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
//IL_01df: Unknown result type (might be due to invalid IL or missing references)
//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0213: Unknown result type (might be due to invalid IL or missing references)
//IL_022d: Unknown result type (might be due to invalid IL or missing references)
//IL_0247: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)root != (Object)null))
{
root = CreateUiObject("MidGameJoinUI", ((Component)this).transform);
Canvas obj = root.AddComponent<Canvas>();
obj.renderMode = (RenderMode)0;
obj.sortingOrder = 5000;
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
GameObject val = CreateUiObject("Panel", root.transform);
RectTransform component = val.GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 1f);
component.anchorMax = new Vector2(0.5f, 1f);
component.pivot = new Vector2(0.5f, 1f);
component.anchoredPosition = new Vector2(0f, -20f);
component.sizeDelta = new Vector2(620f, 330f);
((Graphic)val.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.72f);
CreateText("Title", val.transform, "SPECTATING — JOINING NEXT ROUND", 27, new Vector2(0f, -22f), new Vector2(580f, 42f)).fontStyle = (FontStyle)1;
statusText = CreateText("Status", val.transform, "Synchronizing match", 20, new Vector2(0f, -72f), new Vector2(580f, 50f));
catchUpText = CreateText("CatchUp", val.transform, string.Empty, 20, new Vector2(0f, -122f), new Vector2(580f, 42f));
GameObject val2 = CreateUiObject("Teams", val.transform);
teamContainer = val2.GetComponent<RectTransform>();
teamContainer.anchorMin = new Vector2(0.5f, 1f);
teamContainer.anchorMax = new Vector2(0.5f, 1f);
teamContainer.pivot = new Vector2(0.5f, 1f);
teamContainer.anchoredPosition = new Vector2(0f, -172f);
teamContainer.sizeDelta = new Vector2(300f, 140f);
}
}
private static Text CreateText(string name, Transform parent, string content, int size, Vector2 position, Vector2 dimensions)
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
Text obj = CreateUiObject(name, parent).AddComponent<Text>();
obj.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
obj.fontSize = size;
obj.alignment = (TextAnchor)4;
((Graphic)obj).color = Color.white;
obj.text = content;
RectTransform rectTransform = ((Graphic)obj).rectTransform;
rectTransform.anchorMin = new Vector2(0.5f, 1f);
rectTransform.anchorMax = new Vector2(0.5f, 1f);
rectTransform.pivot = new Vector2(0.5f, 1f);
rectTransform.anchoredPosition = position;
rectTransform.sizeDelta = dimensions;
return obj;
}
private static GameObject CreateUiObject(string name, Transform parent)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Expected O, but got Unknown
GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
val.transform.SetParent(parent, false);
return val;
}
private void ClearTeamButtons()
{
for (int i = 0; i < teamButtons.Count; i++)
{
if ((Object)(object)teamButtons[i] != (Object)null)
{
Object.Destroy((Object)(object)teamButtons[i]);
}
}
teamButtons.Clear();
}
}
public sealed class TeamOption
{
public int TeamId { get; private set; }
public string Label { get; private set; }
public TeamOption(int teamId, string label)
{
TeamId = teamId;
Label = label;
}
}
}
namespace MidGameJoin.Runtime
{
public static class CapacityValidator
{
public static bool CanInsert(int activePlayers, int rwfMaxPlayers, int modeMaxPlayers)
{
int num = Math.Min(rwfMaxPlayers, modeMaxPlayers);
return activePlayers < num;
}
}
public sealed class CatchUpCardController : MonoBehaviour
{
private LateJoinUI ui;
public void Initialize(LateJoinUI lateJoinUi)
{
ui = lateJoinUi;
}
public IEnumerator RunCatchUp(Player player, int catchUpCount)
{
if ((Object)(object)player == (Object)null || catchUpCount < 0)
{
yield break;
}
LobbyCharacter character = PlayerExtensions.GetAdditionalData(player).character;
bool isMine = character != null && character.IsMine;
if ((Object)(object)CardChoiceVisuals.instance != (Object)null)
{
CardChoiceVisuals.instance.Show(player.playerID, true);
}
for (int i = 0; i < catchUpCount; i++)
{
if (isMine && (Object)(object)ui != (Object)null)
{
ui.ShowCatchUp(catchUpCount - i, catchUpCount);
}
yield return GameModeManager.TriggerHook("PlayerPickStart");
yield return CardChoice.instance.DoPick(1, player.playerID, (PickerType)1);
yield return GameModeManager.TriggerHook("PlayerPickEnd");
}
if ((Object)(object)CardChoiceVisuals.instance != (Object)null)
{
CardChoiceVisuals.instance.Hide();
}
if (isMine && (Object)(object)ui != (Object)null)
{
ui.ShowSpectating("Finalizing join");
}
}
}
public sealed class JoinabilityController : MonoBehaviour
{
private void Update()
{
LateJoinManager instance = LateJoinManager.Instance;
if (!((Object)(object)instance == (Object)null) && instance.Bridge != null && instance.Bridge.IsSupportedGameMode() && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null && !((Object)(object)GameManager.instance == (Object)null) && GameManager.instance.isPlaying)
{
if (!PhotonNetwork.CurrentRoom.IsOpen)
{
PhotonNetwork.CurrentRoom.IsOpen = true;
}
if (PhotonNetwork.CurrentRoom.IsVisible)
{
PhotonNetwork.CurrentRoom.IsVisible = false;
}
}
}
}
public sealed class LateJoinManager : MonoBehaviour
{
private sealed class PickEndDecision
{
public int Generation { get; private set; }
public int BatchId { get; private set; }
public int CatchUpCount { get; private set; }
public int[] ActorIds { get; private set; }
public PickEndDecision(int generation, int batchId, int catchUpCount, int[] actorIds)
{
Generation = generation;
BatchId = batchId;
CatchUpCount = catchUpCount;
ActorIds = actorIds;
}
}
private sealed class PlayerCreationDirective
{
public int TeamId { get; private set; }
public int ColorId { get; private set; }
public PlayerCreationDirective(int teamId, int colorId)
{
TeamId = teamId;
ColorId = colorId;
}
}
private readonly LateJoinCoordinator coordinator = new LateJoinCoordinator();
private readonly Dictionary<int, LateJoinRecord> mirroredRecords = new Dictionary<int, LateJoinRecord>();
private readonly Random nonceRandom = new Random();
private ManualLogSource logger;
private bool localJoinedRoom;
private PickEndDecision pickEndDecision;
private readonly Dictionary<string, PlayerCreationDirective> creationDirectives = new Dictionary<string, PlayerCreationDirective>();
private readonly HashSet<string> rolledBackActors = new HashSet<string>();
private readonly HashSet<string> catchUpReadyActors = new HashSet<string>();
private readonly HashSet<string> catchUpBeginActors = new HashSet<string>();
private readonly HashSet<string> localCatchUpReadySent = new HashSet<string>();
private CatchUpCardController catchUpCards;
private bool inPickEndHook;
private readonly HashSet<string> detachedLocalFlows = new HashSet<string>();
private int localTeamRequestNonce;
private bool hostMigrationRecoveryRunning;
public static LateJoinManager Instance { get; private set; }
public RWFBridge Bridge { get; private set; }
public int LocalChallengeNonce { get; private set; }
public int PickEndGeneration { get; set; }
public SessionSyncController SessionSync { get; private set; }
public LateJoinUI UI { get; private set; }
public IReadOnlyList<LateJoinRecord> Records
{
get
{
if (!PhotonNetwork.IsMasterClient)
{
return mirroredRecords.Values.OrderBy((LateJoinRecord r) => r.ActorId).ToArray();
}
return coordinator.Records;
}
}
public LateJoinRecord LocalRecord
{
get
{
if (PhotonNetwork.LocalPlayer == null)
{
return null;
}
return GetRecord(PhotonNetwork.LocalPlayer.ActorNumber);
}
}
internal LateJoinCoordinator Coordinator => coordinator;
public void Initialize(RWFBridge bridge, ManualLogSource log)
{
Bridge = bridge;
logger = log;
LateJoinNetwork.Manager = this;
}
public void AttachRuntimeControllers(SessionSyncController sessionSync, LateJoinUI ui, CatchUpCardController catchUpController)
{
SessionSync = sessionSync;
UI = ui;
catchUpCards = catchUpController;
}
private void Awake()
{
Instance = this;
}
private void OnDestroy()
{
if ((Object)(object)Instance == (Object)(object)this)
{
Instance = null;
}
if ((Object)(object)LateJoinNetwork.Manager == (Object)(object)this)
{
LateJoinNetwork.Manager = null;
}
}
public void OnLocalJoinedRoom()
{
localJoinedRoom = true;
LocalChallengeNonce = 0;
localTeamRequestNonce = 0;
}
public void OnRemoteActorEntered(int actorId)
{
if (PhotonNetwork.IsMasterClient && !((Object)(object)GameManager.instance == (Object)null) && GameManager.instance.isPlaying && Bridge != null && Bridge.IsSupportedGameMode())
{
LateJoinRecord lateJoinRecord = coordinator.Register(actorId, 0);
if (lateJoinRecord.State != LateJoinState.Disconnected)
{
int nonce = (lateJoinRecord.ChallengeNonce = NextPositiveNonce());
lateJoinRecord.LastTeamNonce = 0;
lateJoinRecord.Status = "Waiting for MidGameJoin handshake";
LateJoinNetwork.SendChallenge(actorId, nonce);
BroadcastRecords();
LogInfo("Challenge sent to late actor " + actorId + ".");
}
}
}
public void OnRemoteActorLeft(Player otherPlayer)
{
if (otherPlayer == null)
{
return;
}
int actorNumber = otherPlayer.ActorNumber;
if (PhotonNetwork.IsMasterClient)
{
LateJoinRecord record = coordinator.GetRecord(actorNumber);
if (record == null || record.State == LateJoinState.Disconnected)
{
mirroredRecords.Remove(actorNumber);
return;
}
int batchId = record.BatchId;
if (record.State != LateJoinState.Active && Bridge != null && (Object)(object)Bridge.FindPlayerOwnedByActor(actorNumber) != (Object)null)
{
Bridge.RollBackLatePlayer(otherPlayer);
}
if (coordinator.Disconnect(actorNumber))
{
if (batchId > 0)
{
ReceiveRollback(actorNumber, batchId, "Disconnected during late join");
LateJoinNetwork.BroadcastRollback(actorNumber, batchId, "Disconnected during late join");
}
BroadcastRecords();
LogInfo("Late actor " + actorNumber + " disconnected.");
}
}
mirroredRecords.Remove(actorNumber);
}
public void OnMasterClientSwitched(int actorId)
{
LogInfo("Photon master switched to actor " + actorId + ". Pending late-join recovery is deferred until RWF issues NetworkRestart.");
}
public void OnRwfHostRestartIssued(int actorId)
{
if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId)
{
BeginHostMigrationRecovery();
}
}
public void OnChallengeReceived(int nonce, int hostProtocolVersion)
{
if (localJoinedRoom && nonce > 0)
{
LocalChallengeNonce = nonce;
localTeamRequestNonce = 0;
if ((Object)(object)UI != (Object)null)
{
UI.ShowSpectating("Synchronizing match");
}
RefreshLocalUi();
if (hostProtocolVersion / 100 != 1)
{
LogWarning("Host MidGameJoin protocol is incompatible: " + hostProtocolVersion + ".");
}
else
{
LateJoinNetwork.SendHello(nonce);
}
}
}
public void OnHello(int actorId, int nonce, int protocolVersion)
{
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record == null || nonce <= 0 || record.ChallengeNonce != nonce)
{
LogWarning("Rejected stale/invalid MidGameJoin hello for actor " + actorId + ".");
return;
}
if (protocolVersion / 100 != 1)
{
record.ProtocolVersion = protocolVersion;
record.Status = "MidGameJoin version mismatch";
LateJoinNetwork.SendHelloRejected(actorId, "MidGameJoin version mismatch");
BroadcastRecords();
return;
}
record.ProtocolVersion = protocolVersion;
bool num = coordinator.HandshakeAccepted(actorId);
bool flag = record.State == LateJoinState.Spectating || record.State == LateJoinState.TeamSelected || record.State == LateJoinState.WaitingForInsertion;
if (num || flag)
{
LateJoinNetwork.SendHelloAccepted(actorId);
SessionSnapshot sessionSnapshot = Bridge.CaptureSessionSnapshot();
sessionSnapshot.PickEndGeneration = PickEndGeneration;
sessionSnapshot.ActivePlayerCount = GetCanonicalActivePlayerCount();
LateJoinNetwork.SendSessionSnapshot(actorId, record.ChallengeNonce, sessionSnapshot);
}
BroadcastRecords();
LogInfo("HelloAccepted actor=" + actorId + " protocol=" + protocolVersion + ".");
}
public void OnHelloAccepted(int actorId, int protocolVersion)
{
if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId)
{
LogInfo("Late join handshake accepted by host; protocol=" + protocolVersion + ".");
}
}
public void OnHelloRejected(int actorId, int hostProtocolVersion, string reason)
{
if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId)
{
LogWarning((reason ?? "MidGameJoin version mismatch") + " (host protocol " + hostProtocolVersion + ").");
}
}
public void OnSessionSnapshot(int challengeNonce, SessionSnapshot snapshot)
{
if (challengeNonce <= 0 || challengeNonce != LocalChallengeNonce)
{
LogDebug("Ignored stale session snapshot nonce=" + challengeNonce + ".");
}
else if ((Object)(object)SessionSync != (Object)null)
{
SessionSync.ApplySnapshot(snapshot);
}
}
public void OnSessionReady(int actorId, int challengeNonce, int currentLevelId)
{
if (!PhotonNetwork.IsMasterClient)
{
return;
}
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record == null || challengeNonce <= 0 || record.ChallengeNonce != challengeNonce || record.State == LateJoinState.Disconnected || record.State == LateJoinState.Active)
{
LogDebug("Ignored stale session-ready actor=" + actorId + " nonce=" + challengeNonce + ".");
return;
}
if ((Object)(object)MapManager.instance != (Object)null && currentLevelId != MapManager.instance.currentLevelID)
{
record.Status = "Synchronizing match";
BroadcastRecords();
return;
}
record.SessionReady = true;
if (Bridge != null && Bridge.IsTeamMode())
{
if (record.SelectedTeamId.HasValue && Bridge.GetCurrentTeamIds().Contains(record.SelectedTeamId.Value))
{
coordinator.MarkWaiting(actorId);
}
else
{
record.Status = "Choose a team while spectating";
}
}
else
{
coordinator.MarkWaiting(actorId);
}
BroadcastRecords();
}
public void OnTeamRequested(int actorId, int challengeNonce, int commandNonce, int teamId)
{
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record == null || challengeNonce <= 0 || record.ChallengeNonce != challengeNonce)
{
LogDebug("Ignored stale team request for actor " + actorId + ".");
}
else
{
if (!ValidateTeamSelection(actorId, teamId))
{
return;
}
if (!coordinator.TrySelectTeamCommand(actorId, commandNonce, teamId))
{
LogDebug("Ignored duplicate/stale team command actor=" + actorId + " nonce=" + commandNonce + ".");
}
else
{
if (record.SessionReady)
{
coordinator.MarkWaiting(actorId);
}
BroadcastRecords();
}
}
}
public bool ValidateTeamSelection(int actorId, int teamId)
{
if (!PhotonNetwork.IsMasterClient || Bridge == null || !Bridge.IsSupportedGameMode() || !Bridge.IsTeamMode())
{
return false;
}
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record == null || !record.SessionReady || record.State == LateJoinState.Active || record.State == LateJoinState.Disconnected)
{
return false;
}
return Bridge.GetCurrentTeamIds().Contains(teamId);
}
public int[] GetBatchEligibleActors()
{
if (!PhotonNetwork.IsMasterClient || Bridge == null || !Bridge.IsSupportedGameMode())
{
return new int[0];
}
int num = (((Object)(object)RWFMod.instance != (Object)null) ? RWFMod.instance.MaxPlayers : 16);
int modeMaxPlayers = num;
if (GameModeManager.CurrentHandler != null && GameModeManager.CurrentHandler.Settings != null && GameModeManager.CurrentHandler.Settings.ContainsKey("maxPlayers"))
{
object obj = GameModeManager.CurrentHandler.Settings["maxPlayers"];
if (obj is int)
{
modeMaxPlayers = (int)obj;
}
}
HashSet<int> hashSet = new HashSet<int>(Bridge.GetCurrentTeamIds());
int num2 = ((!((Object)(object)PlayerManager.instance == (Object)null)) ? PlayerManager.instance.players.Count : 0);
List<int> list = new List<int>();
foreach (LateJoinRecord item in coordinator.Records.OrderBy((LateJoinRecord r) => r.ActorId))
{
if (item.ProtocolVersion / 100 == 1 && item.SessionReady && (item.State == LateJoinState.WaitingForInsertion || item.State == LateJoinState.TeamSelected))
{
if (Bridge.IsTeamMode() && (!item.SelectedTeamId.HasValue || !hashSet.Contains(item.SelectedTeamId.Value)))
{
item.Status = "Choose a valid team";
continue;
}
if (!CapacityValidator.CanInsert(num2 + list.Count, num, modeMaxPlayers))
{
item.Status = "Lobby/player limit reached for this insertion";
continue;
}
list.Add(item.ActorId);
item.Status = "Waiting for insertion";
}
}
BroadcastRecords();
return list.ToArray();
}
public void ApplyRecordSnapshot(int[] actorIds, int[] states, int[] teamIds, int[] batchIds, int[] catchUpCounts, int[] protocolVersions, string[] statuses)
{
if (!PhotonNetwork.IsMasterClient)
{
LateJoinRecord[] array;
try
{
array = SnapshotCodec.DecodeRecords(actorIds, states, teamIds, batchIds, catchUpCounts, protocolVersions, statuses);
}
catch (Exception ex)
{
LogWarning("Rejected malformed MidGameJoin record snapshot: " + ex.Message);
return;
}
mirroredRecords.Clear();
for (int i = 0; i < array.Length; i++)
{
mirroredRecords[array[i].ActorId] = array[i];
}
RefreshLocalUi();
}
}
public void RefreshLocalUi()
{
if ((Object)(object)UI == (Object)null)
{
return;
}
LateJoinRecord localRecord = LocalRecord;
if (localRecord == null || localRecord.State == LateJoinState.Active)
{
if (localRecord != null && localRecord.State == LateJoinState.Active)
{
UI.Hide();
}
return;
}
UI.ShowSpectating(localRecord.Status);
if (localRecord.State == LateJoinState.CatchUpPicking)
{
UI.SetTeams(new TeamOption[0], null);
}
else if (Bridge != null && Bridge.IsTeamMode())
{
TeamOption[] teams = (from id in Bridge.GetCurrentTeamIds()
orderby id
select new TeamOption(id, "TEAM " + (id + 1))).ToArray();
UI.SetTeams(teams, localRecord.SelectedTeamId);
}
else
{
UI.SetTeams(new TeamOption[0], null);
}
}
public LateJoinRecord GetRecord(int actorId)
{
if (PhotonNetwork.IsMasterClient)
{
return coordinator.GetRecord(actorId);
}
if (!mirroredRecords.TryGetValue(actorId, out var value))
{
return null;
}
return value;
}
public IEnumerator OnPickEnd(IGameModeHandler handler)
{
inPickEndHook = true;
try
{
yield return OnPickEndCore(handler);
}
finally
{
inPickEndHook = false;
}
}
private IEnumerator OnPickEndCore(IGameModeHandler handler)
{
PickEndGeneration++;
int generation = PickEndGeneration;
pickEndDecision = null;
creationDirectives.Clear();
rolledBackActors.Clear();
catchUpReadyActors.Clear();
catchUpBeginActors.Clear();
localCatchUpReadySent.Clear();
if (Bridge == null || !Bridge.IsSupportedGameMode() || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
{
yield break;
}
HashSet<int> activeOwners = GetActiveOwnerActorIds();
if (!PhotonNetwork.CurrentRoom.Players.Keys.Any((int item) => !activeOwners.Contains(item)))
{
yield break;
}
if (PhotonNetwork.IsMasterClient)
{
int catchUpCount = LateJoinRules.CalculateCatchUpCount(Bridge.GetExistingCardCounts().ToArray());
int[] batchEligibleActors = GetBatchEligibleActors();
InsertionBatch insertionBatch = coordinator.LockBatch(batchEligibleActors, catchUpCount);
ReceivePickEndDecision(generation, insertionBatch.Id, insertionBatch.CatchUpCount, insertionBatch.ActorIds.ToArray());
LateJoinNetwork.BroadcastPickEndDecision(generation, insertionBatch.Id, insertionBatch.CatchUpCount, insertionBatch.ActorIds.ToArray());
BroadcastRecords();
}
while (pickEndDecision == null || pickEndDecision.Generation != generation)
{
if (!PhotonNetwork.InRoom)
{
yield break;
}
yield return null;
}
if (pickEndDecision.BatchId <= 0 || pickEndDecision.ActorIds.Length == 0)
{
yield break;
}
for (int index = 0; index < pickEndDecision.ActorIds.Length; index++)
{
int actorId = pickEndDecision.ActorIds[index];
if (PhotonNetwork.IsMasterClient)
{
if (PhotonNetwork.CurrentRoom == null || !PhotonNetwork.CurrentRoom.Players.ContainsKey(actorId))
{
coordinator.Disconnect(actorId);
RollBackActor(actorId, pickEndDecision.BatchId, "Disconnected before player creation", cleanPlayerObjects: false);
}
else
{
LateJoinRecord record = coordinator.GetRecord(actorId);
int teamId = ((Bridge.IsTeamMode() && record != null && record.SelectedTeamId.HasValue) ? record.SelectedTeamId.Value : Bridge.GetNextFfaTeamId());
int colorId = (Bridge.IsTeamMode() ? Bridge.GetColorForTeam(teamId) : Bridge.GetAvailableColorId());
ReceiveBeginPlayerCreation(actorId, pickEndDecision.BatchId, teamId, colorId);
LateJoinNetwork.BroadcastBeginPlayerCreation(actorId, pickEndDecision.BatchId, teamId, colorId);
}
}
while (!HasDirective(actorId, pickEndDecision.BatchId) && !IsRolledBack(actorId, pickEndDecision.BatchId))
{
if (!PhotonNetwork.InRoom)
{
yield break;
}
yield return null;
}
if (IsRolledBack(actorId, pickEndDecision.BatchId))
{
continue;
}
PlayerCreationDirective playerCreationDirective = creationDirectives[DirectiveKey(actorId, pickEndDecision.BatchId)];
yield return Bridge.CreateLatePlayer(actorId, 0, playerCreationDirective.TeamId, playerCreationDirective.ColorId);
Player player = Bridge.FindPlayerOwnedByActor(actorId);
if (PhotonNetwork.IsMasterClient)
{
if ((Object)(object)player == (Object)null)
{
RollBackActor(actorId, pickEndDecision.BatchId, "Player creation timed out", cleanPlayerObjects: true);
}
else
{
LateJoinRecord record2 = coordinator.GetRecord(actorId);
if (record2 != null && record2.BatchId == pickEndDecision.BatchId)
{
record2.StartingCardCount = (((Object)(object)player.data != (Object)null && player.data.currentCards != null) ? player.data.currentCards.Count : 0);
record2.State = LateJoinState.CatchUpPicking;
record2.Status = ((pickEndDecision.CatchUpCount == 0) ? "Finalizing join" : "Choosing catch-up cards");
Bridge.EnsurePlayerJoined(player);
BroadcastRecords();
}
}
}
while (!IsRolledBack(actorId, pickEndDecision.BatchId) && !IsActorInState(actorId, LateJoinState.CatchUpPicking) && (!PhotonNetwork.IsMasterClient || !IsActorResolved(actorId, pickEndDecision.BatchId)))
{
yield return null;
}
if (IsRolledBack(actorId, pickEndDecision.BatchId))
{
continue;
}
player = Bridge.FindPlayerOwnedByActor(actorId);
if ((Object)(object)player == (Object)null)
{
if (PhotonNetwork.IsMasterClient)
{
RollBackActor(actorId, pickEndDecision.BatchId, "Player view missing before catch-up", cleanPlayerObjects: true);
}
while (!IsActorResolved(actorId, pickEndDecision.BatchId))
{
yield return null;
}
continue;
}
Bridge.EnsurePlayerJoined(player);
yield return WaitForCatchUpBegin(actorId, pickEndDecision.BatchId);
if (IsRolledBack(actorId, pickEndDecision.BatchId))
{
continue;
}
if ((Object)(object)catchUpCards != (Object)null)
{
yield return catchUpCards.RunCatchUp(player, pickEndDecision.CatchUpCount);
}
if (PhotonNetwork.IsMasterClient)
{
LateJoinRecord record3 = coordinator.GetRecord(actorId);
int num = (((Object)(object)player.data != (Object)null && player.data.currentCards != null) ? player.data.currentCards.Count : 0);
if (record3 != null && record3.BatchId == pickEndDecision.BatchId && num >= record3.StartingCardCount + record3.CatchUpCount)
{
if (record3.CatchUpCount == 0)
{
record3.CatchUpComplete = true;
}
else
{
coordinator.MarkCatchUpComplete(actorId, pickEndDecision.BatchId);
}
if (coordinator.Activate(actorId, pickEndDecision.BatchId))
{
record3.ChallengeNonce = 0;
record3.LastTeamNonce = 0;
}
BroadcastRecords();
}
else
{
RollBackActor(actorId, pickEndDecision.BatchId, "Catch-up card count did not synchronize", cleanPlayerObjects: true);
}
}
while (!IsActorResolved(actorId, pickEndDecision.BatchId))
{
if (!PhotonNetwork.InRoom)
{
yield break;
}
yield return null;
}
}
if ((Object)(object)PlayerManager.instance != (Object)null)
{
PlayerManagerExtensions.ResetPickOrder(PlayerManager.instance);
}
}
public void ReceivePickEndDecision(int generation, int batchId, int catchUpCount, int[] actorIds)
{
if (!inPickEndHook && generation > PickEndGeneration)
{
creationDirectives.Clear();
rolledBackActors.Clear();
catchUpReadyActors.Clear();
catchUpBeginActors.Clear();
localCatchUpReadySent.Clear();
}
if (inPickEndHook)
{
if (generation != PickEndGeneration)
{
return;
}
}
else
{
if (generation < PickEndGeneration)
{
return;
}
PickEndGeneration = generation;
}
pickEndDecision = new PickEndDecision(generation, batchId, catchUpCount, actorIds ?? new int[0]);
}
public void ReceiveBeginPlayerCreation(int actorId, int batchId, int teamId, int colorId)
{
if (pickEndDecision == null || pickEndDecision.BatchId != batchId || !pickEndDecision.ActorIds.Contains(actorId))
{
return;
}
string text = DirectiveKey(actorId, batchId);
if (!IsRolledBack(actorId, batchId))
{
PlayerCreationDirective playerCreationDirective = new PlayerCreationDirective(teamId, colorId);
creationDirectives[text] = playerCreationDirective;
if (!inPickEndHook && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId && Bridge != null && (Object)(object)Bridge.FindPlayerOwnedByActor(actorId) == (Object)null && !detachedLocalFlows.Contains(text) && !IsActorResolved(actorId, batchId))
{
detachedLocalFlows.Add(text);
((MonoBehaviour)this).StartCoroutine(RunDetachedLocalActorFlow(actorId, batchId, playerCreationDirective));
}
}
}
public void OnCatchUpReady(int actorId, int batchId, int nonce)
{
if (!PhotonNetwork.IsMasterClient || batchId <= 0 || nonce <= 0)
{
return;
}
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record != null && record.BatchId == batchId && record.State == LateJoinState.CatchUpPicking && record.ChallengeNonce == nonce && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players.ContainsKey(actorId))
{
string item = DirectiveKey(actorId, batchId);
if (catchUpReadyActors.Add(item))
{
ReceiveCatchUpBegin(actorId, batchId);
LateJoinNetwork.BroadcastCatchUpBegin(actorId, batchId);
}
}
}
public void ReceiveCatchUpBegin(int actorId, int batchId)
{
if (batchId > 0)
{
catchUpBeginActors.Add(DirectiveKey(actorId, batchId));
}
}
public void ReceiveRollback(int actorId, int batchId, string reason)
{
rolledBackActors.Add(DirectiveKey(actorId, batchId));
if (!PhotonNetwork.IsMasterClient && (Object)(object)UI != (Object)null && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId)
{
UI.ShowSpectating(reason);
}
}
private IEnumerator RunDetachedLocalActorFlow(int actorId, int batchId, PlayerCreationDirective directive)
{
string key = DirectiveKey(actorId, batchId);
yield return Bridge.CreateLatePlayer(actorId, 0, directive.TeamId, directive.ColorId);
while (!IsRolledBack(actorId, batchId) && !IsActorInState(actorId, LateJoinState.CatchUpPicking) && !IsActorResolved(actorId, batchId))
{
yield return null;
}
if (!IsRolledBack(actorId, batchId) && IsActorInState(actorId, LateJoinState.CatchUpPicking))
{
Player player = Bridge.FindPlayerOwnedByActor(actorId);
if ((Object)(object)player != (Object)null)
{
yield return WaitForCatchUpBegin(actorId, batchId);
if (!IsRolledBack(actorId, batchId) && (Object)(object)catchUpCards != (Object)null)
{
yield return catchUpCards.RunCatchUp(player, (pickEndDecision != null) ? pickEndDecision.CatchUpCount : 0);
}
}
}
detachedLocalFlows.Remove(key);
}
private IEnumerator WaitForCatchUpBegin(int actorId, int batchId)
{
string key = DirectiveKey(actorId, batchId);
if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId && !localCatchUpReadySent.Contains(key))
{
localCatchUpReadySent.Add(key);
LateJoinNetwork.SendCatchUpReady(actorId, batchId, LocalChallengeNonce);
}
while (!catchUpBeginActors.Contains(key) && !IsRolledBack(actorId, batchId) && PhotonNetwork.InRoom)
{
yield return null;
}
}
private HashSet<int> GetActiveOwnerActorIds()
{
HashSet<int> hashSet = new HashSet<int>();
if ((Object)(object)PlayerManager.instance == (Object)null)
{
return hashSet;
}
foreach (Player player in PlayerManager.instance.players)
{
if (!((Object)(object)player == (Object)null) && !((Object)(object)player.data == (Object)null) && !((Object)(object)player.data.view == (Object)null))
{
hashSet.Add(player.data.view.OwnerActorNr);
}
}
return hashSet;
}
private int GetCanonicalActivePlayerCount()
{
if ((Object)(object)PlayerManager.instance == (Object)null)
{
return 0;
}
int num = 0;
foreach (Player player in PlayerManager.instance.players)
{
if (!((Object)(object)player == (Object)null) && !((Object)(object)player.data == (Object)null) && !((Object)(object)player.data.view == (Object)null))
{
LateJoinRecord record = coordinator.GetRecord(player.data.view.OwnerActorNr);
if (record == null || record.State == LateJoinState.Active)
{
num++;
}
}
}
return num;
}
private void RollBackActor(int actorId, int batchId, string reason, bool cleanPlayerObjects)
{
if (PhotonNetwork.IsMasterClient)
{
if (cleanPlayerObjects && Bridge != null)
{
Bridge.RollBackLatePlayer(actorId);
}
LateJoinRecord record = coordinator.GetRecord(actorId);
if (record != null && record.State != LateJoinState.Disconnected)
{
coordinator.RollBackToSpectator(actorId, reason);
}
ReceiveRollback(actorId, batchId, reason);
LateJoinNetwork.BroadcastRollback(actorId, batchId, reason);
BroadcastRecords();
}
}
private bool HasDirective(int actorId, int batchId)
{
return creationDirectives.ContainsKey(DirectiveKey(actorId, batchId));
}
private bool IsRolledBack(int actorId, int batchId)
{
return rolledBackActors.Contains(DirectiveKey(actorId, batchId));
}
private bool IsActorInState(int actorId, LateJoinState state)
{
LateJoinRecord record = GetRecord(actorId);
if (record != null)
{
return record.State == state;
}
return false;
}
private bool IsActorResolved(int actorId, int batchId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null)
{
return IsRolledBack(actorId, batchId);
}
if (record.State != LateJoinState.Active && record.State != LateJoinState.Disconnected)
{
if (record.State == LateJoinState.Spectating)
{
return record.BatchId == 0;
}
return false;
}
return true;
}
private static string DirectiveKey(int actorId, int batchId)
{
return actorId + ":" + batchId;
}
private void Update()
{
}
private void CheckSpectatorExclusionInvariant()
{
if (Bridge == null)
{
return;
}
foreach (LateJoinRecord record in Records)
{
if (record != null && record.State != LateJoinState.Active && record.State != LateJoinState.Disconnected && record.BatchId == 0 && record.State != LateJoinState.CatchUpPicking && (Object)(object)Bridge.FindPlayerOwnedByActor(record.ActorId) != (Object)null)
{
LogError("Spectator exclusion invariant violated for actor " + record.ActorId + ".");
}
}
}
private void BeginHostMigrationRecovery()
{
if (!hostMigrationRecoveryRunning && PhotonNetwork.IsMasterClient)
{
hostMigrationRecoveryRunning = true;
((MonoBehaviour)this).StartCoroutine(RecoverPendingAfterHostMigration());
}
}
private IEnumerator RecoverPendingAfterHostMigration()
{
try
{
yield return null;
yield return null;
float deadline = Time.realtimeSinceStartup + 8f;
while (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && (Bridge == null || !Bridge.IsSupportedGameMode() || (Object)(object)GameManager.instance == (Object)null || !GameManager.instance.isPlaying) && Time.realtimeSinceStartup < deadline)
{
yield return null;
}
if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null)
{
yield break;
}
LateJoinRecord[] array = (from r in mirroredRecords.Values
where r != null && r.State != LateJoinState.Active && r.State != LateJoinState.Disconnected
orderby r.ActorId
select r).ToArray();
HashSet<int> hashSet = ((Bridge != null && Bridge.IsTeamMode()) ? new HashSet<int>(Bridge.GetCurrentTeamIds()) : new HashSet<int>());
int lastObservedBatchId = (from r in mirroredRecords.Values
where r != null
select r.BatchId).DefaultIfEmpty(0).Max();
coordinator.ClearAndAdvanceBatchSequence(lastObservedBatchId);
pickEndDecision = null;
creationDirectives.Clear();
rolledBackActors.Clear();
catchUpReadyActors.Clear();
catchUpBeginActors.Clear();
localCatchUpReadySent.Clear();
LateJoinRecord[] array2 = array;
foreach (LateJoinRecord lateJoinRecord in array2)
{
if (PhotonNetwork.CurrentRoom.Players.ContainsKey(lateJoinRecord.ActorId))
{
if (Bridge != null && (Object)(object)Bridge.FindPlayerOwnedByActor(lateJoinRecord.ActorId) != (Object)null)
{
Bridge.RollBackLatePlayer(lateJoinRecord.ActorId);
}
bool flag = !Bridge.IsTeamMode() || (lateJoinRecord.SelectedTeamId.HasValue && hashSet.Contains(lateJoinRecord.SelectedTeamId.Value));
LateJoinRecord lateJoinRecord2 = coordinator.Register(lateJoinRecord.ActorId, lateJoinRecord.ProtocolVersion);
lateJoinRecord2.SelectedTeamId = (flag ? lateJoinRecord.SelectedTeamId : ((int?)null));
coordinator.ResetPendingForHostMigration(lateJoinRecord.ActorId, "Recovering after host migration", flag);
LateJoinNetwork.SendChallenge(nonce: lateJoinRecord2.ChallengeNonce = NextPositiveNonce(), actorId: lateJoinRecord.ActorId);
}
}
mirroredRecords.Clear();
BroadcastRecords();
LogInfo("Recovered " + array.Length + " pending late-join record(s) after host migration.");
}
finally
{
hostMigrationRecoveryRunning = false;
}
}
public int NextLocalTeamRequestNonce()
{
if (localTeamRequestNonce == int.MaxValue)
{
localTeamRequestNonce = 0;
}
localTeamRequestNonce++;
return localTeamRequestNonce;
}
public void BroadcastRecords()
{
if (PhotonNetwork.IsMasterClient)
{
LateJoinNetwork.BroadcastRecords(SnapshotCodec.EncodeRecords(coordinator.Records));
}
}
private int NextPositiveNonce()
{
lock (nonceRandom)
{
return nonceRandom.Next(1, int.MaxValue);
}
}
private void LogInfo(string message)
{
if (logger != null)
{
logger.LogInfo((object)message);
}
}
private void LogWarning(string message)
{
if (logger != null)
{
logger.LogWarning((object)message);
}
}
private void LogDebug(string message)
{
if (logger != null)
{
logger.LogDebug((object)message);
}
}
private void LogError(string message)
{
if (logger != null)
{
logger.LogError((object)message);
}
}
}
public static class LateJoinNetwork
{
public static LateJoinManager Manager { get; set; }
public static void SendChallenge(int actorId, int nonce)
{
SendToActor(actorId, "RPCT_Challenge", actorId, nonce, 100);
}
public static void SendHello(int nonce)
{
if (PhotonNetwork.LocalPlayer != null)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCH_Hello", new object[3]
{
PhotonNetwork.LocalPlayer.ActorNumber,
nonce,
100
});
}
}
public static void SendHelloAccepted(int actorId)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_HelloAccepted", new object[2] { actorId, 100 });
}
public static void SendHelloRejected(int actorId, string reason)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_HelloRejected", new object[3]
{
actorId,
100,
reason ?? string.Empty
});
}
public static void RequestTeam(int teamId)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.LocalPlayer != null)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCH_SelectTeam", new object[4]
{
PhotonNetwork.LocalPlayer.ActorNumber,
Manager.LocalChallengeNonce,
Manager.NextLocalTeamRequestNonce(),
teamId
});
}
}
public static void SendSessionSnapshot(int actorId, int challengeNonce, SessionSnapshot snapshot)
{
if (snapshot != null && challengeNonce > 0)
{
SendToActor(actorId, "RPCT_SessionSnapshot", actorId, challengeNonce, snapshot.GameModeId ?? string.Empty, snapshot.CurrentLevelId, snapshot.BattleOngoing, snapshot.IsTransitioning, snapshot.PickEndGeneration, snapshot.ActivePlayerCount, snapshot.TeamIds ?? new int[0], snapshot.TeamPoints ?? new int[0], snapshot.TeamRounds ?? new int[0]);
}
}
public static void SendSessionReady(int challengeNonce, int currentLevelId)
{
if (PhotonNetwork.LocalPlayer != null && challengeNonce > 0)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCH_SessionReady", new object[3]
{
PhotonNetwork.LocalPlayer.ActorNumber,
challengeNonce,
currentLevelId
});
}
}
public static void BroadcastPickEndDecision(int generation, int batchId, int catchUpCount, int[] actorIds)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_PickEndDecision", new object[4]
{
generation,
batchId,
catchUpCount,
actorIds ?? new int[0]
});
}
public static void BroadcastBeginPlayerCreation(int actorId, int batchId, int teamId, int colorId)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCT_BeginPlayerCreation", new object[4] { actorId, batchId, teamId, colorId });
}
public static void SendCatchUpReady(int actorId, int batchId, int nonce)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCH_CatchUpReady", new object[3] { actorId, batchId, nonce });
}
public static void BroadcastCatchUpBegin(int actorId, int batchId)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_CatchUpBegin", new object[2] { actorId, batchId });
}
public static void BroadcastRollback(int actorId, int batchId, string reason)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_RollBackActor", new object[3]
{
actorId,
batchId,
reason ?? string.Empty
});
}
public static void BroadcastRecords(EncodedRecords snapshot)
{
if (snapshot != null)
{
NetworkingManager.RPC(typeof(LateJoinNetwork), "RPCA_RecordSnapshot", new object[7] { snapshot.ActorIds, snapshot.States, snapshot.TeamIds, snapshot.BatchIds, snapshot.CatchUpCounts, snapshot.ProtocolVersions, snapshot.Statuses });
}
}
[UnboundRPC]
public static void RPCT_Challenge(int targetActorId, int nonce, int hostProtocolVersion)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActorId)
{
Manager.OnChallengeReceived(nonce, hostProtocolVersion);
}
}
[UnboundRPC]
public static void RPCH_Hello(int actorId, int nonce, int protocolVersion)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.IsMasterClient)
{
Manager.OnHello(actorId, nonce, protocolVersion);
}
}
[UnboundRPC]
public static void RPCA_HelloAccepted(int actorId, int protocolVersion)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.OnHelloAccepted(actorId, protocolVersion);
}
}
[UnboundRPC]
public static void RPCA_HelloRejected(int actorId, int hostProtocolVersion, string reason)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.OnHelloRejected(actorId, hostProtocolVersion, reason);
}
}
[UnboundRPC]
public static void RPCH_SelectTeam(int actorId, int challengeNonce, int commandNonce, int teamId)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.IsMasterClient)
{
Manager.OnTeamRequested(actorId, challengeNonce, commandNonce, teamId);
}
}
[UnboundRPC]
public static void RPCA_PickEndDecision(int generation, int batchId, int catchUpCount, int[] actorIds)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.ReceivePickEndDecision(generation, batchId, catchUpCount, actorIds);
}
}
[UnboundRPC]
public static void RPCT_BeginPlayerCreation(int actorId, int batchId, int teamId, int colorId)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.ReceiveBeginPlayerCreation(actorId, batchId, teamId, colorId);
}
}
[UnboundRPC]
public static void RPCH_CatchUpReady(int actorId, int batchId, int nonce)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.IsMasterClient)
{
Manager.OnCatchUpReady(actorId, batchId, nonce);
}
}
[UnboundRPC]
public static void RPCA_CatchUpBegin(int actorId, int batchId)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.ReceiveCatchUpBegin(actorId, batchId);
}
}
[UnboundRPC]
public static void RPCA_RollBackActor(int actorId, int batchId, string reason)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.ReceiveRollback(actorId, batchId, reason);
}
}
[UnboundRPC]
public static void RPCT_SessionSnapshot(int targetActorId, int challengeNonce, string gameModeId, int currentLevelId, bool battleOngoing, bool isTransitioning, int pickEndGeneration, int activePlayerCount, int[] teamIds, int[] teamPoints, int[] teamRounds)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActorId)
{
Manager.OnSessionSnapshot(challengeNonce, new SessionSnapshot
{
GameModeId = gameModeId,
CurrentLevelId = currentLevelId,
BattleOngoing = battleOngoing,
IsTransitioning = isTransitioning,
PickEndGeneration = pickEndGeneration,
ActivePlayerCount = activePlayerCount,
TeamIds = teamIds,
TeamPoints = teamPoints,
TeamRounds = teamRounds
});
}
}
[UnboundRPC]
public static void RPCH_SessionReady(int actorId, int challengeNonce, int currentLevelId)
{
if (!((Object)(object)Manager == (Object)null) && PhotonNetwork.IsMasterClient)
{
Manager.OnSessionReady(actorId, challengeNonce, currentLevelId);
}
}
[UnboundRPC]
public static void RPCA_RecordSnapshot(int[] actorIds, int[] states, int[] teamIds, int[] batchIds, int[] catchUpCounts, int[] protocolVersions, string[] statuses)
{
if ((Object)(object)Manager != (Object)null)
{
Manager.ApplyRecordSnapshot(actorIds, states, teamIds, batchIds, catchUpCounts, protocolVersions, statuses);
}
}
private static void SendToActor(int actorId, string methodName, params object[] data)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
RaiseEventOptions val = new RaiseEventOptions();
val.TargetActors = new int[1] { actorId };
RaiseEventOptions val2 = val;
NetworkingManager.RPC(typeof(LateJoinNetwork), methodName, val2, SendOptions.SendReliable, data);
}
}
public sealed class LateJoinPhotonCallbacks : MonoBehaviourPunCallbacks
{
public override void OnJoinedRoom()
{
if ((Object)(object)LateJoinManager.Instance != (Object)null)
{
LateJoinManager.Instance.OnLocalJoinedRoom();
}
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
if (newPlayer != null && !((Object)(object)LateJoinManager.Instance == (Object)null) && PhotonNetwork.IsMasterClient && (Object)(object)GameManager.instance != (Object)null && GameManager.instance.isPlaying)
{
LateJoinManager.Instance.OnRemoteActorEntered(newPlayer.ActorNumber);
}
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
if (otherPlayer != null && (Object)(object)LateJoinManager.Instance != (Object)null)
{
LateJoinManager.Instance.OnRemoteActorLeft(otherPlayer);
}
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
if (newMasterClient != null && (Object)(object)LateJoinManager.Instance != (Object)null)
{
LateJoinManager.Instance.OnMasterClientSwitched(newMasterClient.ActorNumber);
}
}
}
public sealed class RWFBridge
{
public const string SupportedRwfVersion = "2.2.2";
public const float CreationTimeoutSeconds = 12f;
private static readonly BindingFlags RwfFieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static FieldInfo teamPointsField;
private static FieldInfo teamRoundsField;
private static FieldInfo isTransitioningField;
private static MethodInfo createPlayerMethod;
public static bool Compatible { get; private set; }
public static bool ValidateCompatibility(ManualLogSource logger)
{
createPlayerMethod = typeof(PlayerAssignerExtensions).GetMethod("CreatePlayer", BindingFlags.Static | BindingFlags.Public, null, new Type[3]
{
typeof(PlayerAssigner),
typeof(LobbyCharacter),
typeof(InputDevice)
}, null);
teamPointsField = typeof(RWFGameMode).GetField("teamPoints", RwfFieldFlags);
teamRoundsField = typeof(RWFGameMode).GetField("teamRounds", RwfFieldFlags);
isTransitioningField = typeof(RWFGameMode).GetField("isTransitioning", RwfFieldFlags);
Compatible = string.Equals("2.2.2", "2.2.2", StringComparison.Ordinal) && createPlayerMethod != null && teamPointsField != null && teamRoundsField != null && isTransitioningField != null;
if (!Compatible && logger != null)
{
logger.LogError((object)"MidGameJoin disabled: incompatible RoundsWithFriends runtime. Expected RWF 2.2.2 with CreatePlayer(PlayerAssigner, LobbyCharacter, InputDevice), teamPoints, teamRounds and isTransitioning. Detected RWF 2.2.2.");
}
return Compatible;
}
public bool IsSupportedGameMode()
{
if (!Compatible || GameModeManager.CurrentHandler == null || !(GameModeManager.CurrentHandler.GameMode is RWFGameMode))
{
return false;
}
if (!string.Equals(GameModeManager.CurrentHandlerID, "Deathmatch", StringComparison.Ordinal))
{
return string.Equals(GameModeManager.CurrentHandlerID, "Team Deathmatch", StringComparison.Ordinal);
}
return true;
}
public bool IsTeamMode()
{
if (IsSupportedGameMode())
{
return GameModeManager.CurrentHandler.AllowTeams;
}
return false;
}
public IReadOnlyList<int> GetCurrentTeamIds()
{
if ((Object)(object)PlayerManager.instance == (Object)null)
{
return new int[0];
}
return (from id in (from p in PlayerManager.instance.players
where (Object)(object)p != (Object)null
select p.teamID).Distinct()
orderby id
select id).ToArray();
}
public IReadOnlyList<int> GetExistingCardCounts()
{
if ((Object)(object)PlayerManager.instance == (Object)null)
{
return new int[0];
}
return (from p in PlayerManager.instance.players
where (Object)(object)p != (Object)null && (Object)(object)p.data != (Object)null && p.data.currentCards != null
select p.data.currentCards.Count).ToArray();
}
public SessionSnapshot CaptureSessionSnapshot()
{
RWFGameMode rwfGameMode = GetRwfGameMode();
Dictionary<int, int> points = ReadScoreDictionary(rwfGameMode, teamPointsField);
Dictionary<int, int> rounds = ReadScoreDictionary(rwfGameMode, teamRoundsField);
int[] array = (from id in points.Keys.Concat(rounds.Keys).Distinct()
orderby id
select id).ToArray();
return new SessionSnapshot
{
GameModeId = (GameModeManager.CurrentHandlerID ?? string.Empty),
CurrentLevelId = (((Object)(object)MapManager.instance != (Object)null) ? MapManager.instance.currentLevelID : (-1)),
BattleOngoing = ((Object)(object)GameManager.instance != (Object)null && GameManager.instance.battleOngoing),
IsTransitioning = ((Object)(object)rwfGameMode != (Object)null && isTransitioningField != null && (bool)isTransitioningField.GetValue(rwfGameMode)),
PickEndGeneration = 0,
ActivePlayerCount = (((Object)(object)PlayerManager.instance != (Object)null) ? PlayerManager.instance.players.Count : 0),
TeamIds = array,
TeamPoints = array.Select((int id) => points.ContainsKey(id) ? points[id] : 0).ToArray(),
TeamRounds = array.Select((int id) => rounds.ContainsKey(id) ? rounds[id] : 0).ToArray()
};
}
public IEnumerator ApplySessionSnapshot(SessionSnapshot snapshot)
{
if (snapshot == null)
{
yield break;
}
RWFGameMode rwfGameMode = GetRwfGameMode();
if (!((Object)(object)rwfGameMode == (Object)null) && snapshot.TeamIds != null && snapshot.TeamPoints != null && snapshot.TeamRounds != null && snapshot.TeamIds.Length == snapshot.TeamPoints.Length && snapshot.TeamIds.Length == snapshot.TeamRounds.Length)
{
Dictionary<int, int> dictionary = new Dictionary<int, int>();
Dictionary<int, int> dictionary2 = new Dictionary<int, int>();
for (int i = 0; i < snapshot.TeamIds.Length; i++)
{
dictionary[snapshot.TeamIds[i]] = snapshot.TeamPoints[i];
dictionary2[snapshot.TeamIds[i]] = snapshot.TeamRounds[i];
}
teamPointsField.SetValue(rwfGameMode, dictionary);
teamRoundsField.SetValue(rwfGameMode, dictionary2);
isTransitioningField.SetValue(rwfGameMode, snapshot.IsTransitioning);
if ((Object)(object)UIHandler.instance != (Object)null)
{
UIHandlerExtensions.ShowRoundCounterSmall(UIHandler.instance, dictionary, dictionary2);
}
yield return null;
}
}
public IEnumerator CreateLatePlayer(int actorId, int localId, int teamId, int colorId)
{
if (Compatible && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(actorId, out var value) && value != null)
{
if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actorId)
{
LobbyCharacter val = new LobbyCharacter(value, colorId, localId)
{
teamID = teamId
};
yield return PlayerAssignerExtensions.CreatePlayer(PlayerAssigner.instance, val, (InputDevice)null);
}
float deadline = Time.realtimeSinceStartup + 12f;
while ((Object)(object)FindPlayerOwnedByActor(actorId) == (Object)null && Time.realtimeSinceStartup < deadline)
{
yield return null;
}
}
}
public Player FindPlayerOwnedByActor(int actorId)
{
if ((Object)(object)PlayerManager.instance == (Object)null)
{
return null;
}
foreach (Player player in PlayerManager.instance.players)
{
if (!((Object)(object)player == (Object)null))
{
LobbyCharacter character = PlayerExtensions.GetAdditionalData(player).character;
if (character != null && character.actorID == actorId)
{
return player;
}
}
}
return null;
}
public void EnsurePlayerJoined(Player player)
{
if (!((Object)(object)player == (Object)null) && GameModeManager.CurrentHandler != null)
{
RWFGameMode rwfGameMode = GetRwfGameMode();
Dictionary<int, int> dictionary = ReadScoreDictionary(rwfGameMode, teamPointsField);
Dictionary<int, int> dictionary2 = ReadScoreDictionary(rwfGameMode, teamRoundsField);
if (!dictionary.ContainsKey(player.teamID) || !dictionary2.ContainsKey(player.teamID))
{
GameModeManager.CurrentHandler.PlayerJoined(player);
}
}
}
public void RollBackLatePlayer(int actorId)
{
if (PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(actorId, out var value) && value != null)
{
RollBackLatePlayer(value);
}
}
public void RollBackLatePlayer(Player networkPlayer)
{
if (PhotonNetwork.IsMasterClient && networkPlayer != null)
{
PhotonNetwork.DestroyPlayerObjects(networkPlayer);
}
}
public int GetColorForTeam(int teamId)
{
if ((Object)(object)PlayerManager.instance != (Object)null)
{
foreach (Player player in PlayerManager.instance.players)
{
if (!((Object)(object)player == (Object)null) && player.teamID == teamId)
{
LobbyCharacter character = PlayerExtensions.GetAdditionalData(player).character;
if (character != null)
{
return NormalizeColorId(character.colorID);
}
}
}
}
return NormalizeColorId(teamId);
}
public int GetNextFfaTeamId()
{
HashSet<int> hashSet = new HashSet<int>(GetCurrentTeamIds());
int i;
for (i = 0; hashSet.Contains(i); i++)
{
}
return i;
}
public int GetAvailableColorId()
{
int num = Math.Max(1, RWFMod.MaxColorsHardLimit);
HashSet<int> hashSet = new HashSet<int>();
if ((Object)(object)PlayerManager.instance != (Object)null)
{
foreach (Player player in PlayerManager.instance.players)
{
if (!((Object)(object)player == (Object)null))
{
LobbyCharacter character = PlayerExtensions.GetAdditionalData(player).character;
if (character != null)
{
hashSet.Add(NormalizeColorId(character.colorID));
}
}
}
}
for (int i = 0; i < num; i++)
{
if (!hashSet.Contains(i))
{
return i;
}
}
return NormalizeColorId(((Object)(object)PlayerManager.instance != (Object)null) ? PlayerManager.instance.players.Count : 0);
}
private static RWFGameMode GetRwfGameMode()
{
if (GameModeManager.CurrentHandler != null)
{
MonoBehaviour gameMode = GameModeManager.CurrentHandler.GameMode;
return (RWFGameMode)(object)((gameMode is RWFGameMode) ? gameMode : null);
}
return null;
}
private static Dictionary<int, int> ReadScoreDictionary(RWFGameMode gameMode, FieldInfo field)
{
if ((Object)(object)gameMode == (Object)null || field == null)
{
return new Dictionary<int, int>();
}
if (field.GetValue(gameMode) is Dictionary<int, int> dictionary)
{
return new Dictionary<int, int>(dictionary);
}
return new Dictionary<int, int>();
}
private static int NormalizeColorId(int colorId)
{
int num = Math.Max(1, RWFMod.MaxColorsHardLimit);
int num2 = colorId % num;
if (num2 >= 0)
{
return num2;
}
return num2 + num;
}
}
public sealed class SessionSyncController : MonoBehaviour
{
private const float PlayerSyncTimeoutSeconds = 10f;
private LateJoinManager manager;
private RWFBridge bridge;
private LateJoinUI ui;
private Coroutine bootstrapRoutine;
public void Initialize(LateJoinManager lateJoinManager, RWFBridge rwfBridge, LateJoinUI lateJoinUi)
{
manager = lateJoinManager;
bridge = rwfBridge;
ui = lateJoinUi;
}
public void ApplySnapshot(SessionSnapshot snapshot)
{
if (bootstrapRoutine != null)
{
((MonoBehaviour)this).StopCoroutine(bootstrapRoutine);
}
bootstrapRoutine = ((MonoBehaviour)this).StartCoroutine(Bootstrap(snapshot));
}
private IEnumerator Bootstrap(SessionSnapshot snapshot)
{
if (snapshot == null || bridge == null || string.IsNullOrEmpty(snapshot.GameModeId))
{
yield break;
}
ui.ShowSpectating("Synchronizing match");
if (GameModeManager.CurrentHandlerID != snapshot.GameModeId)
{
GameModeManager.SetGameMode(snapshot.GameModeId);
}
float handlerDeadline = Time.realtimeSinceStartup + 10f;
while (GameModeManager.CurrentHandler == null && Time.realtimeSinceStartup < handlerDeadline)
{
yield return null;
}
if (GameModeManager.CurrentHandler == null)
{
yield break;
}
if ((Object)(object)GameManager.instance != (Object)null)
{
GameManager.instance.isPlaying = true;
GameManager.instance.battleOngoing = false;
}
yield return bridge.ApplySessionSnapshot(snapshot);
float playerDeadline = Time.realtimeSinceStartup + 10f;
while ((Object)(object)PlayerManager.instance != (Object)null && !SessionSyncRules.HasAllActivePlayers(PlayerManager.instance.players.Count, snapshot.ActivePlayerCount) && Time.realtimeSinceStartup < playerDeadline)
{
yield return null;
}
if (!SessionSyncRules.HasAllActivePlayers((!((Object)(object)PlayerManager.instance == (Object)null)) ? PlayerManager.instance.players.Count : 0, snapshot.ActivePlayerCount))
{
ui.ShowSpectating("Resynchronizing active players");
LateJoinNetwork.SendHello(manager.LocalChallengeNonce);
bootstrapRoutine = null;
yield break;
}
if ((Object)(object)MapManager.instance != (Object)null && snapshot.CurrentLevelId >= 0)
{
MapManager.instance.currentLevelID = snapshot.CurrentLevelId;
MapManager.instance.CallInNewMapAndMovePlayers(snapshot.CurrentLevelId);
}
if ((Object)(object)PlayerManager.instance != (Object)null)
{
PlayerManager.instance.SetPlayersSimulated(snapshot.BattleOngoing);
ExtensionMethods.InvokeMethod((object)PlayerManager.instance, "SetPlayersVisible", new object[1] { true });
}
manager.PickEndGeneration = snapshot.PickEndGeneration;
LateJoinNetwork.SendSessionReady(manager.LocalChallengeNonce, snapshot.CurrentLevelId);
ui.ShowSpectating("Waiting for card picks to finish");
bootstrapRoutine = null;
}
}
public sealed class SpectatorController : MonoBehaviour
{
public LateJoinManager Manager { get; set; }
public LateJoinUI UI { get; set; }
private void Update()
{
if ((Object)(object)Manager == (Object)null || PhotonNetwork.LocalPlayer == null)
{
return;
}
LateJoinRecord localRecord = Manager.LocalRecord;
if (localRecord == null)
{
return;
}
Player val = ((Manager.Bridge == null) ? null : Manager.Bridge.FindPlayerOwnedByActor(PhotonNetwork.LocalPlayer.ActorNumber));
if (localRecord.State == LateJoinState.Active)
{
if ((Object)(object)val != (Object)null && (Object)(object)val.data != (Object)null && (Object)(object)val.data.input != (Object)null)
{
((Behaviour)val.data.input).enabled = true;
}
if ((Object)(object)UI != (Object)null)
{
UI.Hide();
}
return;
}
if ((Object)(object)val != (Object)null && (Object)(object)val.data != (Object)null && (Object)(object)val.data.input != (Object)null)
{
((Behaviour)val.data.input).enabled = localRecord.State == LateJoinState.CatchUpPicking;
}
if (localRecord.State != LateJoinState.CatchUpPicking)
{
if ((Object)(object)CardChoiceVisuals.instance != (Object)null)
{
CardChoiceVisuals.instance.Hide();
}
if ((Object)(object)UI != (Object)null)
{
UI.ShowSpectating(localRecord.Status);
}
}
}
}
}
namespace MidGameJoin.Patches
{
[HarmonyPatch]
internal static class DisconnectSafetyPatches
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("RWF.PrivateRoomHandler");
if (!(type == null))
{
return AccessTools.Method(type, "OnPlayerLeftRoom", (Type[])null, (Type[])null);
}
return null;
}
private static void Postfix(Player otherPlayer)
{
if (otherPlayer != null && !((Object)(object)LateJoinManager.Instance == (Object)null))
{
LateJoinManager.Instance.OnRemoteActorLeft(otherPlayer);
}
}
}
[HarmonyPatch]
internal static class HostMigrationPatches
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("RWF.PrivateRoomHandler");
if (!(type == null))
{
return AccessTools.Method(type, "OnMasterClientSwitched", (Type[])null, (Type[])null);
}
return null;
}
private static void Postfix(Player newMaster)
{
if (newMaster != null && !((Object)(object)LateJoinManager.Instance == (Object)null))
{
LateJoinManager.Instance.OnRwfHostRestartIssued(newMaster.ActorNumber);
}
}
}
}
namespace MidGameJoin.Core
{
public sealed class InsertionBatch
{
public int Id { get; private set; }
public int CatchUpCount { get; private set; }
public IReadOnlyList<int> ActorIds { get; private set; }
public InsertionBatch(int id, int catchUpCount, IReadOnlyList<int> actorIds)
{
Id = id;
CatchUpCount = catchUpCount;
ActorIds = actorIds;
}
}
public sealed class LateJoinCoordinator
{
private readonly Dictionary<int, LateJoinRecord> records = new Dictionary<int, LateJoinRecord>();
private int nextBatchId = 1;
public IReadOnlyList<LateJoinRecord> Records => records.Values.OrderBy((LateJoinRecord r) => r.ActorId).ToArray();
public LateJoinRecord Register(int actorId, int protocolVersion)
{
if (records.TryGetValue(actorId, out var value))
{
return value;
}
LateJoinRecord lateJoinRecord = new LateJoinRecord(actorId, protocolVersion);
records.Add(actorId, lateJoinRecord);
return lateJoinRecord;
}
public LateJoinRecord GetRecord(int actorId)
{
if (!records.TryGetValue(actorId, out var value))
{
return null;
}
return value;
}
public bool HandshakeAccepted(int actorId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || record.State != LateJoinState.Connecting)
{
return false;
}
record.State = LateJoinState.Spectating;
record.Status = "Synchronizing match";
return true;
}
public bool SelectTeam(int actorId, int teamId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || teamId < 0)
{
return false;
}
if (record.State != LateJoinState.Spectating && record.State != LateJoinState.TeamSelected && record.State != LateJoinState.WaitingForInsertion)
{
return false;
}
record.SelectedTeamId = teamId;
if (record.State != LateJoinState.WaitingForInsertion)
{
record.State = LateJoinState.TeamSelected;
}
return true;
}
public bool MarkWaiting(int actorId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null)
{
return false;
}
if (record.State != LateJoinState.Spectating && record.State != LateJoinState.TeamSelected && record.State != LateJoinState.WaitingForInsertion)
{
return false;
}
record.State = LateJoinState.WaitingForInsertion;
record.Status = "Waiting for card picks to finish";
return true;
}
public InsertionBatch LockBatch(IReadOnlyList<int> actorIds, int catchUpCount)
{
if (actorIds == null)
{
throw new ArgumentNullException("actorIds");
}
if (catchUpCount < 0)
{
throw new ArgumentOutOfRangeException("catchUpCount");
}
List<int> list = new List<int>();
for (int i = 0; i < actorIds.Count; i++)
{
LateJoinRecord record = GetRecord(actorIds[i]);
if (record != null && record.BatchId == 0 && record.State != LateJoinState.Active && record.State != LateJoinState.Disconnected)
{
list.Add(record.ActorId);
}
}
if (list.Count == 0)
{
return new InsertionBatch(0, catchUpCount, list.ToArray());
}
int num = nextBatchId++;
foreach (int item in list)
{
LateJoinRecord lateJoinRecord = records[item];
lateJoinRecord.BatchId = num;
lateJoinRecord.CatchUpCount = catchUpCount;
lateJoinRecord.CatchUpComplete = catchUpCount == 0;
lateJoinRecord.State = LateJoinState.WaitingForInsertion;
}
return new InsertionBatch(num, catchUpCount, list.ToArray());
}
public bool MarkCatchUpComplete(int actorId, int batchId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || batchId <= 0 || record.BatchId != batchId || record.CatchUpComplete)
{
return false;
}
if (record.State != LateJoinState.CatchUpPicking && record.State != LateJoinState.WaitingForInsertion)
{
return false;
}
record.CatchUpComplete = true;
return true;
}
public bool Activate(int actorId, int batchId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || record.State == LateJoinState.Active || record.BatchId != batchId)
{
return false;
}
if (!record.CatchUpComplete && record.CatchUpCount > 0)
{
return false;
}
record.State = LateJoinState.Active;
record.Status = string.Empty;
return true;
}
public bool RollBackToSpectator(int actorId, string status)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || record.State == LateJoinState.Disconnected || record.State == LateJoinState.Active)
{
return false;
}
record.State = LateJoinState.Spectating;
record.BatchId = 0;
record.CatchUpCount = 0;
record.StartingCardCount = 0;
record.CatchUpComplete = false;
record.Status = status ?? string.Empty;
return true;
}
public bool TrySelectTeamCommand(int actorId, int commandNonce, int teamId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || commandNonce <= 0 || commandNonce <= record.LastTeamNonce)
{
return false;
}
if (!SelectTeam(actorId, teamId))
{
return false;
}
record.LastTeamNonce = commandNonce;
return true;
}
public bool IsBatchResolved(int batchId)
{
if (batchId <= 0)
{
return true;
}
return !records.Values.Any((LateJoinRecord record) => record.BatchId == batchId && record.State != LateJoinState.Active && record.State != LateJoinState.Disconnected);
}
public void ClearAndAdvanceBatchSequence(int lastObservedBatchId)
{
records.Clear();
if (lastObservedBatchId >= nextBatchId && lastObservedBatchId < int.MaxValue)
{
nextBatchId = lastObservedBatchId + 1;
}
}
public bool ResetPendingForHostMigration(int actorId, string status, bool keepSelectedTeam)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || record.State == LateJoinState.Active || record.State == LateJoinState.Disconnected)
{
return false;
}
record.State = LateJoinState.Connecting;
record.BatchId = 0;
record.CatchUpCount = 0;
record.StartingCardCount = 0;
record.CatchUpComplete = false;
record.SessionReady = false;
record.ChallengeNonce = 0;
record.LastTeamNonce = 0;
if (!keepSelectedTeam)
{
record.SelectedTeamId = null;
}
record.Status = status ?? string.Empty;
return true;
}
public bool Disconnect(int actorId)
{
LateJoinRecord record = GetRecord(actorId);
if (record == null || record.State == LateJoinState.Disconnected)
{
return false;
}
record.State = LateJoinState.Disconnected;
record.BatchId = 0;
record.CatchUpComplete = true;
record.Status = "Disconnected";
return true;
}
}
public sealed class LateJoinRecord
{
public int ActorId { get; private set; }
public int ProtocolVersion { get; set; }
public LateJoinState State { get; set; }
public int? SelectedTeamId { get; set; }
public int BatchId { get; set; }
public int CatchUpCount { get; set; }
public int StartingCardCount { get; set; }
public bool CatchUpComplete { get; set; }
public string Status { get; set; }
public int ChallengeNonce { get; set; }
public int LastTeamNonce { get; set; }
public bool SessionReady { get; set; }
public LateJoinRecord(int actorId, int protocolVersion)
{
ActorId = actorId;
ProtocolVersion = protocolVersion;
State = LateJoinState.Connecting;
Status = string.Empty;
}
}
public static class LateJoinRules
{
public static int CalculateCatchUpCount(IReadOnlyList<int> cardCounts)
{
if (cardCounts == null || cardCounts.Count == 0)
{
return 0;
}
long num = 0L;
for (int i = 0; i < cardCounts.Count; i++)
{
if (cardCounts[i] < 0)
{
throw new ArgumentOutOfRangeException("cardCounts", "Card counts cannot be negative.");
}
num += cardCounts[i];
}
return (int)(num / cardCounts.Count);
}
}
public enum LateJoinState
{
Connecting,
Spectating,
TeamSelected,
WaitingForInsertion,
CatchUpPicking,
Active,
Disconnected
}
public static class ProtocolInfo
{
public const int ProtocolMajor = 1;
public const int ProtocolVersion = 100;
public const string PluginGuid = "com.thunderstore.midgamejoin";
public const string PluginName = "MidGameJoin";
public const string PluginVersion = "0.1.3";
}
public sealed class SessionSnapshot
{
public string GameModeId { get; set; }
public int CurrentLevelId { get; set; }
public bool BattleOngoing { get; set; }
public bool IsTransitioning { get; set; }
public int PickEndGeneration { get; set; }
public int ActivePlayerCount { get; set; }
public int[] TeamIds { get; set; }
public int[] TeamPoints { get; set; }
public int[] TeamRounds { get; set; }
}
public static class SessionSyncRules
{
public static bool HasAllActivePlayers(int actualPlayers, int expectedPlayers)
{
if (actualPlayers < 0)
{
actualPlayers = 0;
}
if (expectedPlayers < 0)
{
expectedPlayers = 0;
}
return actualPlayers >= expectedPlayers;
}
}
public sealed class EncodedRecords
{
public int[] ActorIds { get; set; }
public int[] States { get; set; }
public int[] TeamIds { get; set; }
public int[] BatchIds { get; set; }
public int[] CatchUpCounts { get; set; }
public int[] ProtocolVersions { get; set; }
public string[] Statuses { get; set; }
}
public static class SnapshotCodec
{
public static EncodedRecords EncodeRecords(IReadOnlyList<LateJoinRecord> records)
{
if (records == null)
{
throw new ArgumentNullException("records");
}
EncodedRecords encodedRecords = new EncodedRecords
{
ActorIds = new int[records.Count],
States = new int[records.Count],
TeamIds = new int[records.Count],
BatchIds = new int[records.Count],
CatchUpCounts = new int[records.Count],
ProtocolVersions = new int[records.Count],
Statuses = new string[records.Count]
};
for (int i = 0; i < records.Count; i++)
{
LateJoinRecord lateJoinRecord = records[i];
encodedRecords.ActorIds[i] = lateJoinRecord.ActorId;
encodedRecords.States[i] = (int)lateJoinRecord.State;
encodedRecords.TeamIds[i] = (lateJoinRecord.SelectedTeamId.HasValue ? lateJoinRecord.SelectedTeamId.Value : (-1));
encodedRecords.BatchIds[i] = lateJoinRecord.BatchId;
encodedRecords.CatchUpCounts[i] = lateJoinRecord.CatchUpCount;
encodedRecords.ProtocolVersions[i] = lateJoinRecord.ProtocolVersion;
encodedRecords.Statuses[i] = lateJoinRecord.Status ?? string.Empty;
}
return encodedRecords;
}
public static LateJoinRecord[] DecodeRecords(int[] actorIds, int[] states, int[] teamIds, int[] batchIds, int[] catchUpCounts, int[] protocolVersions, string[] statuses)
{
if (actorIds == null || states == null || teamIds == null || batchIds == null || catchUpCounts == null || protocolVersions == null || statuses == null)
{
throw new ArgumentNullException("Snapshot record arrays cannot be null.");
}
int num = actorIds.Length;
if (states.Length != num || teamIds.Length != num || batchIds.Length != num || catchUpCounts.Length != num || protocolVersions.Length != num || statuses.Length != num)
{
throw new ArgumentException("Snapshot record arrays must have identical lengths.");
}
LateJoinRecord[] array = new LateJoinRecord[num];
for (int i = 0; i < num; i++)
{
if (!Enum.IsDefined(typeof(LateJoinState), states[i]))
{
throw new ArgumentException("Invalid late join state value.");
}
LateJoinRecord lateJoinRecord = new LateJoinRecord(actorIds[i], protocolVersions[i])
{
State = (LateJoinState)states[i],
SelectedTeamId = ((teamIds[i] < 0) ? ((int?)null) : new int?(teamIds[i])),
BatchId = batchIds[i],
CatchUpCount = catchUpCounts[i],
Status = (statuses[i] ?? string.Empty)
};
array[i] = lateJoinRecord;
}
return array;
}
}
}