Decompiled source of RoundsTracker v1.0.1

rounds-tracker.dll

Decompiled 18 hours 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.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModdingUtils.Utils;
using Photon.Pun;
using UnboundLib;
using UnboundLib.GameModes;
using UnboundLib.Networking;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("rounds-tracker")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+fe6c0319ebd8c995e3254bdcfa0cd8c295a86a7f")]
[assembly: AssemblyProduct("rounds-tracker")]
[assembly: AssemblyTitle("rounds-tracker")]
[assembly: AssemblyVersion("1.0.0.0")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RoundsTracker
{
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("com.rounds.tracker", "Rounds Tracker", "1.0.1")]
	[BepInProcess("Rounds.exe")]
	public class RT : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <OnGameStart>d__27 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public IGameModeHandler gm;

			public RT <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <OnGameStart>d__27(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				if (<>1__state != 0)
				{
					return false;
				}
				<>1__state = -1;
				ResetSession();
				ReplaceCardTracker.ClearAll();
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <OnPointEnd>d__28 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public IGameModeHandler gm;

			public RT <>4__this;

			private Player <winner>5__1;

			private RoundResultData <roundData>5__2;

			private Exception <ex>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <OnPointEnd>d__28(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<winner>5__1 = null;
				<roundData>5__2 = null;
				<ex>5__3 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				if (<>1__state != 0)
				{
					return false;
				}
				<>1__state = -1;
				if (!EnableTracking.Value || !TrackRoundResults.Value)
				{
					return false;
				}
				if (PhotonNetwork.OfflineMode)
				{
					return false;
				}
				try
				{
					_roundNumber++;
					<winner>5__1 = PlayerManager.instance.GetLastPlayerAlive();
					if ((Object)(object)<winner>5__1 == (Object)null)
					{
						LogDebug("OnPointEnd: No winner found");
						return false;
					}
					if (!<winner>5__1.data.view.IsMine)
					{
						LogDebug("OnPointEnd: Local client is not winner, skipping send");
						return false;
					}
					<roundData>5__2 = new RoundResultData
					{
						session_id = GetSessionId(),
						round_number = _roundNumber,
						winner_steam_id = GetSteamIdForPlayer(<winner>5__1),
						ended_at = DateTime.UtcNow.ToString("o")
					};
					ApiClient.SendRoundResult(<roundData>5__2);
					Log($"Round {_roundNumber} ended: Winner {<roundData>5__2.winner_steam_id}");
					<winner>5__1 = null;
					<roundData>5__2 = null;
				}
				catch (Exception ex)
				{
					<ex>5__3 = ex;
					LogError("OnPointEnd error: " + <ex>5__3.Message);
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string ModId = "com.rounds.tracker";

		public const string ModName = "Rounds Tracker";

		public const string Version = "1.0.1";

		public static ConfigEntry<bool> EnableTracking;

		public static ConfigEntry<string> ApiUrl;

		public static ConfigEntry<bool> TrackPickedCards;

		public static ConfigEntry<bool> TrackAddedCards;

		public static ConfigEntry<bool> TrackRemovedCards;

		public static ConfigEntry<bool> TrackRoundResults;

		public static ConfigEntry<bool> DebugLogging;

		private static string _sessionId;

		private static int _pickNumber;

		private static int _roundNumber;

		private static string _steamId;

		private static string _steamNickname;

		private static bool _steamInfoTried = false;

		public static string ApiUrlValue = "http://77.246.107.74:8000";

		private static Harmony _harmonyInstance;

		public static RT Instance { get; private set; }

		private void Awake()
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			EnableTracking = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTracking", true, "Enable or disable card tracking");
			TrackPickedCards = ((BaseUnityPlugin)this).Config.Bind<bool>("Tracking", "TrackPickedCards", true, "Track cards picked from card choice");
			TrackAddedCards = ((BaseUnityPlugin)this).Config.Bind<bool>("Tracking", "TrackAddedCards", true, "Track cards added programmatically");
			TrackRemovedCards = ((BaseUnityPlugin)this).Config.Bind<bool>("Tracking", "TrackRemovedCards", true, "Track cards removed from players");
			TrackRoundResults = ((BaseUnityPlugin)this).Config.Bind<bool>("Tracking", "TrackRoundResults", true, "Track round results (who won each round)");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Enable detailed debug logging");
			new Harmony("com.rounds.tracker").PatchAll();
		}

		private void Start()
		{
			ResetSession();
			GameModeManager.AddHook("GameStart", (Func<IGameModeHandler, IEnumerator>)OnGameStart);
			GameModeManager.AddHook("PointEnd", (Func<IGameModeHandler, IEnumerator>)OnPointEnd);
			ApplyReplaceCardPatches();
			Log(string.Format("{0} v{1} initialized. Tracking: {2}, API: {3}", "Rounds Tracker", "1.0.1", EnableTracking.Value, ApiUrlValue));
		}

		private void ApplyReplaceCardPatches()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			try
			{
				_harmonyInstance = new Harmony("com.rounds.tracker.cardremovalpatches");
				Type typeFromHandle = typeof(Cards);
				MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "RemoveAllCardsFromPlayer", new Type[2]
				{
					typeof(Player),
					typeof(bool)
				}, (Type[])null);
				if (methodInfo != null)
				{
					HarmonyMethod val = new HarmonyMethod(typeof(ReplaceCardTracker), "RemoveAllCardsPrefix", (Type[])null);
					_harmonyInstance.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log("Patched RemoveAllCardsFromPlayer");
				}
				else
				{
					LogError("RemoveAllCardsFromPlayer method not found");
				}
				MethodInfo methodInfo2 = AccessTools.Method(typeFromHandle, "AddCardsToPlayer", new Type[7]
				{
					typeof(Player),
					typeof(CardInfo[]),
					typeof(bool[]),
					typeof(string[]),
					typeof(float[]),
					typeof(float[]),
					typeof(bool)
				}, (Type[])null);
				if (methodInfo2 != null)
				{
					HarmonyMethod val2 = new HarmonyMethod(typeof(ReplaceCardTracker), "AddCardsToPlayerPostfix", (Type[])null);
					_harmonyInstance.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log("Patched AddCardsToPlayer");
				}
				else
				{
					LogError("AddCardsToPlayer method not found");
				}
			}
			catch (Exception ex)
			{
				LogError("Failed to apply card removal patches: " + ex.Message);
			}
		}

		[UnboundRPC]
		public static void RPCA_CardRemoved(int playerID, string cardName, string cardObjectName)
		{
			if (!EnableTracking.Value || !TrackRemovedCards.Value)
			{
				return;
			}
			try
			{
				Player val = PlayerManager.instance.players.Find((Player p) => p.playerID == playerID);
				if ((Object)(object)val == (Object)null)
				{
					LogDebug($"RPCA_CardRemoved: Player {playerID} not found");
					return;
				}
				if (!val.data.view.IsMine)
				{
					LogDebug($"RPCA_CardRemoved: Player {playerID} is not mine, skipping");
					return;
				}
				CardData cardDataFromNames = DataCollector.GetCardDataFromNames(cardName, cardObjectName);
				if (cardDataFromNames == null || string.IsNullOrWhiteSpace(cardDataFromNames.card_name))
				{
					LogDebug("RPCA_CardRemoved: Could not get card data for " + cardName);
					return;
				}
				PickData data = new PickData
				{
					pick_id = Guid.NewGuid().ToString(),
					source_type = "removed",
					tracker_version = "1.0.1",
					player = DataCollector.GetPlayer(val),
					session = DataCollector.GetSession(),
					game_state = DataCollector.GetGameState(),
					picked_card = cardDataFromNames,
					picked_at = DateTime.UtcNow.ToString("o")
				};
				ApiClient.Send(data);
				Log($"Removed (synced): {cardDataFromNames.card_name} from player {playerID}");
			}
			catch (Exception ex)
			{
				LogError("RPCA_CardRemoved error: " + ex.Message);
			}
		}

		private static void EnsureSteamInfo()
		{
			if (_steamInfoTried)
			{
				return;
			}
			_steamInfoTried = true;
			try
			{
				Type type = Type.GetType("SteamManager, Assembly-CSharp");
				if (type == null)
				{
					return;
				}
				PropertyInfo property = type.GetProperty("Initialized", BindingFlags.Static | BindingFlags.Public);
				if (property == null || !(bool)property.GetValue(null))
				{
					return;
				}
				Type type2 = Type.GetType("Steamworks.SteamUser, Assembly-CSharp-firstpass") ?? Type.GetType("Steamworks.SteamUser, com.rlabrecque.steamworks.net");
				if (type2 != null)
				{
					MethodInfo method = type2.GetMethod("GetSteamID", BindingFlags.Static | BindingFlags.Public);
					if (method != null)
					{
						object obj = method.Invoke(null, null);
						if (obj != null)
						{
							_steamId = obj.ToString();
						}
					}
				}
				Type type3 = Type.GetType("Steamworks.SteamFriends, Assembly-CSharp-firstpass") ?? Type.GetType("Steamworks.SteamFriends, com.rlabrecque.steamworks.net");
				if (type3 != null)
				{
					MethodInfo method2 = type3.GetMethod("GetPersonaName", BindingFlags.Static | BindingFlags.Public);
					if (method2 != null)
					{
						object obj2 = method2.Invoke(null, null);
						if (obj2 != null)
						{
							_steamNickname = obj2.ToString();
						}
					}
				}
				if (!string.IsNullOrEmpty(_steamId))
				{
					Log("Steam ID: " + _steamId + ", Name: " + _steamNickname);
				}
			}
			catch (Exception ex)
			{
				LogError("Failed to get Steam info: " + ex.Message);
			}
		}

		[IteratorStateMachine(typeof(<OnGameStart>d__27))]
		private IEnumerator OnGameStart(IGameModeHandler gm)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <OnGameStart>d__27(0)
			{
				<>4__this = this,
				gm = gm
			};
		}

		[IteratorStateMachine(typeof(<OnPointEnd>d__28))]
		private IEnumerator OnPointEnd(IGameModeHandler gm)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <OnPointEnd>d__28(0)
			{
				<>4__this = this,
				gm = gm
			};
		}

		private static string GetSteamIdForPlayer(Player player)
		{
			CharacterData data = player.data;
			if (data != null)
			{
				PhotonView view = data.view;
				if (((view != null) ? new bool?(view.IsMine) : null).GetValueOrDefault())
				{
					return GetSteamId();
				}
			}
			try
			{
				CharacterData data2 = player.data;
				int? obj;
				if (data2 == null)
				{
					obj = null;
				}
				else
				{
					PhotonView view2 = data2.view;
					obj = ((view2 != null) ? new int?(view2.OwnerActorNr) : null);
				}
				int? num = obj;
				int valueOrDefault = num.GetValueOrDefault(-1);
				if (valueOrDefault > 0)
				{
					return $"PHOTON_{valueOrDefault}";
				}
			}
			catch
			{
			}
			return "Unknown";
		}

		private static string GetCurrentGameMode()
		{
			try
			{
				if (GameModeManager.CurrentHandlerID != null)
				{
					return GameModeManager.CurrentHandlerID;
				}
			}
			catch
			{
			}
			return "Unknown";
		}

		public static void ResetSession()
		{
			_sessionId = Guid.NewGuid().ToString();
			_pickNumber = 0;
			_roundNumber = 0;
			LogDebug("Session reset");
		}

		public static string GetSessionId()
		{
			return (!string.IsNullOrEmpty(_sessionId)) ? _sessionId : (_sessionId = Guid.NewGuid().ToString());
		}

		public static int GetAndIncrementPickNumber()
		{
			return ++_pickNumber;
		}

		public static int GetRoundNumber()
		{
			return _roundNumber;
		}

		public static string GetSteamId()
		{
			EnsureSteamInfo();
			return _steamId;
		}

		public static string GetSteamNickname()
		{
			EnsureSteamInfo();
			return _steamNickname;
		}

		public static void Log(string msg)
		{
			RT instance = Instance;
			if (instance != null)
			{
				((BaseUnityPlugin)instance).Logger.LogInfo((object)msg);
			}
		}

		public static void LogError(string msg)
		{
			RT instance = Instance;
			if (instance != null)
			{
				((BaseUnityPlugin)instance).Logger.LogError((object)msg);
			}
		}

		public static void LogDebug(string msg)
		{
			ConfigEntry<bool> debugLogging = DebugLogging;
			if (debugLogging != null && debugLogging.Value)
			{
				RT instance = Instance;
				if (instance != null)
				{
					((BaseUnityPlugin)instance).Logger.LogInfo((object)("[DEBUG] " + msg));
				}
			}
		}
	}
	[HarmonyPatch(typeof(CardChoice), "Pick")]
	internal class CardChoicePatch
	{
		private static FieldInfo SpawnedCardsField = AccessTools.Field(typeof(CardChoice), "spawnedCards");

		private static FieldInfo PickerTypeField = AccessTools.Field(typeof(CardChoice), "pickerType");

		private static void Postfix(CardChoice __instance, GameObject pickedCard, int ___pickrID)
		{
			if (!RT.EnableTracking.Value || !RT.TrackPickedCards.Value)
			{
				return;
			}
			try
			{
				if ((Object)(object)pickedCard == (Object)null || PhotonNetwork.OfflineMode)
				{
					return;
				}
				List<GameObject> list = (List<GameObject>)(SpawnedCardsField?.GetValue(__instance));
				Player player = GetPlayer(__instance, ___pickrID);
				if ((Object)(object)player == (Object)null || !player.data.view.IsMine)
				{
					return;
				}
				CardData card = DataCollector.GetCard(pickedCard);
				if (card == null || string.IsNullOrWhiteSpace(card.card_name))
				{
					return;
				}
				List<CardData> list2 = new List<CardData>();
				int position = 0;
				for (int i = 0; i < list.Count; i++)
				{
					GameObject val = list[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					CardData card2 = DataCollector.GetCard(val);
					if (card2 != null && !string.IsNullOrWhiteSpace(card2.card_name))
					{
						card2.position = i;
						list2.Add(card2);
						if ((Object)(object)val == (Object)(object)pickedCard)
						{
							position = i;
						}
					}
				}
				card.position = position;
				PickData data = new PickData
				{
					pick_id = Guid.NewGuid().ToString(),
					source_type = "picked",
					tracker_version = "1.0.1",
					player = DataCollector.GetPlayer(player),
					session = DataCollector.GetSession(),
					game_state = DataCollector.GetGameState(),
					picked_card = card,
					offered_cards = list2,
					picked_at = DateTime.UtcNow.ToString("o")
				};
				ApiClient.Send(data);
				RT.Log("Picked: " + card.card_name);
			}
			catch (Exception ex)
			{
				RT.LogError("CardChoice.Pick patch error: " + ex.Message);
			}
		}

		private static Player GetPlayer(CardChoice instance, int pickerId)
		{
			//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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			try
			{
				PickerType val = (PickerType)(PickerTypeField?.GetValue(instance));
				if ((int)val == 0)
				{
					Player[] playersInTeam = PlayerManager.instance.GetPlayersInTeam(pickerId);
					return (playersInTeam != null && playersInTeam.Length != 0) ? playersInTeam[0] : null;
				}
			}
			catch
			{
			}
			if (pickerId < PlayerManager.instance.players.Count)
			{
				return PlayerManager.instance.players[pickerId];
			}
			return null;
		}
	}
	[HarmonyPatch]
	internal class AddCardPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = Type.GetType("ModdingUtils.Utils.Cards, ModdingUtils");
			if (type == null)
			{
				RT.LogDebug("AddCardPatch: ModdingUtils.Utils.Cards type not found");
				return null;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "RPCA_AssignCard", new Type[7]
			{
				typeof(string),
				typeof(int),
				typeof(bool),
				typeof(string),
				typeof(float),
				typeof(float),
				typeof(bool)
			}, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = AccessTools.Method(type, "RPCA_AssignCard", new Type[6]
				{
					typeof(string),
					typeof(int),
					typeof(bool),
					typeof(string),
					typeof(float),
					typeof(float)
				}, (Type[])null);
			}
			if (methodInfo != null)
			{
				RT.LogDebug($"AddCardPatch: Found RPCA_AssignCard with {methodInfo.GetParameters().Length} params");
			}
			else
			{
				RT.LogDebug("AddCardPatch: RPCA_AssignCard method not found");
			}
			return methodInfo;
		}

		private static void Postfix(string cardObjectName, int playerID, bool reassign)
		{
			if (!RT.EnableTracking.Value || !RT.TrackAddedCards.Value)
			{
				return;
			}
			try
			{
				if (reassign)
				{
					RT.LogDebug("AddCardPatch: Skipping reassign for " + cardObjectName);
				}
				else
				{
					if (PhotonNetwork.OfflineMode)
					{
						return;
					}
					Player val = PlayerManager.instance.players.Find((Player p) => p.playerID == playerID);
					if ((Object)(object)val == (Object)null)
					{
						RT.LogDebug($"AddCardPatch: Player {playerID} not found");
						return;
					}
					if (!val.data.view.IsMine)
					{
						RT.LogDebug($"AddCardPatch: Player {playerID} is not mine, skipping");
						return;
					}
					CardData cardFromObjectName = DataCollector.GetCardFromObjectName(cardObjectName);
					if (cardFromObjectName == null || string.IsNullOrWhiteSpace(cardFromObjectName.card_name))
					{
						RT.LogDebug("AddCardPatch: Could not get card data for " + cardObjectName);
						return;
					}
					PickData data = new PickData
					{
						pick_id = Guid.NewGuid().ToString(),
						source_type = "added",
						tracker_version = "1.0.1",
						player = DataCollector.GetPlayer(val),
						session = DataCollector.GetSession(),
						game_state = DataCollector.GetGameState(),
						picked_card = cardFromObjectName,
						picked_at = DateTime.UtcNow.ToString("o")
					};
					ApiClient.Send(data);
					RT.Log($"Added (synced): {cardFromObjectName.card_name} to player {playerID}");
				}
			}
			catch (Exception ex)
			{
				RT.LogError("RPCA_AssignCard patch error: " + ex.Message);
			}
		}
	}
	internal static class ReplaceCardTracker
	{
		private static Dictionary<int, Stack<List<CardInfo>>> _savedCards = new Dictionary<int, Stack<List<CardInfo>>>();

		public static void ClearAll()
		{
			_savedCards.Clear();
			RT.LogDebug("ReplaceCardTracker: Cleared all saved cards");
		}

		public static void RemoveAllCardsPrefix(Player player, bool clearBar)
		{
			if (!RT.EnableTracking.Value || !RT.TrackRemovedCards.Value || (Object)(object)player == (Object)null)
			{
				return;
			}
			try
			{
				List<CardInfo> list = new List<CardInfo>();
				if (player.data?.currentCards != null)
				{
					foreach (CardInfo currentCard in player.data.currentCards)
					{
						if ((Object)(object)currentCard != (Object)null)
						{
							list.Add(currentCard);
						}
					}
				}
				if (!_savedCards.ContainsKey(player.playerID))
				{
					_savedCards[player.playerID] = new Stack<List<CardInfo>>();
				}
				_savedCards[player.playerID].Push(list);
				RT.LogDebug($"ReplaceCardTracker: Saved {list.Count} cards for player {player.playerID}");
			}
			catch (Exception ex)
			{
				RT.LogError("ReplaceCardTracker.RemoveAllCardsPrefix error: " + ex.Message);
			}
		}

		public static void AddCardsToPlayerPostfix(Player player, CardInfo[] cards, bool[] reassigns, string[] twoLetterCodes, float[] forceDisplays, float[] forceDisplayDelays, bool addToCardBar)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			List<CardInfo> list = null;
			if (_savedCards.TryGetValue(player.playerID, out var value) && value.Count > 0)
			{
				list = value.Pop();
			}
			if (!RT.EnableTracking.Value || !RT.TrackRemovedCards.Value || cards == null || list == null || list.Count == 0 || PhotonNetwork.OfflineMode)
			{
				return;
			}
			try
			{
				if (!PhotonNetwork.IsMasterClient)
				{
					RT.LogDebug("ReplaceCardTracker: Not master client, skipping");
					return;
				}
				HashSet<string> hashSet = new HashSet<string>();
				for (int i = 0; i < cards.Length; i++)
				{
					if ((Object)(object)cards[i] != (Object)null && reassigns != null && i < reassigns.Length && reassigns[i])
					{
						hashSet.Add(((Object)cards[i]).name);
					}
				}
				foreach (CardInfo item in list)
				{
					if (!hashSet.Contains(((Object)item).name))
					{
						RT.LogDebug($"ReplaceCardTracker: Detected removed/replaced card {item.cardName} for player {player.playerID}");
						GameObject gameObject = ((Component)item).gameObject;
						string text = ((gameObject != null) ? ((Object)gameObject).name : null) ?? item.cardName;
						NetworkingManager.RPC(typeof(RT), "RPCA_CardRemoved", new object[3] { player.playerID, item.cardName, text });
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("ReplaceCardTracker.AddCardsToPlayerPostfix error: " + ex.Message);
			}
		}
	}
	internal static class DataCollector
	{
		private static Dictionary<string, string> _modVersionCache = new Dictionary<string, string>();

		public static PlayerData GetPlayer(Player player)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			string steam_id = "Unknown";
			string text = "Unknown";
			if (!string.IsNullOrEmpty(RT.GetSteamId()))
			{
				steam_id = RT.GetSteamId();
				text = RT.GetSteamNickname() ?? text;
			}
			PlayerSkin playerSkinColors = PlayerSkinBank.GetPlayerSkinColors(player.playerID);
			return new PlayerData
			{
				steam_id = steam_id,
				nickname = text,
				color = "#" + ColorUtility.ToHtmlStringRGB(playerSkinColors.color)
			};
		}

		public static SessionData GetSession()
		{
			string game_mode = "Unknown";
			try
			{
				if (GameModeManager.CurrentHandlerID != null)
				{
					game_mode = GameModeManager.CurrentHandlerID;
				}
			}
			catch
			{
			}
			return new SessionData
			{
				session_id = RT.GetSessionId(),
				game_mode = game_mode,
				player_count = PlayerManager.instance.players.Count
			};
		}

		public static GameStateData GetGameState()
		{
			GameStateData gameStateData = new GameStateData
			{
				pick_number = RT.GetAndIncrementPickNumber()
			};
			try
			{
				IGameModeHandler currentHandler = GameModeManager.CurrentHandler;
				if (((currentHandler != null) ? currentHandler.Settings : null) != null)
				{
					object value = default(object);
					if (currentHandler.Settings.TryGetValue("pointsToWinRound", ref value))
					{
						gameStateData.points_to_win_round = Convert.ToInt32(value);
					}
					object value2 = default(object);
					if (currentHandler.Settings.TryGetValue("roundsToWinGame", ref value2))
					{
						gameStateData.rounds_to_win_game = Convert.ToInt32(value2);
					}
				}
			}
			catch
			{
			}
			return gameStateData;
		}

		public static CardData GetCard(GameObject cardObject)
		{
			if ((Object)(object)cardObject == (Object)null)
			{
				return null;
			}
			CardInfo component = cardObject.GetComponent<CardInfo>();
			return ((Object)(object)component != (Object)null) ? GetCardFromInfo(component, ((Object)cardObject).name) : null;
		}

		public static CardData GetCardFromObjectName(string objectName)
		{
			if (string.IsNullOrEmpty(objectName))
			{
				return null;
			}
			try
			{
				Type type = Type.GetType("ModdingUtils.Utils.Cards, ModdingUtils");
				if (type != null)
				{
					object obj = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
					MethodInfo method = type.GetMethod("GetCardWithObjectName", BindingFlags.Instance | BindingFlags.Public);
					if (method != null && obj != null)
					{
						object? obj2 = method.Invoke(obj, new object[1] { objectName });
						CardInfo val = (CardInfo)((obj2 is CardInfo) ? obj2 : null);
						if ((Object)(object)val != (Object)null)
						{
							return GetCardFromInfo(val, objectName);
						}
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogDebug("GetCardFromObjectName error: " + ex.Message);
			}
			string cardName = ExtractCardNameFromObjectName(objectName);
			return GetCardDataFromNames(cardName, objectName);
		}

		private static string ExtractCardNameFromObjectName(string objectName)
		{
			if (string.IsNullOrEmpty(objectName))
			{
				return objectName;
			}
			if (objectName.StartsWith("__"))
			{
				string[] array = objectName.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 2)
				{
					return array[1];
				}
			}
			return objectName;
		}

		public static CardData GetCardDataFromNames(string cardName, string objectName)
		{
			string text = "Vanilla";
			string text2 = objectName ?? cardName ?? "";
			if (text2.StartsWith("__"))
			{
				string[] array = text2.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 1)
				{
					text = array[0];
				}
			}
			string text3 = cardName ?? "";
			if (text3.StartsWith("__"))
			{
				text3 = ExtractCardNameFromObjectName(text3);
			}
			return new CardData
			{
				card_name = text3,
				mod_name = text,
				mod_version = GetModVersion(text),
				description = "",
				rarity = "Common",
				color_theme = "TechWhite",
				categories = new List<string>(),
				stats = new List<StatData>()
			};
		}

		public static CardData GetCardFromInfo(CardInfo cardInfo, string objName = null)
		{
			if ((Object)(object)cardInfo == (Object)null)
			{
				return null;
			}
			string text = "Vanilla";
			string text2 = null;
			object obj = objName;
			if (obj == null)
			{
				GameObject gameObject = ((Component)cardInfo).gameObject;
				obj = ((gameObject != null) ? ((Object)gameObject).name : null) ?? "";
			}
			string text3 = (string)obj;
			if (text3.StartsWith("__"))
			{
				string[] array = text3.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 1)
				{
					text = array[0];
				}
			}
			text2 = GetModVersion(text);
			string rarity = "Common";
			try
			{
				rarity = ((object)(Rarity)(ref cardInfo.rarity)).ToString();
			}
			catch
			{
			}
			string color_theme = "TechWhite";
			try
			{
				color_theme = ((object)(CardThemeColorType)(ref cardInfo.colorTheme)).ToString();
			}
			catch
			{
			}
			List<StatData> list = new List<StatData>();
			try
			{
				if (cardInfo.cardStats != null)
				{
					CardInfoStat[] cardStats = cardInfo.cardStats;
					foreach (CardInfoStat val in cardStats)
					{
						list.Add(new StatData
						{
							stat = (val.stat ?? ""),
							amount = (val.amount ?? ""),
							positive = val.positive
						});
					}
				}
			}
			catch
			{
			}
			List<string> list2 = new List<string>();
			if (cardInfo.categories != null)
			{
				CardCategory[] categories = cardInfo.categories;
				foreach (CardCategory val2 in categories)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						list2.Add(((Object)val2).name);
					}
				}
			}
			return new CardData
			{
				card_name = (cardInfo.cardName ?? ""),
				mod_name = text,
				mod_version = GetModVersion(text),
				description = (cardInfo.cardDestription ?? ""),
				rarity = rarity,
				color_theme = color_theme,
				categories = list2,
				stats = list
			};
		}

		private static string GetModVersion(string modName)
		{
			if (string.IsNullOrEmpty(modName) || modName == "Vanilla")
			{
				return null;
			}
			if (_modVersionCache.TryGetValue(modName, out var value))
			{
				return value;
			}
			try
			{
				foreach (PluginInfo value2 in Chainloader.PluginInfos.Values)
				{
					BepInPlugin metadata = value2.Metadata;
					if (metadata.GUID.ToUpper().Contains(modName) || metadata.Name.ToUpper().Contains(modName) || metadata.Name.ToUpper().Replace(" ", "").Contains(modName))
					{
						_modVersionCache[modName] = metadata.Version.ToString();
						return metadata.Version.ToString();
					}
				}
			}
			catch
			{
			}
			_modVersionCache[modName] = null;
			return null;
		}
	}
	internal static class ApiClient
	{
		[CompilerGenerated]
		private sealed class <SendPickCoroutine>d__3 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PickData data;

			public int retryCount;

			private string <json>5__1;

			private string <url>5__2;

			private UnityWebRequest <req>5__3;

			private byte[] <body>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SendPickCoroutine>d__3(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || (uint)(num - 1) <= 1u)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<json>5__1 = null;
				<url>5__2 = null;
				<req>5__3 = null;
				<body>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cc: Expected O, but got Unknown
				//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
				//IL_0101: Expected O, but got Unknown
				//IL_0108: Unknown result type (might be due to invalid IL or missing references)
				//IL_0112: Expected O, but got Unknown
				//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d1: Expected O, but got Unknown
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<json>5__1 = JsonSerializer.Serialize(data);
						<url>5__2 = RT.ApiUrlValue + "/api/picks";
						RT.LogDebug("Sending to " + <url>5__2 + ": " + data.source_type + " " + data.picked_card?.card_name);
						<req>5__3 = new UnityWebRequest(<url>5__2, "POST");
						<>1__state = -3;
						<body>5__4 = Encoding.UTF8.GetBytes(<json>5__1);
						<req>5__3.uploadHandler = (UploadHandler)new UploadHandlerRaw(<body>5__4);
						<req>5__3.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
						<req>5__3.SetRequestHeader("Content-Type", "application/json");
						<req>5__3.timeout = 10;
						<>2__current = <req>5__3.SendWebRequest();
						<>1__state = 1;
						return true;
					case 1:
						<>1__state = -3;
						if (<req>5__3.isNetworkError || <req>5__3.isHttpError)
						{
							RT.LogError($"Send failed: {<req>5__3.error} (attempt {retryCount + 1})");
							if (retryCount < 3)
							{
								<>2__current = (object)new WaitForSeconds(2f * (float)(retryCount + 1));
								<>1__state = 2;
								return true;
							}
						}
						else
						{
							RT.LogDebug("Send success: " + <req>5__3.downloadHandler.text);
						}
						break;
					case 2:
						<>1__state = -3;
						((MonoBehaviour)RT.Instance).StartCoroutine(SendPickCoroutine(data, retryCount + 1));
						break;
					}
					<body>5__4 = null;
					<>m__Finally1();
					<req>5__3 = null;
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<req>5__3 != null)
				{
					((IDisposable)<req>5__3).Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <SendRoundCoroutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public RoundResultData data;

			public int retryCount;

			private string <json>5__1;

			private string <url>5__2;

			private UnityWebRequest <req>5__3;

			private byte[] <body>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SendRoundCoroutine>d__4(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || (uint)(num - 1) <= 1u)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<json>5__1 = null;
				<url>5__2 = null;
				<req>5__3 = null;
				<body>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_008e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0098: Expected O, but got Unknown
				//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cd: Expected O, but got Unknown
				//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00de: Expected O, but got Unknown
				//IL_0193: Unknown result type (might be due to invalid IL or missing references)
				//IL_019d: Expected O, but got Unknown
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<json>5__1 = JsonSerializer.SerializeRound(data);
						<url>5__2 = RT.ApiUrlValue + "/api/rounds";
						RT.LogDebug($"Sending round result to {<url>5__2}: Round {data.round_number}");
						<req>5__3 = new UnityWebRequest(<url>5__2, "POST");
						<>1__state = -3;
						<body>5__4 = Encoding.UTF8.GetBytes(<json>5__1);
						<req>5__3.uploadHandler = (UploadHandler)new UploadHandlerRaw(<body>5__4);
						<req>5__3.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
						<req>5__3.SetRequestHeader("Content-Type", "application/json");
						<req>5__3.timeout = 10;
						<>2__current = <req>5__3.SendWebRequest();
						<>1__state = 1;
						return true;
					case 1:
						<>1__state = -3;
						if (<req>5__3.isNetworkError || <req>5__3.isHttpError)
						{
							RT.LogError($"Send round failed: {<req>5__3.error} (attempt {retryCount + 1})");
							if (retryCount < 3)
							{
								<>2__current = (object)new WaitForSeconds(2f * (float)(retryCount + 1));
								<>1__state = 2;
								return true;
							}
						}
						else
						{
							RT.LogDebug("Round send success: " + <req>5__3.downloadHandler.text);
						}
						break;
					case 2:
						<>1__state = -3;
						((MonoBehaviour)RT.Instance).StartCoroutine(SendRoundCoroutine(data, retryCount + 1));
						break;
					}
					<body>5__4 = null;
					<>m__Finally1();
					<req>5__3 = null;
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<req>5__3 != null)
				{
					((IDisposable)<req>5__3).Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const int MaxRetries = 3;

		public static void Send(PickData data)
		{
			if (RT.EnableTracking.Value)
			{
				((MonoBehaviour)RT.Instance).StartCoroutine(SendPickCoroutine(data, 0));
			}
		}

		public static void SendRoundResult(RoundResultData data)
		{
			if (RT.EnableTracking.Value)
			{
				((MonoBehaviour)RT.Instance).StartCoroutine(SendRoundCoroutine(data, 0));
			}
		}

		[IteratorStateMachine(typeof(<SendPickCoroutine>d__3))]
		private static IEnumerator SendPickCoroutine(PickData data, int retryCount)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendPickCoroutine>d__3(0)
			{
				data = data,
				retryCount = retryCount
			};
		}

		[IteratorStateMachine(typeof(<SendRoundCoroutine>d__4))]
		private static IEnumerator SendRoundCoroutine(RoundResultData data, int retryCount)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendRoundCoroutine>d__4(0)
			{
				data = data,
				retryCount = retryCount
			};
		}
	}
	internal static class JsonSerializer
	{
		public static string Serialize(PickData d)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{");
			stringBuilder.Append("\"pick_id\":\"" + Esc(d.pick_id) + "\",");
			stringBuilder.Append("\"source_type\":\"" + Esc(d.source_type) + "\",");
			stringBuilder.Append("\"tracker_version\":\"" + Esc(d.tracker_version) + "\",");
			stringBuilder.Append("\"picked_at\":\"" + Esc(d.picked_at) + "\",");
			stringBuilder.Append("\"player\":");
			SerializePlayer(stringBuilder, d.player);
			stringBuilder.Append(",");
			stringBuilder.Append("\"session\":");
			SerializeSession(stringBuilder, d.session);
			stringBuilder.Append(",");
			stringBuilder.Append("\"game_state\":");
			SerializeGameState(stringBuilder, d.game_state);
			stringBuilder.Append(",");
			stringBuilder.Append("\"picked_card\":");
			SerializeCard(stringBuilder, d.picked_card);
			if (d.offered_cards != null && d.offered_cards.Count > 0)
			{
				stringBuilder.Append(",\"offered_cards\":[");
				for (int i = 0; i < d.offered_cards.Count; i++)
				{
					if (i > 0)
					{
						stringBuilder.Append(",");
					}
					SerializeCard(stringBuilder, d.offered_cards[i]);
				}
				stringBuilder.Append("]");
			}
			stringBuilder.Append("}");
			return stringBuilder.ToString();
		}

		private static void SerializePlayer(StringBuilder sb, PlayerData p)
		{
			sb.Append("{");
			sb.Append("\"steam_id\":\"" + Esc(p.steam_id) + "\",");
			sb.Append("\"nickname\":\"" + Esc(p.nickname) + "\",");
			sb.Append("\"color\":\"" + Esc(p.color) + "\"");
			sb.Append("}");
		}

		private static void SerializeSession(StringBuilder sb, SessionData s)
		{
			sb.Append("{");
			sb.Append("\"session_id\":\"" + Esc(s.session_id) + "\",");
			sb.Append("\"game_mode\":\"" + Esc(s.game_mode) + "\",");
			sb.Append($"\"player_count\":{s.player_count}");
			sb.Append("}");
		}

		private static void SerializeGameState(StringBuilder sb, GameStateData g)
		{
			sb.Append("{");
			sb.Append($"\"pick_number\":{g.pick_number},");
			sb.Append("\"points_to_win_round\":" + Null(g.points_to_win_round) + ",");
			sb.Append("\"rounds_to_win_game\":" + Null(g.rounds_to_win_game));
			sb.Append("}");
		}

		private static void SerializeCard(StringBuilder sb, CardData c)
		{
			sb.Append("{");
			sb.Append("\"card_name\":\"" + Esc(c.card_name) + "\",");
			sb.Append("\"mod_name\":\"" + Esc(c.mod_name) + "\",");
			sb.Append("\"mod_version\":" + NullStr(c.mod_version) + ",");
			sb.Append("\"description\":\"" + Esc(c.description) + "\",");
			sb.Append("\"rarity\":\"" + Esc(c.rarity) + "\",");
			sb.Append("\"color_theme\":\"" + Esc(c.color_theme) + "\",");
			sb.Append($"\"position\":{c.position},");
			sb.Append("\"categories\":[");
			for (int i = 0; i < c.categories.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(",");
				}
				sb.Append("\"" + Esc(c.categories[i]) + "\"");
			}
			sb.Append("],\"stats\":[");
			for (int j = 0; j < c.stats.Count; j++)
			{
				if (j > 0)
				{
					sb.Append(",");
				}
				StatData statData = c.stats[j];
				sb.Append("{\"stat\":\"" + Esc(statData.stat) + "\",\"amount\":\"" + Esc(statData.amount) + "\",\"positive\":" + Bool(statData.positive) + "}");
			}
			sb.Append("]}");
		}

		private static string Null(int? v)
		{
			return v.HasValue ? v.Value.ToString() : "null";
		}

		private static string NullStr(string v)
		{
			return string.IsNullOrEmpty(v) ? "null" : ("\"" + Esc(v) + "\"");
		}

		private static string Bool(bool v)
		{
			return v ? "true" : "false";
		}

		private static string Esc(string s)
		{
			return string.IsNullOrEmpty(s) ? "" : s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n")
				.Replace("\r", "\\r")
				.Replace("\t", "\\t");
		}

		public static string SerializeRound(RoundResultData r)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{");
			stringBuilder.Append("\"session_id\":\"" + Esc(r.session_id) + "\",");
			stringBuilder.Append($"\"round_number\":{r.round_number},");
			stringBuilder.Append("\"winner_steam_id\":" + NullStr(r.winner_steam_id) + ",");
			stringBuilder.Append("\"ended_at\":\"" + Esc(r.ended_at) + "\"");
			stringBuilder.Append("}");
			return stringBuilder.ToString();
		}
	}
	internal class PickData
	{
		public string pick_id;

		public string source_type;

		public string tracker_version;

		public PlayerData player;

		public SessionData session;

		public GameStateData game_state;

		public CardData picked_card;

		public List<CardData> offered_cards;

		public string picked_at;
	}
	internal class PlayerData
	{
		public string steam_id;

		public string nickname;

		public string color;
	}
	internal class SessionData
	{
		public string session_id;

		public string game_mode;

		public int player_count;
	}
	internal class GameStateData
	{
		public int pick_number;

		public int? points_to_win_round;

		public int? rounds_to_win_game;
	}
	internal class CardData
	{
		public string card_name;

		public string mod_name;

		public string mod_version;

		public string description;

		public string rarity;

		public string color_theme;

		public int position;

		public List<string> categories = new List<string>();

		public List<StatData> stats = new List<StatData>();
	}
	internal class StatData
	{
		public string stat;

		public string amount;

		public bool positive;
	}
	internal class RoundResultData
	{
		public string session_id;

		public int round_number;

		public string winner_steam_id;

		public string ended_at;
	}
}