Decompiled source of RoundsMidLeave v0.0.1

BepInEx/plugins/RoundsMidLeave/RoundsMidLeave.dll

Decompiled 5 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RoundsMidJoin
{
	public static class MidJoinManager
	{
		private static readonly HashSet<int> _disconnectedActors = new HashSet<int>();

		private static readonly Queue<Player> _pendingJoins = new Queue<Player>();

		public static bool HasPendingJoins => _pendingJoins.Count > 0;

		public static int DisconnectedCount => _disconnectedActors.Count;

		public static void HandlePlayerLeft(Player photonPlayer)
		{
			Plugin.ModLogger.LogInfo((object)$"[RoundsMidJoin] Player '{photonPlayer.NickName}' (actor #{photonPlayer.ActorNumber}) left.");
			_disconnectedActors.Add(photonPlayer.ActorNumber);
		}

		public static void HandlePlayerJoined(Player photonPlayer)
		{
			Plugin.ModLogger.LogInfo((object)$"[RoundsMidJoin] Player '{photonPlayer.NickName}' (actor #{photonPlayer.ActorNumber}) joined.");
			_disconnectedActors.Remove(photonPlayer.ActorNumber);
			_pendingJoins.Enqueue(photonPlayer);
		}

		public static bool IsActorDisconnected(int actorNumber)
		{
			return _disconnectedActors.Contains(actorNumber);
		}

		public static bool IsPlayerDisconnected(Player unityPlayer)
		{
			if ((Object)(object)unityPlayer == (Object)null)
			{
				return false;
			}
			try
			{
				CharacterData data = unityPlayer.data;
				object obj;
				if (data == null)
				{
					obj = null;
				}
				else
				{
					PhotonView view = data.view;
					obj = ((view != null) ? view.Owner : null);
				}
				Player val = (Player)obj;
				return val != null && IsActorDisconnected(val.ActorNumber);
			}
			catch (Exception ex)
			{
				Plugin.ModLogger.LogWarning((object)("[RoundsMidJoin] IsPlayerDisconnected check threw: " + ex.Message));
				return false;
			}
		}

		public static void ResetState()
		{
			_disconnectedActors.Clear();
			_pendingJoins.Clear();
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] State reset for new game.");
		}

		public static Player DequeuePendingJoin()
		{
			return _pendingJoins.Dequeue();
		}

		public static Player? FindUnityPlayer(Player photonPlayer)
		{
			if ((Object)(object)PlayerManager.instance == (Object)null)
			{
				return null;
			}
			foreach (Player player in PlayerManager.instance.players)
			{
				try
				{
					int? obj;
					if (player == null)
					{
						obj = null;
					}
					else
					{
						CharacterData data = player.data;
						if (data == null)
						{
							obj = null;
						}
						else
						{
							PhotonView view = data.view;
							if (view == null)
							{
								obj = null;
							}
							else
							{
								Player owner = view.Owner;
								obj = ((owner != null) ? new int?(owner.ActorNumber) : null);
							}
						}
					}
					if (obj == photonPlayer.ActorNumber)
					{
						return player;
					}
				}
				catch
				{
				}
			}
			return null;
		}

		public static int GetAverageCardCount(Player? excluding = null)
		{
			if ((Object)(object)PlayerManager.instance == (Object)null)
			{
				return 0;
			}
			int num = 0;
			int num2 = 0;
			foreach (Player player in PlayerManager.instance.players)
			{
				if (!((Object)(object)player == (Object)null) && !((Object)(object)player == (Object)(object)excluding))
				{
					num += GetCardCount(player);
					num2++;
				}
			}
			if (num2 != 0)
			{
				return num / num2;
			}
			return 0;
		}

		public static int GetCardCount(Player player)
		{
			if ((Object)(object)player?.data == (Object)null)
			{
				return 0;
			}
			try
			{
				return player.data.currentCards?.Count ?? 0;
			}
			catch (Exception ex)
			{
				Plugin.ModLogger.LogDebug((object)("[RoundsMidJoin] GetCardCount: " + ex.Message));
				return 0;
			}
		}
	}
	[BepInPlugin("com.dandol328.rounds.midjoin", "RoundsMidJoin", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModId = "com.dandol328.rounds.midjoin";

		public const string ModName = "RoundsMidJoin";

		public const string ModVersion = "0.1.0";

		internal static ManualLogSource ModLogger;

		internal static Plugin Instance;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Instance = this;
			ModLogger = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("com.dandol328.rounds.midjoin");
			_harmony.PatchAll();
			ModLogger.LogInfo((object)"[RoundsMidJoin] v0.1.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchAll("com.dandol328.rounds.midjoin");
			}
		}
	}
}
namespace RoundsMidJoin.Patches
{
	[HarmonyPatch(typeof(CardChoice))]
	internal static class CardChoicePatches
	{
		private static readonly FieldInfo SpawnedCardsField = AccessTools.Field(typeof(CardChoice), "spawnedCards");

		private static volatile Player? _currentPickingPlayer;

		internal static Player? CurrentPickingPlayer => _currentPickingPlayer;

		[HarmonyPrefix]
		[HarmonyPatch("StartPicking")]
		private static bool StartPicking_Prefix(Player player, int picksLeft)
		{
			if ((Object)(object)player == (Object)null)
			{
				return true;
			}
			if (!MidJoinManager.IsPlayerDisconnected(player))
			{
				_currentPickingPlayer = player;
				return true;
			}
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] Disconnected player's card-pick turn detected — auto-resolving.");
			_currentPickingPlayer = null;
			return PhotonNetwork.IsMasterClient;
		}

		[HarmonyPostfix]
		[HarmonyPatch("StartPicking")]
		private static void StartPicking_Postfix(Player player)
		{
			if (!((Object)(object)player == (Object)null) && MidJoinManager.IsPlayerDisconnected(player) && PhotonNetwork.IsMasterClient)
			{
				DoAutoPick();
			}
		}

		internal static void HandleDisconnect(Player photonPlayer)
		{
			if ((Object)(object)CurrentPickingPlayer == (Object)null || (Object)(object)CardChoice.instance == (Object)null)
			{
				return;
			}
			try
			{
				CharacterData data = CurrentPickingPlayer.data;
				object obj;
				if (data == null)
				{
					obj = null;
				}
				else
				{
					PhotonView view = data.view;
					obj = ((view != null) ? view.Owner : null);
				}
				if (((obj != null) ? new int?(((Player)obj).ActorNumber) : null) != photonPlayer.ActorNumber)
				{
					return;
				}
			}
			catch (Exception arg)
			{
				Plugin.ModLogger.LogWarning((object)$"[RoundsMidJoin] HandleDisconnect owner-check threw: {arg}");
				return;
			}
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] Player disconnected during their card-pick turn — auto-resolving.");
			_currentPickingPlayer = null;
			DoAutoPick();
		}

		private static void DoAutoPick()
		{
			if (!PhotonNetwork.IsMasterClient)
			{
				return;
			}
			CardChoice instance = CardChoice.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				List<GameObject> list = (List<GameObject>)SpawnedCardsField.GetValue(instance);
				if (list != null && list.Count > 0)
				{
					instance.Pick(list[0], true);
				}
			}
		}
	}
	[HarmonyPatch(typeof(GM_ArmsRace))]
	internal static class GM_ArmsRace_Patches
	{
		private static readonly List<Player> _pendingRemoval = new List<Player>();

		[HarmonyPrefix]
		[HarmonyPatch("PlayerDied")]
		private static bool PlayerDied_Prefix(Player player, int __)
		{
			if (!MidJoinManager.IsPlayerDisconnected(player))
			{
				return true;
			}
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] Disconnected player's death event intercepted — scheduling removal instead of triggering win condition.");
			if (!_pendingRemoval.Contains(player))
			{
				_pendingRemoval.Add(player);
			}
			return false;
		}

		[HarmonyPostfix]
		[HarmonyPatch("PlayerDied")]
		private static void PlayerDied_Postfix()
		{
			if (_pendingRemoval.Count == 0)
			{
				return;
			}
			foreach (Player item in _pendingRemoval)
			{
				if ((Object)(object)PlayerManager.instance != (Object)null && PlayerManager.instance.players.Contains(item))
				{
					PlayerManager.instance.players.Remove(item);
					Plugin.ModLogger.LogInfo((object)("[RoundsMidJoin] Removed disconnected player from PlayerManager. " + $"Remaining: {PlayerManager.instance.players.Count}"));
				}
			}
			_pendingRemoval.Clear();
		}
	}
	[HarmonyPatch("UnboundLib.GameModes.GameModeHandler")]
	internal static class GameModeHandler_Patches
	{
		private static readonly FieldInfo SpawnedCardsField = AccessTools.Field(typeof(CardChoice), "spawnedCards");

		[HarmonyPostfix]
		[HarmonyPatch("StartGame")]
		private static void StartGame_Postfix()
		{
			MidJoinManager.ResetState();
		}

		[HarmonyPostfix]
		[HarmonyPatch("StartRound")]
		private static void StartRound_Postfix()
		{
			if (!MidJoinManager.HasPendingJoins)
			{
				return;
			}
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] Round started — processing pending mid-game joins.");
			List<(Player, int)> list = new List<(Player, int)>();
			while (MidJoinManager.HasPendingJoins)
			{
				var (val, num) = HandleMidGameJoin(MidJoinManager.DequeuePendingJoin());
				if ((Object)(object)val != (Object)null && num > 0)
				{
					list.Add((val, num));
				}
			}
			if (list.Count > 0)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(RunAllCatchUpPicking(list));
			}
		}

		private static (Player? unityPlayer, int cardsNeeded) HandleMidGameJoin(Player photonPlayer)
		{
			Plugin.ModLogger.LogInfo((object)("[RoundsMidJoin] Handling mid-game join for '" + photonPlayer.NickName + "'."));
			Player val = MidJoinManager.FindUnityPlayer(photonPlayer);
			if ((Object)(object)val != (Object)null)
			{
				((Component)val).gameObject.SetActive(true);
				int averageCardCount = MidJoinManager.GetAverageCardCount(val);
				int cardCount = MidJoinManager.GetCardCount(val);
				int num = Math.Max(0, averageCardCount - cardCount);
				Plugin.ModLogger.LogInfo((object)("[RoundsMidJoin] Reactivated existing Unity Player. " + $"Catch-up: avg={averageCardCount}, current={cardCount}, picks={num}."));
				return (val, num);
			}
			Plugin.ModLogger.LogInfo((object)("[RoundsMidJoin] No existing Unity Player found for '" + photonPlayer.NickName + "' — full mid-game spawn not yet implemented (see roadmap)."));
			return (null, 0);
		}

		private static IEnumerator RunAllCatchUpPicking(List<(Player unityPlayer, int cardsNeeded)> sessions)
		{
			foreach (var (player, cardsNeeded) in sessions)
			{
				yield return ((MonoBehaviour)Plugin.Instance).StartCoroutine(DoCatchUpCardPickingCoroutine(player, cardsNeeded));
			}
		}

		private static IEnumerator DoCatchUpCardPickingCoroutine(Player player, int cardsNeeded)
		{
			ManualLogSource modLogger = Plugin.ModLogger;
			object arg = cardsNeeded;
			Player owner = player.data.view.Owner;
			modLogger.LogInfo((object)("[RoundsMidJoin] Catch-up picking started — " + $"{arg} card(s) for '{((owner != null) ? owner.NickName : null)}'."));
			for (int i = 0; i < cardsNeeded; i++)
			{
				if ((Object)(object)CardChoice.instance == (Object)null)
				{
					break;
				}
				Plugin.ModLogger.LogInfo((object)$"[RoundsMidJoin] Auto-picking catch-up card {i + 1}/{cardsNeeded}.");
				if (PhotonNetwork.IsMasterClient)
				{
					CardChoice instance = CardChoice.instance;
					if ((Object)(object)instance != (Object)null)
					{
						List<GameObject> list = (List<GameObject>)SpawnedCardsField.GetValue(instance);
						if (list != null && list.Count > 0)
						{
							instance.Pick(list[0], true);
						}
					}
				}
				yield return (object)new WaitForSeconds(1f);
			}
			Plugin.ModLogger.LogInfo((object)"[RoundsMidJoin] Catch-up card picking complete.");
		}
	}
	[HarmonyPatch(typeof(NetworkConnectionHandler))]
	internal static class NetworkHandlerPatches
	{
		[HarmonyPrefix]
		[HarmonyPatch("OnPlayerLeftRoom")]
		private static void OnPlayerLeftRoom_Prefix(Player other)
		{
			MidJoinManager.HandlePlayerLeft(other);
			CardChoicePatches.HandleDisconnect(other);
		}

		[HarmonyPostfix]
		[HarmonyPatch("OnPlayerEnteredRoom")]
		private static void OnPlayerEnteredRoom_Postfix(Player newPlayer)
		{
			MidJoinManager.HandlePlayerJoined(newPlayer);
		}

		[HarmonyPostfix]
		[HarmonyPatch("OnMasterClientSwitched")]
		private static void OnMasterClientSwitched_Postfix(Player newMasterClient)
		{
			Plugin.ModLogger.LogInfo((object)("[RoundsMidJoin] Master client switched to '" + newMasterClient.NickName + "' " + $"(actor #{newMasterClient.ActorNumber})."));
		}
	}
}