Decompiled source of RoundsTracker v1.0.22

rounds-tracker.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using ExitGames.Client.Photon;
using GameMessageLib;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModdingUtils.Utils;
using Photon.Pun;
using Photon.Realtime;
using RarityLib.Utils;
using TMPro;
using UnboundLib;
using UnboundLib.GameModes;
using UnboundLib.Networking;
using UnboundLib.Utils.UI;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;

[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+04232ad0fde4750954abaac9c7f0e1b0764b6682")]
[assembly: AssemblyProduct("rounds-tracker")]
[assembly: AssemblyTitle("rounds-tracker")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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
{
	internal static class Api
	{
		private const int MaxAttempts = 5;

		private const int AttemptTimeout = 2;

		private const float RetryBaseDelay = 1f;

		public static void Send(RoundReport report)
		{
			if (RT.EnableTracking.Value && !string.IsNullOrEmpty(report.steam_id))
			{
				((MonoBehaviour)RT.Instance).StartCoroutine(SendCoroutine(report, 0));
			}
		}

		private static IEnumerator SendCoroutine(RoundReport report, int attempt)
		{
			string json = ReportSerializer.ToJson(report);
			string url = RT.ApiUrlValue + "/api/rounds";
			RT.LogDebug($"Sending {json.Length} bytes to {url} (attempt {attempt + 1}/{5})");
			UnityWebRequest req = new UnityWebRequest(url, "POST");
			try
			{
				req.uploadHandler = (UploadHandler)new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
				req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				req.SetRequestHeader("Content-Type", "application/json");
				req.timeout = 2;
				yield return req.SendWebRequest();
				if (req.isNetworkError || req.isHttpError)
				{
					RT.LogError($"Send error: {req.error} (attempt {attempt + 1}/{5})");
					if (attempt + 1 < 5)
					{
						float delay = 1f * Mathf.Pow(2f, (float)attempt);
						RT.LogDebug($"Retry in {delay}s (attempt {attempt + 1}/{5})");
						yield return (object)new WaitForSeconds(delay);
						((MonoBehaviour)RT.Instance).StartCoroutine(SendCoroutine(report, attempt + 1));
					}
					else
					{
						RT.LogError($"Send failed after {5} attempts, data not sent");
						if (RT.ShowSendErrorNotifications.Value)
						{
							GameMessage.Error($"Rounds Tracker: failed to send data ({5} attempts)", 5f);
						}
					}
					yield break;
				}
				string responseText = req.downloadHandler.text;
				RT.Log("Send OK: " + responseText.Substring(0, Math.Min(responseText.Length, 500)));
				if (responseText?.Contains("\"elo\"") ?? false)
				{
					try
					{
						ParseEloResponse(responseText);
					}
					catch (Exception ex)
					{
						Exception ex2 = ex;
						RT.LogError("ELO parse error: " + ex2.Message);
					}
				}
				if (responseText?.Contains("\"xp_change\"") ?? false)
				{
					try
					{
						ParseXpResponse(responseText);
					}
					catch (Exception ex)
					{
						Exception ex3 = ex;
						RT.LogError("XP parse error: " + ex3.Message);
					}
				}
				if (responseText?.Contains("\"new_achievements\"") ?? false)
				{
					try
					{
						ParseAchievementsResponse(responseText, report.is_game_over);
					}
					catch (Exception ex)
					{
						Exception ex4 = ex;
						RT.LogError("ACH parse error: " + ex4.Message);
					}
				}
			}
			finally
			{
				((IDisposable)req)?.Dispose();
			}
		}

		private static void ParseEloResponse(string json)
		{
			float before = 0f;
			float after = 0f;
			float num = 0f;
			bool flag = false;
			List<EloPlayerData> list = new List<EloPlayerData>();
			MatchCollection matchCollection = Regex.Matches(json, "\\{[^{}]*\\}");
			foreach (Match item in matchCollection)
			{
				string value = item.Value;
				string text = ExtractStr(value, "steam_id");
				if (!string.IsNullOrEmpty(text))
				{
					list.Add(new EloPlayerData
					{
						steam_id = text,
						before = ExtractFloat(value, "before", 0f),
						after = ExtractFloat(value, "after", 0f),
						change = ExtractFloat(value, "change", 0f)
					});
				}
				else if (value.Contains("\"before\"") && value.Contains("\"change\""))
				{
					before = ExtractFloat(value, "before", 0f);
					after = ExtractFloat(value, "after", 0f);
					num = ExtractFloat(value, "change", 0f);
					flag = true;
				}
			}
			if (flag && num != 0f && RT.ShowEloNotifications.Value && !RT._eloShownThisGame)
			{
				RT._eloShownThisGame = true;
				RT.ShowEloChangeNotification(before, after, num, "server");
			}
			if (list.Count > 0)
			{
				Networking.BroadcastEloResults(list);
			}
		}

		public static IEnumerator PollEloFromHttp(string sessionId, string steamId, int roundNumber)
		{
			if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(steamId) || roundNumber <= 0)
			{
				yield break;
			}
			yield return (object)new WaitForSeconds(2f);
			int i = 0;
			while (true)
			{
				if (i < 5)
				{
					if (RT._eloShownThisGame)
					{
						break;
					}
					string url = RT.ApiUrlValue + "/api/elo/check?session_id=" + Uri.EscapeDataString(sessionId) + "&steam_id=" + Uri.EscapeDataString(steamId) + "&round_number=" + roundNumber.ToString(CultureInfo.InvariantCulture);
					UnityWebRequest req = UnityWebRequest.Get(url);
					try
					{
						req.timeout = 5;
						yield return req.SendWebRequest();
						if (!req.isNetworkError && !req.isHttpError)
						{
							string text = req.downloadHandler.text;
							if (text != null && text.Contains("\"elo\"") && !RT._eloShownThisGame)
							{
								MatchCollection matches = Regex.Matches(text, "\\{[^{}]*\\}");
								foreach (Match m in matches)
								{
									string obj = m.Value;
									if (!obj.Contains("\"before\"") || !obj.Contains("\"change\"") || obj.Contains("\"steam_id\""))
									{
										continue;
									}
									float before = ExtractFloat(obj, "before", 0f);
									float after = ExtractFloat(obj, "after", 0f);
									float change = ExtractFloat(obj, "change", 0f);
									if (change == 0f)
									{
										break;
									}
									if (!RT._eloShownThisGame)
									{
										RT._eloShownThisGame = true;
										RT.ShowEloChangeNotification(before, after, change, "http-poll");
									}
									yield break;
								}
							}
						}
					}
					finally
					{
						((IDisposable)req)?.Dispose();
					}
					if (i < 4)
					{
						yield return (object)new WaitForSeconds(3f);
					}
					i++;
					continue;
				}
				RT.LogDebug("ELO http-poll: no result after all attempts");
				break;
			}
		}

		private static void ParseXpResponse(string json)
		{
			XpChangeData xpChangeData = null;
			List<XpChangeData> list = new List<XpChangeData>();
			MatchCollection matchCollection = Regex.Matches(json, "\\{[^{}]*\\}");
			foreach (Match item in matchCollection)
			{
				string value = item.Value;
				if (value.Contains("\"xp_gained\""))
				{
					string text = ExtractStr(value, "steam_id");
					XpChangeData xpChangeData2 = new XpChangeData
					{
						steam_id = text,
						xp_gained = (int)ExtractFloat(value, "xp_gained", 0f),
						xp_before = (int)ExtractFloat(value, "xp_before", 0f),
						xp_after = (int)ExtractFloat(value, "xp_after", 0f),
						level_before = (int)ExtractFloat(value, "level_before", 1f),
						level_after = (int)ExtractFloat(value, "level_after", 1f)
					};
					if (string.IsNullOrEmpty(text))
					{
						xpChangeData = xpChangeData2;
					}
					else
					{
						list.Add(xpChangeData2);
					}
				}
			}
			if (xpChangeData != null && xpChangeData.xp_gained > 0 && RT.ShowXpNotifications.Value && !RT._xpShownThisGame)
			{
				RT._xpShownThisGame = true;
				((MonoBehaviour)RT.Instance).StartCoroutine(RT.ShowXpNotification(xpChangeData, null));
				RT.Log($"XP change (server): +{xpChangeData.xp_gained} Lv.{xpChangeData.level_before}->{xpChangeData.level_after}");
			}
			if (list.Count > 0)
			{
				Networking.BroadcastXpResults(list);
			}
		}

		private static void ParseAchievementsResponse(string json, bool isGameOver)
		{
			if (!isGameOver || !RT.ShowXpNotifications.Value)
			{
				return;
			}
			List<AchievementNotif> list = new List<AchievementNotif>();
			MatchCollection matchCollection = Regex.Matches(json, "\\{[^{}]*\\}");
			foreach (Match item in matchCollection)
			{
				string value = item.Value;
				if (value.Contains("\"icon\""))
				{
					string text = ExtractStr(value, "icon");
					string text2 = ExtractStr(value, "name");
					string id = ExtractStr(value, "id");
					if (!string.IsNullOrEmpty(text2))
					{
						list.Add(new AchievementNotif
						{
							id = id,
							icon = (text ?? "\ud83c\udfc5"),
							name = text2,
							xp = (int)ExtractFloat(value, "xp", 0f),
							description = (ExtractStr(value, "description") ?? "")
						});
					}
				}
			}
			if (list.Count > 0)
			{
				RT.Log($"New achievements: {list.Count}");
				((MonoBehaviour)RT.Instance).StartCoroutine(RT.ShowAchievementNotifications(list));
			}
		}

		private static string ExtractStr(string obj, string field)
		{
			Match match = Regex.Match(obj, "\"" + field + "\":\"((?:[^\"\\\\]|\\\\.)*)\"");
			return match.Success ? match.Groups[1].Value : null;
		}

		private static float ExtractFloat(string obj, string field, float defaultVal)
		{
			Match match = Regex.Match(obj, "\"" + field + "\":(-?[0-9]+(?:\\.[0-9]+)?)");
			if (!match.Success)
			{
				return defaultVal;
			}
			float result;
			return float.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result) ? result : defaultVal;
		}
	}
	internal static class WinRateCache
	{
		private static Dictionary<string, WinRateEntry> _cache = new Dictionary<string, WinRateEntry>();

		private static bool _loaded = false;

		private static bool _loading = false;

		private static Action _onLoaded;

		public static bool IsLoaded => _loaded;

		private static string CacheFilePath
		{
			get
			{
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)RT.Instance).Info.Location);
				return Path.Combine(directoryName, "winrate_cache.json");
			}
		}

		public static string MakeKey(string cardName, string modName, string rarity)
		{
			return cardName + "|" + (modName ?? "Vanilla") + "|" + (rarity ?? "Common");
		}

		public static WinRateEntry Get(string key)
		{
			if (_cache.TryGetValue(key, out var value))
			{
				return value;
			}
			return null;
		}

		public static List<string> FindSimilar(string cardName, int max)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(cardName))
			{
				return list;
			}
			string value = cardName.ToLowerInvariant();
			foreach (KeyValuePair<string, WinRateEntry> item in _cache)
			{
				if (list.Count >= max)
				{
					break;
				}
				if (item.Key.ToLowerInvariant().Contains(value))
				{
					list.Add(item.Key + "(wr=" + item.Value.round_win_rate + ")");
				}
			}
			return list;
		}

		public static void Clear()
		{
			_cache.Clear();
			_loaded = false;
		}

		public static void Load(Action onComplete = null)
		{
			if (!_loading)
			{
				_onLoaded = onComplete;
				((MonoBehaviour)RT.Instance).StartCoroutine(LoadCoroutine());
			}
		}

		private static void SaveToFile(string rawJson)
		{
			try
			{
				File.WriteAllText(CacheFilePath, rawJson, Encoding.UTF8);
				RT.LogDebug("WinRateCache: saved to " + CacheFilePath);
			}
			catch (Exception ex)
			{
				RT.LogError("WinRateCache save error: " + ex.Message);
			}
		}

		private static string LoadFromFile()
		{
			try
			{
				if (File.Exists(CacheFilePath))
				{
					string text = File.ReadAllText(CacheFilePath, Encoding.UTF8);
					if (!string.IsNullOrEmpty(text) && text.Length > 10)
					{
						RT.LogDebug("WinRateCache: loaded from file, length=" + text.Length);
						return text;
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("WinRateCache file read error: " + ex.Message);
			}
			return null;
		}

		private static IEnumerator LoadCoroutine()
		{
			_loading = true;
			for (float waited = 0f; waited < 45f; waited += 1f)
			{
				try
				{
					if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null && CardChoice.instance.cards.Length > 10)
					{
						break;
					}
				}
				catch
				{
				}
				yield return (object)new WaitForSeconds(1f);
			}
			int lastCardCount = 0;
			float stableTime = 0f;
			for (float stabilizeWaited = 0f; stabilizeWaited < 45f; stabilizeWaited += 1f)
			{
				int currentCount = 0;
				try
				{
					if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null)
					{
						currentCount = CardChoice.instance.cards.Length;
					}
				}
				catch
				{
				}
				if (currentCount > 0 && currentCount == lastCardCount)
				{
					stableTime += 1f;
					if (stableTime >= 15f)
					{
						RT.LogDebug("WinRateCache: card count stabilized at " + currentCount + " after " + stabilizeWaited + "s");
						break;
					}
				}
				else
				{
					stableTime = 0f;
					lastCardCount = currentCount;
				}
				yield return (object)new WaitForSeconds(1f);
			}
			string modsParam = "";
			try
			{
				HashSet<string> modSet = new HashSet<string> { "Vanilla" };
				try
				{
					List<CardInfo> allCards = Cards.all;
					if (allCards != null)
					{
						foreach (CardInfo card in allCards)
						{
							if (!((Object)(object)card == (Object)null) && !((Object)(object)((Component)card).gameObject == (Object)null))
							{
								string modName = DC.ExtractModName(((Object)((Component)card).gameObject).name);
								modSet.Add(modName);
							}
						}
					}
				}
				catch
				{
					if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null)
					{
						CardInfo[] cards = CardChoice.instance.cards;
						foreach (CardInfo card2 in cards)
						{
							if (!((Object)(object)card2 == (Object)null))
							{
								string modName2 = DC.ExtractModName(((Object)((Component)card2).gameObject).name);
								modSet.Add(modName2);
							}
						}
					}
				}
				try
				{
					ReadOnlyCollection<CardInfo> hidden = Cards.instance.HiddenCards;
					if (hidden != null)
					{
						foreach (CardInfo card3 in hidden)
						{
							if (!((Object)(object)card3 == (Object)null) && !((Object)(object)((Component)card3).gameObject == (Object)null))
							{
								string modName3 = DC.ExtractModName(((Object)((Component)card3).gameObject).name);
								modSet.Add(modName3);
							}
						}
					}
				}
				catch
				{
				}
				RT.Log("WinRateCache: detected " + modSet.Count + " installed mods");
				if (modSet.Count > 0)
				{
					List<string> parts = new List<string>();
					foreach (string mod in modSet)
					{
						parts.Add(Uri.EscapeDataString(mod));
					}
					modsParam = "?mods=" + string.Join(",", parts.ToArray());
				}
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				RT.LogError("WinRateCache: mod detection error: " + ex2.Message);
			}
			string url = RT.ApiUrlValue + "/api/cards/winrates" + modsParam;
			RT.Log("WinRateCache: loading from " + url);
			bool serverSuccess = false;
			for (int attempt = 0; attempt < 3; attempt++)
			{
				if (serverSuccess)
				{
					break;
				}
				if (attempt > 0)
				{
					float delay = 1f * Mathf.Pow(2f, (float)(attempt - 1));
					RT.LogDebug("WinRateCache: retry " + attempt + "/" + 2 + " in " + delay + "s");
					yield return (object)new WaitForSeconds(delay);
				}
				UnityWebRequest req = UnityWebRequest.Get(url);
				try
				{
					req.timeout = 10;
					yield return req.SendWebRequest();
					if (req.isNetworkError || req.isHttpError)
					{
						RT.LogError("WinRateCache load error: " + req.error + " (attempt " + (attempt + 1) + "/" + 3 + ")");
						continue;
					}
					try
					{
						string rawText = req.downloadHandler.text;
						RT.Log("WinRateCache: HTTP=" + req.responseCode + " length=" + rawText.Length + " bytes");
						ParseResponse(rawText);
						if (_cache.Count > 0)
						{
							_loaded = true;
							serverSuccess = true;
							SaveToFile(rawText);
							RT.Log("WinRateCache: updated from server, " + _cache.Count + " cards");
							if (RT.ShowCardUpdateNotifications.Value)
							{
								GameMessage.Success("Card data updated (" + _cache.Count + " cards)", 5f);
							}
						}
					}
					catch (Exception ex)
					{
						Exception ex3 = ex;
						RT.LogError("WinRateCache parse error: " + ex3.Message);
					}
				}
				finally
				{
					((IDisposable)req)?.Dispose();
				}
			}
			if (!serverSuccess)
			{
				string cached = LoadFromFile();
				if (cached != null)
				{
					try
					{
						ParseResponse(cached);
						if (_cache.Count > 0)
						{
							_loaded = true;
							RT.Log("WinRateCache: loaded from local cache, " + _cache.Count + " cards");
							if (RT.ShowCardUpdateNotifications.Value)
							{
								GameMessage.Warn("Card data: using cached version (" + _cache.Count + " cards)", 5f);
							}
						}
					}
					catch (Exception ex)
					{
						Exception ex4 = ex;
						RT.LogError("WinRateCache cached parse error: " + ex4.Message);
					}
				}
				else
				{
					RT.Log("WinRateCache: no local cache available");
					if (RT.ShowCardUpdateNotifications.Value)
					{
						GameMessage.Error("Card data unavailable", 5f);
					}
				}
			}
			_loading = false;
			if (_onLoaded != null)
			{
				try
				{
					_onLoaded();
				}
				catch (Exception ex)
				{
					Exception ex5 = ex;
					RT.LogError("WinRateCache onLoaded error: " + ex5.Message);
				}
				_onLoaded = null;
			}
		}

		private static void ParseResponse(string json)
		{
			_cache.Clear();
			if (string.IsNullOrEmpty(json) || json.Length < 3)
			{
				return;
			}
			MatchCollection matchCollection = Regex.Matches(json, "\\{[^{}]*\\}");
			int num = 0;
			foreach (Match item in matchCollection)
			{
				string value = item.Value;
				string text = ExtractJsonString(value, "cn");
				if (!string.IsNullOrEmpty(text))
				{
					string text2 = ExtractJsonString(value, "mn");
					string text3 = ExtractJsonString(value, "r");
					string text4 = ExtractJsonString(value, "t");
					float num2 = ExtractJsonFloat(value, "wr", 0f);
					float pick_rate = ExtractJsonFloat(value, "pr", 0f);
					float num3 = ExtractJsonFloat(value, "s", -9999f);
					num++;
					if (num <= 3)
					{
						RT.Log($"WinRateCache PARSE[{num}] => cn=\"{text}\" mn=\"{text2}\" r=\"{text3}\" wr={num2} t=\"{text4}\" s={num3}");
					}
					string key = MakeKey(text, text2, text3);
					float? score = ((num3 > -9000f) ? new float?(num3) : ((float?)null));
					string[] tags = ((!string.IsNullOrEmpty(text4)) ? text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) : null);
					_cache[key] = new WinRateEntry
					{
						round_win_rate = num2,
						pick_rate = pick_rate,
						tags = tags,
						score = score
					};
				}
			}
			RT.Log($"WinRateCache ParseResponse done: {num} objects, {_cache.Count} cached");
		}

		private static string ExtractJsonString(string obj, string field)
		{
			Match match = Regex.Match(obj, "\"" + field + "\":\"((?:[^\"\\\\]|\\\\.)*)\"");
			return match.Success ? match.Groups[1].Value : null;
		}

		private static float ExtractJsonFloat(string obj, string field, float defaultVal)
		{
			Match match = Regex.Match(obj, "\"" + field + "\":(-?[0-9]+(?:\\.[0-9]+)?)");
			if (!match.Success)
			{
				return defaultVal;
			}
			float result;
			return float.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result) ? result : defaultVal;
		}
	}
	public class RoundCollector
	{
		public List<PickEvent> Picks = new List<PickEvent>();

		public List<CardData> Added = new List<CardData>();

		public List<CardData> Removed = new List<CardData>();

		public int PointsWon;

		public int PointsPlayed;

		public void Reset()
		{
			Picks.Clear();
			Added.Clear();
			Removed.Clear();
			PointsWon = 0;
			PointsPlayed = 0;
		}

		public void AddPick(PickEvent pick)
		{
			Picks.Add(pick);
			RT.LogDebug($"Collector: Added pick {pick.card_name}, total picks: {Picks.Count}");
		}

		public void AddAdded(CardData card)
		{
			Added.Add(card);
			RT.LogDebug($"Collector: Added card {card.card_name}, total added: {Added.Count}");
		}

		public void AddRemoved(CardData card)
		{
			Removed.Add(card);
			RT.LogDebug($"Collector: Removed card {card.card_name}, total removed: {Removed.Count}");
		}
	}
	[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)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			if (!RT.EnableTracking.Value || (Object)(object)pickedCard == (Object)null || PhotonNetwork.OfflineMode)
			{
				return;
			}
			try
			{
				PickerType val = (PickerType)(PickerTypeField?.GetValue(__instance));
				Player val2 = null;
				if ((int)val == 0)
				{
					Player[] playersInTeam = PlayerManager.instance.GetPlayersInTeam(___pickrID);
					val2 = ((playersInTeam != null && playersInTeam.Length != 0) ? playersInTeam[0] : null);
				}
				else if (___pickrID < PlayerManager.instance.players.Count)
				{
					val2 = PlayerManager.instance.players[___pickrID];
				}
				if ((Object)(object)val2 == (Object)null || !val2.data.view.IsMine)
				{
					return;
				}
				CardData cardData = DC.FromGO(pickedCard);
				if (cardData == null || string.IsNullOrEmpty(cardData.card_name))
				{
					return;
				}
				List<GameObject> list = SpawnedCardsField?.GetValue(__instance) as List<GameObject>;
				List<CardData> list2 = new List<CardData>();
				int num = 0;
				if (list != null)
				{
					for (int i = 0; i < list.Count; i++)
					{
						GameObject val3 = list[i];
						if ((Object)(object)val3 == (Object)null)
						{
							continue;
						}
						CardData cardData2 = DC.FromGO(val3);
						if (cardData2 != null && !string.IsNullOrEmpty(cardData2.card_name))
						{
							list2.Add(cardData2);
							if ((Object)(object)val3 == (Object)(object)pickedCard)
							{
								num = list2.Count - 1;
							}
						}
					}
				}
				PickEvent pick = new PickEvent
				{
					card_name = cardData.card_name,
					mod_name = cardData.mod_name,
					rarity = cardData.rarity,
					description = cardData.description,
					color_theme = cardData.color_theme,
					card_color = cardData.card_color,
					card_color_bg = cardData.card_color_bg,
					stats = cardData.stats,
					position = num,
					pick_number = RT.NextPickNumber(),
					offered_cards = list2
				};
				RT.Collector.AddPick(pick);
				if (RT._picksToChoose == 0 && list2.Count > 0)
				{
					RT._picksToChoose = list2.Count;
					RT.LogDebug($"Detected picks_to_choose={RT._picksToChoose}");
				}
				RT.Log($"Pick: {cardData.card_name} (pos {num + 1} of {list2.Count})");
			}
			catch (Exception ex)
			{
				RT.LogError("CardChoice.Pick error: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(CardChoice), "StartPick")]
	internal class StartPickPatch
	{
		private static void Prefix(int picksToSet)
		{
			if (!RT.EnableTracking.Value || PhotonNetwork.OfflineMode)
			{
				return;
			}
			try
			{
				if (RT._drawsPerPickPhase == 0 && picksToSet > 0)
				{
					RT._drawsPerPickPhase = picksToSet;
					RT.LogDebug($"Detected draws_per_pick_phase={picksToSet}");
				}
			}
			catch (Exception ex)
			{
				RT.LogError("StartPick patch error: " + ex.Message);
			}
		}
	}
	[HarmonyPatch]
	internal class AssignCardPatch
	{
		private static MethodBase TargetMethod()
		{
			Type typeFromHandle = typeof(Cards);
			MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "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(typeFromHandle, "RPCA_AssignCard", new Type[6]
				{
					typeof(string),
					typeof(int),
					typeof(bool),
					typeof(string),
					typeof(float),
					typeof(float)
				}, (Type[])null);
			}
			return methodInfo;
		}

		private static void Postfix(string cardObjectName, int playerID, bool reassign)
		{
			if (!RT.EnableTracking.Value || reassign || PhotonNetwork.OfflineMode)
			{
				return;
			}
			try
			{
				Player val = PlayerManager.instance.players.Find((Player p) => p.playerID == playerID);
				if (!((Object)(object)val == (Object)null) && val.data.view.IsMine)
				{
					CardData cardData = DC.FromObjectName(cardObjectName);
					if (cardData != null && !string.IsNullOrEmpty(cardData.card_name))
					{
						RT.Collector.AddAdded(cardData);
						RT.Log("Added: " + cardData.card_name);
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("RPCA_AssignCard error: " + ex.Message);
			}
		}
	}
	internal static class CardTracker
	{
		private static Dictionary<string, List<CardInfo>> _savedCardsById = new Dictionary<string, List<CardInfo>>();

		private static int _callCounter = 0;

		private static Stack<string> _callIdStack = new Stack<string>();

		public static void ClearAll()
		{
			_savedCardsById.Clear();
			_callCounter = 0;
			_callIdStack.Clear();
		}

		public static void RemoveAllCardsPrefix(Player player, bool clearBar)
		{
			if ((Object)(object)player == (Object)null || !((Object)(object)player.data?.view != (Object)null) || !player.data.view.IsMine || !RT.EnableTracking.Value || PhotonNetwork.OfflineMode)
			{
				return;
			}
			try
			{
				_callCounter++;
				string text = $"{player.playerID}_{_callCounter}_{Time.frameCount}";
				_callIdStack.Push(text);
				List<CardInfo> list = new List<CardInfo>();
				List<string> list2 = new List<string>();
				if (player.data?.currentCards != null)
				{
					foreach (CardInfo currentCard in player.data.currentCards)
					{
						if ((Object)(object)currentCard != (Object)null)
						{
							list.Add(currentCard);
							list2.Add(currentCard.cardName);
						}
					}
				}
				_savedCardsById[text] = list;
				if (_savedCardsById.Count > 100)
				{
					List<string> list3 = _savedCardsById.Keys.Take(_savedCardsById.Count - 50).ToList();
					foreach (string item in list3)
					{
						_savedCardsById.Remove(item);
					}
				}
				RT.LogDebug(string.Format("RemoveAllCardsPrefix: callId={0}, stack={1}, cards=[{2}]", text, _callIdStack.Count, string.Join(", ", list2)));
			}
			catch (Exception ex)
			{
				RT.LogError("RemoveAllCardsPrefix error: " + ex.Message);
			}
		}

		public static void AddCardsPostfix(Player player, CardInfo[] cards, bool[] reassigns, string[] twoLetterCodes, float[] forceDisplays, float[] forceDisplayDelays, bool addToCardBar)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)player.data?.view != (Object)null && player.data.view.IsMine;
			string text = null;
			if (flag && _callIdStack.Count > 0)
			{
				text = _callIdStack.Pop();
			}
			if (!flag)
			{
				return;
			}
			List<CardInfo> value = null;
			if (!string.IsNullOrEmpty(text) && _savedCardsById.TryGetValue(text, out value))
			{
				_savedCardsById.Remove(text);
			}
			List<string> list = new List<string>();
			if (cards != null)
			{
				for (int i = 0; i < cards.Length; i++)
				{
					if ((Object)(object)cards[i] != (Object)null)
					{
						bool flag2 = reassigns != null && i < reassigns.Length && reassigns[i];
						list.Add(cards[i].cardName + "(" + (flag2 ? "R" : "N") + ")");
					}
				}
			}
			List<string> values = value?.Select((CardInfo c) => c.cardName).ToList() ?? new List<string>();
			RT.LogDebug(string.Format("AddCardsPostfix: callId={0}, stack={1}, saved=[{2}], new=[{3}]", text, _callIdStack.Count, string.Join(", ", values), string.Join(", ", list)));
			if (!RT.EnableTracking.Value)
			{
				return;
			}
			if (cards == null || value == null || value.Count == 0)
			{
				RT.LogDebug("AddCardsPostfix: No data to process");
			}
			else
			{
				if (PhotonNetwork.OfflineMode)
				{
					return;
				}
				try
				{
					Dictionary<string, int> dictionary = new Dictionary<string, int>();
					for (int num = 0; num < cards.Length; num++)
					{
						if ((Object)(object)cards[num] != (Object)null && reassigns != null && num < reassigns.Length && reassigns[num])
						{
							string name = ((Object)cards[num]).name;
							if (!dictionary.ContainsKey(name))
							{
								dictionary[name] = 0;
							}
							dictionary[name]++;
						}
					}
					foreach (CardInfo item in value)
					{
						string name2 = ((Object)item).name;
						if (dictionary.ContainsKey(name2) && dictionary[name2] > 0)
						{
							dictionary[name2]--;
							continue;
						}
						RT.LogDebug("AddCardsPostfix: REMOVED " + item.cardName + " (name=" + name2 + ")");
						GameObject gameObject = ((Component)item).gameObject;
						CardData cardData = DC.FromInfo(item, (gameObject != null) ? ((Object)gameObject).name : null);
						if (cardData != null && !string.IsNullOrEmpty(cardData.card_name))
						{
							RT.Collector.AddRemoved(cardData);
							RT.Log("Removed: " + cardData.card_name);
						}
					}
				}
				catch (Exception ex)
				{
					RT.LogError("AddCardsPostfix error: " + ex.Message);
				}
			}
		}
	}
	internal static class DC
	{
		public static CardData FromGO(GameObject obj)
		{
			if ((Object)(object)obj == (Object)null)
			{
				return null;
			}
			CardInfo component = obj.GetComponent<CardInfo>();
			return ((Object)(object)component != (Object)null) ? FromInfo(component, ((Object)obj).name) : null;
		}

		public static CardData FromObjectName(string objectName)
		{
			if (string.IsNullOrEmpty(objectName))
			{
				return null;
			}
			try
			{
				CardInfo cardWithObjectName = Cards.instance.GetCardWithObjectName(objectName);
				if ((Object)(object)cardWithObjectName != (Object)null)
				{
					return FromInfo(cardWithObjectName, objectName);
				}
			}
			catch
			{
			}
			return ParseObjectName(objectName);
		}

		public static CardData ParseObjectName(string objectName)
		{
			string mod_name = ExtractModName(objectName);
			string card_name = objectName;
			if (objectName.StartsWith("__"))
			{
				string[] array = objectName.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 2)
				{
					card_name = array[1];
				}
			}
			return new CardData
			{
				card_name = card_name,
				mod_name = mod_name,
				rarity = "Common",
				description = null,
				color_theme = null,
				card_color = null,
				card_color_bg = null,
				rarity_color = null,
				rarity_color_off = null,
				stats = new List<Stat>(),
				allow_multiple = null
			};
		}

		public static string ExtractModName(string objectName)
		{
			if (string.IsNullOrEmpty(objectName))
			{
				return "Vanilla";
			}
			string text = objectName;
			int num = text.IndexOf("(Clone)");
			if (num >= 0)
			{
				text = text.Substring(0, num).Trim();
			}
			if (text.StartsWith("__"))
			{
				string[] array = text.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 1)
				{
					return array[0];
				}
			}
			return "Vanilla";
		}

		public static CardData FromInfo(CardInfo info, string objectName = null)
		{
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: 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_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)info == (Object)null)
			{
				return null;
			}
			string mod_name = "Vanilla";
			object obj = objectName;
			if (obj == null)
			{
				GameObject gameObject = ((Component)info).gameObject;
				obj = ((gameObject != null) ? ((Object)gameObject).name : null) ?? "";
			}
			string text = (string)obj;
			if (text.StartsWith("__"))
			{
				string[] array = text.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length >= 1)
				{
					mod_name = array[0];
				}
			}
			List<Stat> list = new List<Stat>();
			try
			{
				if (info.cardStats != null)
				{
					CardInfoStat[] cardStats = info.cardStats;
					foreach (CardInfoStat val in cardStats)
					{
						list.Add(new Stat
						{
							stat = (val.stat ?? ""),
							amount = (val.amount ?? ""),
							positive = val.positive
						});
					}
				}
			}
			catch
			{
			}
			string description = null;
			try
			{
				description = info.cardDestription;
			}
			catch
			{
			}
			string rarity = "Common";
			try
			{
				rarity = ((object)Unsafe.As<Rarity, Rarity>(ref info.rarity)/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
			}
			string color_theme = null;
			try
			{
				color_theme = ((object)Unsafe.As<CardThemeColorType, CardThemeColorType>(ref info.colorTheme)/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
			}
			string card_color = null;
			try
			{
				card_color = "#" + ColorUtility.ToHtmlStringRGB(info.cardColor);
			}
			catch
			{
			}
			string card_color_bg = null;
			try
			{
				if ((Object)(object)CardChoice.instance != (Object)null)
				{
					Color cardColor = CardChoice.instance.GetCardColor2(info.colorTheme);
					card_color_bg = "#" + ColorUtility.ToHtmlStringRGB(cardColor);
				}
			}
			catch
			{
			}
			bool? allow_multiple = null;
			try
			{
				allow_multiple = info.allowMultiple;
			}
			catch
			{
			}
			string rarity_color = null;
			string rarity_color_off = null;
			try
			{
				Rarity rarityData = RarityUtils.GetRarityData(info.rarity);
				if (rarityData != null)
				{
					rarity_color = "#" + ColorUtility.ToHtmlStringRGB(rarityData.color);
					rarity_color_off = "#" + ColorUtility.ToHtmlStringRGB(rarityData.colorOff);
				}
			}
			catch
			{
			}
			return new CardData
			{
				card_name = (info.cardName ?? ""),
				mod_name = mod_name,
				rarity = rarity,
				description = description,
				color_theme = color_theme,
				card_color = card_color,
				card_color_bg = card_color_bg,
				rarity_color = rarity_color,
				rarity_color_off = rarity_color_off,
				stats = list,
				allow_multiple = allow_multiple
			};
		}
	}
	[Serializable]
	public class CardData
	{
		public string card_name;

		public string mod_name;

		public string rarity;

		public string description;

		public string color_theme;

		public string card_color;

		public string card_color_bg;

		public string rarity_color;

		public string rarity_color_off;

		public List<Stat> stats = new List<Stat>();

		public bool? allow_multiple;
	}
	[Serializable]
	public class PickEvent : CardData
	{
		public int position;

		public int pick_number;

		public List<CardData> offered_cards = new List<CardData>();
	}
	[Serializable]
	public class Stat
	{
		public string stat;

		public string amount;

		public bool positive;
	}
	[Serializable]
	public class RoundReport
	{
		public string report_id;

		public string session_id;

		public int round_number;

		public string steam_id;

		public string nickname;

		public string player_color;

		public bool is_round_winner;

		public int points_won;

		public int points_played;

		public List<CardData> current_cards = new List<CardData>();

		public List<PickEvent> picks = new List<PickEvent>();

		public List<CardData> offered_cards = new List<CardData>();

		public List<CardData> added = new List<CardData>();

		public List<CardData> removed = new List<CardData>();

		public List<PlayerInfo> players = new List<PlayerInfo>();

		public string game_mode;

		public int player_count;

		public int points_to_win_round;

		public int rounds_to_win_game;

		public int game_continued_count;

		public int picks_to_choose;

		public int draws_per_pick_phase;

		public bool is_game_over;

		public bool is_legitimate_game_over;

		public string tracker_version;
	}
	[Serializable]
	public class PlayerInfo
	{
		public int player_id;

		public string steam_id;

		public string nickname;

		public string player_color;
	}
	public class WinRateEntry
	{
		public float round_win_rate;

		public float pick_rate;

		public string[] tags;

		public float? score;
	}
	[Serializable]
	public class EloPlayerData
	{
		public string steam_id;

		public float before;

		public float after;

		public float change;
	}
	internal class WinRateCacheDto
	{
		public string cn;

		public string mn;

		public string r;

		public float wr;

		public string t;

		public float s;
	}
	[Serializable]
	internal class LocalEloResponseDto
	{
		public float elo;

		public int elo_games;

		public int xp;

		public int level;
	}
	[Serializable]
	public class XpChangeData
	{
		public string steam_id;

		public int xp_gained;

		public int xp_before;

		public int xp_after;

		public int level_before;

		public int level_after;
	}
	[Serializable]
	public class AchievementNotif
	{
		public string id;

		public string icon;

		public string name;

		public int xp;

		public string description;
	}
	internal static class Networking
	{
		private const string ELO_PROP_PREFIX = "rt_elo_";

		private const string XP_PROP_PREFIX = "rt_xp_";

		private const float ELO_POLL_INTERVAL = 0.5f;

		private const float ELO_POLL_TIMEOUT = 10f;

		private static string GetEloKey(string steamId)
		{
			return string.Format("{0}{1}_{2}", "rt_elo_", steamId, RT._gameContinuedCount);
		}

		private static string GetXpKey(string steamId)
		{
			return string.Format("{0}{1}_{2}", "rt_xp_", steamId, RT._gameContinuedCount);
		}

		public static void BroadcastEloResults(List<EloPlayerData> allElo)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			if (PhotonNetwork.OfflineMode || allElo == null || allElo.Count == 0)
			{
				return;
			}
			if (PhotonNetwork.CurrentRoom == null)
			{
				RT.LogError("BroadcastEloResults: no current room");
				return;
			}
			Hashtable val = new Hashtable();
			int num = 0;
			foreach (EloPlayerData item in allElo)
			{
				if (!string.IsNullOrEmpty(item.steam_id))
				{
					string eloKey = GetEloKey(item.steam_id);
					string text = (string)(val[(object)eloKey] = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2}", item.before, item.after, item.change));
					num++;
					RT.Log("BroadcastElo prop: ****" + item.steam_id.Substring(Math.Max(0, item.steam_id.Length - 4)) + " = " + text);
				}
			}
			if (num > 0)
			{
				PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				RT.Log($"BroadcastElo: set {num} room properties");
			}
		}

		public static void StartEloPropertyPoll(int roundNumber)
		{
			if (RT.EnableTracking.Value && RT.ShowEloNotifications.Value && !RT._eloShownThisGame && roundNumber > 0)
			{
				if (!PhotonNetwork.OfflineMode)
				{
					((MonoBehaviour)RT.Instance).StartCoroutine(PollEloFromRoomProperties());
				}
				string sessionId = RT.GetSessionId();
				string steamId = RT.GetSteamId();
				if (!string.IsNullOrEmpty(sessionId) && !string.IsNullOrEmpty(steamId))
				{
					((MonoBehaviour)RT.Instance).StartCoroutine(Api.PollEloFromHttp(sessionId, steamId, roundNumber));
				}
			}
		}

		private static IEnumerator PollEloFromRoomProperties()
		{
			string mySteamId = RT.GetSteamId();
			if (string.IsNullOrEmpty(mySteamId))
			{
				yield break;
			}
			string myKey = GetEloKey(mySteamId);
			float elapsed = 0f;
			RT.LogDebug("ELO poll: waiting for room prop '" + myKey + "'");
			for (; elapsed < 10f; elapsed += 0.5f)
			{
				if (RT._eloShownThisGame)
				{
					yield break;
				}
				if (PhotonNetwork.CurrentRoom == null)
				{
					RT.LogDebug("ELO poll: room is null, stopping");
					yield break;
				}
				Hashtable roomProps = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties;
				if (((Dictionary<object, object>)(object)roomProps)?.ContainsKey((object)myKey) ?? false)
				{
					string value = roomProps[(object)myKey] as string;
					if (!string.IsNullOrEmpty(value))
					{
						ProcessEloPropertyValue(value, mySteamId);
						yield break;
					}
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
			RT.LogDebug("ELO poll: timeout, falling back to HTTP check");
		}

		private static void ProcessEloPropertyValue(string value, string mySteamId)
		{
			try
			{
				string[] array = value.Split(new char[1] { '|' });
				if (array.Length < 3)
				{
					return;
				}
				float num = float.Parse(array[0], CultureInfo.InvariantCulture);
				float num2 = float.Parse(array[1], CultureInfo.InvariantCulture);
				float num3 = float.Parse(array[2], CultureInfo.InvariantCulture);
				RT.Log($"ELO from room prop for ****{mySteamId.Substring(Math.Max(0, mySteamId.Length - 4))}: {num:F1} -> {num2:F1} ({num3:F1})");
				if (num <= 0f && num2 <= 0f)
				{
					RT.LogDebug("ELO room prop: invalid data (all zeros), skipping — HTTP poll will handle");
				}
				else if (!RT._eloShownThisGame)
				{
					RT._eloShownThisGame = true;
					if (num3 != 0f && RT.ShowEloNotifications.Value)
					{
						RT.ShowEloChangeNotification(num, num2, num3, "room prop");
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("ELO prop parse error: " + ex.Message);
			}
		}

		public static void BroadcastXpResults(List<XpChangeData> allXp)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			if (PhotonNetwork.OfflineMode || allXp == null || allXp.Count == 0)
			{
				return;
			}
			if (PhotonNetwork.CurrentRoom == null)
			{
				RT.LogError("BroadcastXpResults: no current room");
				return;
			}
			Hashtable val = new Hashtable();
			int num = 0;
			foreach (XpChangeData item in allXp)
			{
				if (!string.IsNullOrEmpty(item.steam_id))
				{
					string xpKey = GetXpKey(item.steam_id);
					string text = (string)(val[(object)xpKey] = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2}|{3}|{4}", item.xp_gained, item.xp_before, item.xp_after, item.level_before, item.level_after));
					num++;
					RT.LogDebug("BroadcastXp prop: ****" + item.steam_id.Substring(Math.Max(0, item.steam_id.Length - 4)) + " = " + text);
				}
			}
			if (num > 0)
			{
				PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				RT.Log($"BroadcastXp: set {num} room properties");
			}
		}

		public static void StartXpPropertyPoll()
		{
			if (!PhotonNetwork.OfflineMode && RT.EnableTracking.Value && RT.ShowXpNotifications.Value && !RT._xpShownThisGame)
			{
				((MonoBehaviour)RT.Instance).StartCoroutine(PollXpFromRoomProperties());
			}
		}

		private static IEnumerator PollXpFromRoomProperties()
		{
			string mySteamId = RT.GetSteamId();
			if (string.IsNullOrEmpty(mySteamId))
			{
				yield break;
			}
			string myKey = GetXpKey(mySteamId);
			float elapsed = 0f;
			RT.LogDebug("XP poll: waiting for room prop '" + myKey + "'");
			for (; elapsed < 10f; elapsed += 0.5f)
			{
				if (RT._xpShownThisGame)
				{
					yield break;
				}
				if (PhotonNetwork.CurrentRoom == null)
				{
					RT.LogDebug("XP poll: room is null, stopping");
					yield break;
				}
				Hashtable roomProps = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties;
				if (((Dictionary<object, object>)(object)roomProps)?.ContainsKey((object)myKey) ?? false)
				{
					string value = roomProps[(object)myKey] as string;
					if (!string.IsNullOrEmpty(value))
					{
						ProcessXpPropertyValue(value, mySteamId);
						yield break;
					}
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
			RT.LogDebug("XP poll: timeout");
		}

		public static void ProcessXpPropertyValue(string value, string mySteamId)
		{
			try
			{
				string[] array = value.Split(new char[1] { '|' });
				if (array.Length >= 5)
				{
					XpChangeData xpChangeData = new XpChangeData
					{
						steam_id = mySteamId,
						xp_gained = int.Parse(array[0], CultureInfo.InvariantCulture),
						xp_before = int.Parse(array[1], CultureInfo.InvariantCulture),
						xp_after = int.Parse(array[2], CultureInfo.InvariantCulture),
						level_before = int.Parse(array[3], CultureInfo.InvariantCulture),
						level_after = int.Parse(array[4], CultureInfo.InvariantCulture)
					};
					RT.Log($"XP from room prop for ****{mySteamId.Substring(Math.Max(0, mySteamId.Length - 4))}: +{xpChangeData.xp_gained} Lv.{xpChangeData.level_before}->{xpChangeData.level_after}");
					if (!RT._xpShownThisGame && xpChangeData.xp_gained > 0 && RT.ShowXpNotifications.Value)
					{
						RT._xpShownThisGame = true;
						((MonoBehaviour)RT.Instance).StartCoroutine(RT.ShowXpNotification(xpChangeData, null));
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("XP prop parse error: " + ex.Message);
			}
		}

		[UnboundRPC]
		public static void RPCA_CardRemoved(int playerID, string cardName, string cardObjectName)
		{
			RT.LogDebug($"RPCA_CardRemoved (ignored): player={playerID}, card={cardName}");
		}

		[UnboundRPC]
		public static void RPCA_ShareSteamId(int playerID, string steamId)
		{
			if (!string.IsNullOrEmpty(steamId))
			{
				RT.PlayerSteamIds[playerID] = steamId;
				RT.LogDebug($"Received Steam ID for player {playerID}: {steamId.Substring(0, Math.Min(4, steamId.Length))}****");
			}
		}

		[UnboundRPC]
		public static void RPCA_ShareEloResult(string data)
		{
		}

		public static void BroadcastSteamId()
		{
			if (!PhotonNetwork.OfflineMode)
			{
				Player localPlayer = RT.GetLocalPlayer();
				string steamId = RT.GetSteamId();
				if ((Object)(object)localPlayer != (Object)null && !string.IsNullOrEmpty(steamId))
				{
					NetworkingManager.RPC(typeof(Networking), "RPCA_ShareSteamId", new object[2] { localPlayer.playerID, steamId });
				}
			}
		}

		public static void FetchLocalElo()
		{
			string steamId = RT.GetSteamId();
			if (!string.IsNullOrEmpty(steamId) && RT.EnableTracking.Value && RT.ShowEloNotifications.Value)
			{
				((MonoBehaviour)RT.Instance).StartCoroutine(FetchLocalEloCoroutine(steamId));
			}
		}

		private static IEnumerator FetchLocalEloCoroutine(string steamId)
		{
			string url = RT.ApiUrlValue + "/api/elo/me?steam_id=" + steamId;
			UnityWebRequest req = UnityWebRequest.Get(url);
			try
			{
				req.timeout = 10;
				yield return req.SendWebRequest();
				if (req.isNetworkError || req.isHttpError)
				{
					RT.LogDebug("FetchLocalElo error: " + req.error);
					yield break;
				}
				string text = req.downloadHandler.text;
				if (string.IsNullOrEmpty(text))
				{
					yield break;
				}
				try
				{
					LocalEloResponseDto resp = JsonUtility.FromJson<LocalEloResponseDto>(text);
					if (resp != null && resp.elo > 0f)
					{
						int displayElo = (int)Math.Round(resp.elo);
						int xpInLevel = resp.xp - 300 * (resp.level - 1) * resp.level / 2;
						int xpForLevel = 300 * resp.level;
						int pct = ((xpForLevel > 0) ? ((int)Math.Round((float)xpInLevel / (float)xpForLevel * 100f)) : 0);
						GameMessage.Show($"ELO: {displayElo}  ({resp.elo_games} games)", (MessageType)0, RT.NotifDuration);
						GameMessage.Show($"Lv.{resp.level}  ({pct}% to next)", (MessageType)0, RT.NotifDuration);
						RT.Log($"Current ELO: {resp.elo:F1} ({resp.elo_games} games) | Lv.{resp.level} {pct}% to next");
					}
				}
				catch (Exception ex)
				{
					RT.LogError("FetchLocalElo parse: " + ex.Message);
				}
			}
			finally
			{
				((IDisposable)req)?.Dispose();
			}
		}

		public static void EnsureSteamInfo()
		{
			if (RT._steamInfoTried)
			{
				return;
			}
			RT._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)
						{
							RT._steamId = obj.ToString();
						}
					}
				}
				Type type3 = Type.GetType("Steamworks.SteamFriends, Assembly-CSharp-firstpass") ?? Type.GetType("Steamworks.SteamFriends, com.rlabrecque.steamworks.net");
				if (!(type3 != null))
				{
					return;
				}
				MethodInfo method2 = type3.GetMethod("GetPersonaName", BindingFlags.Static | BindingFlags.Public);
				if (method2 != null)
				{
					object obj2 = method2.Invoke(null, null);
					if (obj2 != null)
					{
						RT._steamNickname = obj2.ToString();
					}
				}
			}
			catch (Exception ex)
			{
				RT.LogError("Steam info error: " + ex.Message);
			}
		}

		public static void CollectSessionPlayers()
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			RT.SessionPlayers.Clear();
			foreach (Player player in PlayerManager.instance.players)
			{
				if ((Object)(object)player == (Object)null || (Object)(object)player.data == (Object)null)
				{
					continue;
				}
				string value = null;
				if ((Object)(object)player.data.view != (Object)null && player.data.view.IsMine)
				{
					value = RT.GetSteamId();
				}
				else
				{
					RT.PlayerSteamIds.TryGetValue(player.playerID, out value);
				}
				string nickname = null;
				try
				{
					if ((Object)(object)player.data.view != (Object)null && player.data.view.Owner != null)
					{
						nickname = player.data.view.Owner.NickName;
					}
				}
				catch
				{
				}
				string player_color = null;
				try
				{
					PlayerSkin playerSkinColors = PlayerSkinBank.GetPlayerSkinColors(player.playerID);
					player_color = "#" + ColorUtility.ToHtmlStringRGB(playerSkinColors.color);
				}
				catch
				{
				}
				RT.SessionPlayers.Add(new PlayerInfo
				{
					player_id = player.playerID,
					steam_id = value,
					nickname = nickname,
					player_color = player_color
				});
			}
			RT.LogDebug($"Collected {RT.SessionPlayers.Count} session players, " + $"{RT.SessionPlayers.Count((PlayerInfo p) => p.steam_id != null)} with Steam ID");
		}

		public static void RefreshSessionPlayers()
		{
			if (RT.SessionPlayers.Count == 0)
			{
				CollectSessionPlayers();
				return;
			}
			bool flag = false;
			foreach (PlayerInfo sessionPlayer in RT.SessionPlayers)
			{
				if (sessionPlayer.steam_id == null && RT.PlayerSteamIds.TryGetValue(sessionPlayer.player_id, out var value) && !string.IsNullOrEmpty(value))
				{
					sessionPlayer.steam_id = value;
					flag = true;
					RT.LogDebug($"RefreshSessionPlayers: player {sessionPlayer.player_id} got steam_id");
				}
			}
			if (flag)
			{
				RT.LogDebug($"RefreshSessionPlayers: now {RT.SessionPlayers.Count((PlayerInfo p) => p.steam_id != null)}/{RT.SessionPlayers.Count} with Steam ID");
			}
		}
	}
	internal static class ReportSerializer
	{
		public static string ToJson(RoundReport r)
		{
			StringBuilder stringBuilder = new StringBuilder(4096);
			stringBuilder.Append('{');
			AppendStr(stringBuilder, "report_id", r.report_id);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "session_id", r.session_id);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "round_number", r.round_number);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "steam_id", r.steam_id);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "nickname", r.nickname);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "player_color", r.player_color);
			stringBuilder.Append(',');
			AppendBool(stringBuilder, "is_round_winner", r.is_round_winner);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "points_won", r.points_won);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "points_played", r.points_played);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "current_cards");
			AppendCardList(stringBuilder, r.current_cards);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "picks");
			AppendPickList(stringBuilder, r.picks);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "offered_cards");
			AppendCardList(stringBuilder, r.offered_cards);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "added");
			AppendCardList(stringBuilder, r.added);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "removed");
			AppendCardList(stringBuilder, r.removed);
			stringBuilder.Append(',');
			AppendKey(stringBuilder, "players");
			AppendPlayerList(stringBuilder, r.players);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "game_mode", r.game_mode);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "player_count", r.player_count);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "points_to_win_round", r.points_to_win_round);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "rounds_to_win_game", r.rounds_to_win_game);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "game_continued_count", r.game_continued_count);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "picks_to_choose", r.picks_to_choose);
			stringBuilder.Append(',');
			AppendInt(stringBuilder, "draws_per_pick_phase", r.draws_per_pick_phase);
			stringBuilder.Append(',');
			AppendBool(stringBuilder, "is_game_over", r.is_game_over);
			stringBuilder.Append(',');
			AppendBool(stringBuilder, "is_legitimate_game_over", r.is_legitimate_game_over);
			stringBuilder.Append(',');
			AppendStr(stringBuilder, "tracker_version", r.tracker_version);
			stringBuilder.Append('}');
			return stringBuilder.ToString();
		}

		private static void AppendKey(StringBuilder sb, string key)
		{
			sb.Append('"').Append(key).Append('"')
				.Append(':');
		}

		private static void AppendStr(StringBuilder sb, string key, string value)
		{
			AppendKey(sb, key);
			if (value == null)
			{
				sb.Append("null");
				return;
			}
			sb.Append('"');
			EscapeString(sb, value);
			sb.Append('"');
		}

		private static void AppendInt(StringBuilder sb, string key, int value)
		{
			AppendKey(sb, key);
			sb.Append(value);
		}

		private static void AppendBool(StringBuilder sb, string key, bool value)
		{
			AppendKey(sb, key);
			sb.Append(value ? "true" : "false");
		}

		private static void AppendNullableBool(StringBuilder sb, string key, bool? value)
		{
			AppendKey(sb, key);
			if (!value.HasValue)
			{
				sb.Append("null");
			}
			else
			{
				sb.Append(value.Value ? "true" : "false");
			}
		}

		private static void EscapeString(StringBuilder sb, string value)
		{
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					sb.AppendFormat("\\u{0:x4}", (int)c);
				}
				else
				{
					sb.Append(c);
				}
			}
		}

		private static void AppendCard(StringBuilder sb, CardData c)
		{
			sb.Append('{');
			AppendStr(sb, "card_name", c.card_name);
			sb.Append(',');
			AppendStr(sb, "mod_name", c.mod_name);
			sb.Append(',');
			AppendStr(sb, "rarity", c.rarity);
			sb.Append(',');
			AppendStr(sb, "description", c.description);
			sb.Append(',');
			AppendStr(sb, "color_theme", c.color_theme);
			sb.Append(',');
			AppendStr(sb, "card_color", c.card_color);
			sb.Append(',');
			AppendStr(sb, "card_color_bg", c.card_color_bg);
			sb.Append(',');
			AppendStr(sb, "rarity_color", c.rarity_color);
			sb.Append(',');
			AppendStr(sb, "rarity_color_off", c.rarity_color_off);
			sb.Append(',');
			AppendKey(sb, "stats");
			AppendStatList(sb, c.stats);
			sb.Append(',');
			AppendNullableBool(sb, "allow_multiple", c.allow_multiple);
			sb.Append('}');
		}

		private static void AppendPick(StringBuilder sb, PickEvent p)
		{
			sb.Append('{');
			AppendStr(sb, "card_name", p.card_name);
			sb.Append(',');
			AppendStr(sb, "mod_name", p.mod_name);
			sb.Append(',');
			AppendStr(sb, "rarity", p.rarity);
			sb.Append(',');
			AppendStr(sb, "description", p.description);
			sb.Append(',');
			AppendStr(sb, "color_theme", p.color_theme);
			sb.Append(',');
			AppendStr(sb, "card_color", p.card_color);
			sb.Append(',');
			AppendStr(sb, "card_color_bg", p.card_color_bg);
			sb.Append(',');
			AppendStr(sb, "rarity_color", p.rarity_color);
			sb.Append(',');
			AppendStr(sb, "rarity_color_off", p.rarity_color_off);
			sb.Append(',');
			AppendKey(sb, "stats");
			AppendStatList(sb, p.stats);
			sb.Append(',');
			AppendNullableBool(sb, "allow_multiple", p.allow_multiple);
			sb.Append(',');
			AppendInt(sb, "position", p.position);
			sb.Append(',');
			AppendInt(sb, "pick_number", p.pick_number);
			sb.Append(',');
			AppendKey(sb, "offered_cards");
			AppendCardList(sb, p.offered_cards);
			sb.Append('}');
		}

		private static void AppendPlayer(StringBuilder sb, PlayerInfo p)
		{
			sb.Append('{');
			AppendInt(sb, "player_id", p.player_id);
			sb.Append(',');
			AppendStr(sb, "steam_id", p.steam_id);
			sb.Append(',');
			AppendStr(sb, "nickname", p.nickname);
			sb.Append(',');
			AppendStr(sb, "player_color", p.player_color);
			sb.Append('}');
		}

		private static void AppendStat(StringBuilder sb, Stat s)
		{
			sb.Append('{');
			AppendStr(sb, "stat", s.stat);
			sb.Append(',');
			AppendStr(sb, "amount", s.amount);
			sb.Append(',');
			AppendBool(sb, "positive", s.positive);
			sb.Append('}');
		}

		private static void AppendCardList(StringBuilder sb, List<CardData> list)
		{
			sb.Append('[');
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					AppendCard(sb, list[i]);
				}
			}
			sb.Append(']');
		}

		private static void AppendPickList(StringBuilder sb, List<PickEvent> list)
		{
			sb.Append('[');
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					AppendPick(sb, list[i]);
				}
			}
			sb.Append(']');
		}

		private static void AppendPlayerList(StringBuilder sb, List<PlayerInfo> list)
		{
			sb.Append('[');
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					AppendPlayer(sb, list[i]);
				}
			}
			sb.Append(']');
		}

		private static void AppendStatList(StringBuilder sb, List<Stat> list)
		{
			sb.Append('[');
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					AppendStat(sb, list[i]);
				}
			}
			sb.Append(']');
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("r00t.systems.best.patch")]
	[BepInPlugin("com.rounds.tracker", "Rounds Tracker", "1.0.22")]
	[BepInProcess("Rounds.exe")]
	public class RT : BaseUnityPlugin
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static UnityAction <>9__56_0;

			public static UnityAction<bool> <>9__62_0;

			public static UnityAction<bool> <>9__62_1;

			public static UnityAction<bool> <>9__62_2;

			public static UnityAction<bool> <>9__62_3;

			public static UnityAction<bool> <>9__62_4;

			public static UnityAction<bool> <>9__62_5;

			public static UnityAction<bool> <>9__62_6;

			public static UnityAction<bool> <>9__62_7;

			public static UnityAction<bool> <>9__62_8;

			public static UnityAction<float> <>9__62_9;

			internal void <Start>b__56_0()
			{
				_pendingEnableTracking = EnableTracking.Value;
				_pendingShowWinRates = ShowWinRates.Value;
				_pendingShowPickRates = ShowPickRates.Value;
				_pendingShowCardScore = ShowCardScore.Value;
				_pendingShowCardTags = ShowCardTags.Value;
				_pendingShowUpdateNotif = ShowCardUpdateNotifications.Value;
				_pendingShowEloNotif = ShowEloNotifications.Value;
				_pendingShowXpNotif = ShowXpNotifications.Value;
				_pendingShowSendErrorNotif = ShowSendErrorNotifications.Value;
				_pendingNotifDuration = NotificationDuration.Value;
			}

			internal void <BuildMenu>b__62_0(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowWinRates = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_1(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowPickRates = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_2(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowCardScore = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_3(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowCardTags = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_4(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingEnableTracking = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_5(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowUpdateNotif = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_6(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowEloNotif = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_7(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowXpNotif = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_8(bool val)
			{
				if (!_menuBuilding)
				{
					_pendingShowSendErrorNotif = val;
					_configDirty = true;
				}
			}

			internal void <BuildMenu>b__62_9(float val)
			{
				if (!_menuBuilding)
				{
					_pendingNotifDuration = (int)val;
					_configDirty = true;
				}
			}
		}

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

		public const string ModName = "Rounds Tracker";

		public const string Version = "1.0.22";

		public const string SettingsResetVersion = "1";

		public const bool ENABLE_LOGS = false;

		public static ConfigEntry<bool> EnableTracking;

		public static ConfigEntry<bool> ShowWinRates;

		public static ConfigEntry<bool> ShowPickRates;

		public static ConfigEntry<bool> ShowCardScore;

		public static ConfigEntry<bool> ShowCardTags;

		public static ConfigEntry<bool> ShowCardUpdateNotifications;

		public static ConfigEntry<bool> ShowEloNotifications;

		public static ConfigEntry<bool> ShowXpNotifications;

		public static ConfigEntry<bool> ShowSendErrorNotifications;

		public static ConfigEntry<int> NotificationDuration;

		public static ConfigEntry<bool> DebugLogging;

		private static bool _pendingEnableTracking;

		private static bool _pendingShowWinRates;

		private static bool _pendingShowPickRates;

		private static bool _pendingShowCardScore;

		private static bool _pendingShowCardTags;

		private static bool _pendingShowUpdateNotif;

		private static bool _pendingShowEloNotif;

		private static bool _pendingShowXpNotif;

		private static bool _pendingShowSendErrorNotif;

		private static int _pendingNotifDuration;

		private static bool _configDirty = false;

		private static string _sessionId;

		private static int _pickNumber;

		private static int _roundNumber;

		private static string _currentRoomName;

		private static int _gameIndexInRoom;

		internal static string _steamId;

		internal static string _steamNickname;

		internal static bool _steamInfoTried;

		private static RoundCollector _collector = new RoundCollector();

		private static int _lastRoundSent = 0;

		private static bool _gameOverSent = false;

		internal static int _gameContinuedCount = 0;

		private static int _lastKnownThreshold = 0;

		internal static int _picksToChoose = 0;

		internal static int _drawsPerPickPhase = 0;

		internal static bool _eloShownThisGame = false;

		internal static bool _xpShownThisGame = false;

		internal static Dictionary<int, string> PlayerSteamIds = new Dictionary<int, string>();

		internal static List<PlayerInfo> SessionPlayers = new List<PlayerInfo>();

		public const bool ENABLE_NGROK = false;

		public static string ApiUrlValueLocal = "http://localhost:8000";

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

		private static bool _menuBuilding = false;

		public static RT Instance { get; private set; }

		public static float NotifDuration => NotificationDuration?.Value ?? 10;

		public static RoundCollector Collector => _collector;

		private void Awake()
		{
			//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			ConfigEntry<string> val = ((BaseUnityPlugin)this).Config.Bind<string>("Meta", "LastResetVersion", "1", "Settings reset version, do not edit");
			EnableTracking = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTracking", true, "Enable tracking");
			ShowWinRates = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowWinRates", false, "Show card win rates during pick phase");
			ShowPickRates = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowPickRates", false, "Show card pick rates during pick phase");
			ShowCardTags = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowCardTags", true, "Show card classification tags (Strong, Balanced, etc.) during pick phase");
			ShowCardScore = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowCardScore", true, "Show card power score during pick phase");
			ShowCardUpdateNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowCardUpdateNotifications", true, "Show notification when card data is updated");
			ShowEloNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowEloNotifications", true, "Show notification when ELO rating changes");
			ShowXpNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowXpNotifications", true, "Show XP and achievement notifications at game end");
			ShowSendErrorNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowSendErrorNotifications", true, "Show notification when data fails to send after all retries");
			NotificationDuration = ((BaseUnityPlugin)this).Config.Bind<int>("Notifications", "NotificationDuration", 10, "Duration (seconds) of in-game notifications. Range: 1-15.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", true, "Debug logging");
			if (val.Value != "1")
			{
				EnableTracking.Value = true;
				ShowWinRates.Value = false;
				ShowPickRates.Value = false;
				ShowCardTags.Value = true;
				ShowCardScore.Value = true;
				ShowCardUpdateNotifications.Value = true;
				ShowEloNotifications.Value = true;
				ShowXpNotifications.Value = true;
				ShowSendErrorNotifications.Value = true;
				NotificationDuration.Value = 10;
				val.Value = "1";
				Log("Settings reset to defaults (reset version 1)");
			}
			_pendingEnableTracking = EnableTracking.Value;
			_pendingShowWinRates = ShowWinRates.Value;
			_pendingShowPickRates = ShowPickRates.Value;
			_pendingShowCardScore = ShowCardScore.Value;
			_pendingShowCardTags = ShowCardTags.Value;
			_pendingShowUpdateNotif = ShowCardUpdateNotifications.Value;
			_pendingShowEloNotif = ShowEloNotifications.Value;
			_pendingShowXpNotif = ShowXpNotifications.Value;
			_pendingShowSendErrorNotif = ShowSendErrorNotifications.Value;
			_pendingNotifDuration = NotificationDuration.Value;
			new Harmony("com.rounds.tracker").PatchAll();
		}

		private void Start()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			ResetSession();
			Unbound.RegisterClientSideMod("com.rounds.tracker");
			object obj = <>c.<>9__56_0;
			if (obj == null)
			{
				UnityAction val = delegate
				{
					_pendingEnableTracking = EnableTracking.Value;
					_pendingShowWinRates = ShowWinRates.Value;
					_pendingShowPickRates = ShowPickRates.Value;
					_pendingShowCardScore = ShowCardScore.Value;
					_pendingShowCardTags = ShowCardTags.Value;
					_pendingShowUpdateNotif = ShowCardUpdateNotifications.Value;
					_pendingShowEloNotif = ShowEloNotifications.Value;
					_pendingShowXpNotif = ShowXpNotifications.Value;
					_pendingShowSendErrorNotif = ShowSendErrorNotifications.Value;
					_pendingNotifDuration = NotificationDuration.Value;
				};
				<>c.<>9__56_0 = val;
				obj = (object)val;
			}
			Unbound.RegisterMenu("Rounds Tracker", (UnityAction)obj, (Action<GameObject>)BuildMenu, (GameObject)null, false);
			GameModeManager.AddHook("GameStart", (Func<IGameModeHandler, IEnumerator>)OnGameStart);
			GameModeManager.AddHook("PointEnd", (Func<IGameModeHandler, IEnumerator>)OnPointEnd);
			GameModeManager.AddHook("RoundEnd", (Func<IGameModeHandler, IEnumerator>)OnRoundEnd);
			GameModeManager.AddHook("GameEnd", (Func<IGameModeHandler, IEnumerator>)OnGameEnd);
			GameModeManager.AddHook("PickStart", (Func<IGameModeHandler, IEnumerator>)OnPickStart);
			GameModeManager.AddHook("PickEnd", (Func<IGameModeHandler, IEnumerator>)OnPickEnd);
			ApplyPatches();
			if (ShowWinRates.Value || ShowPickRates.Value || ShowCardTags.Value || ShowCardScore.Value)
			{
				WinRateCache.Load(Networking.FetchLocalElo);
			}
			else if (EnableTracking.Value)
			{
				Networking.FetchLocalElo();
			}
			Log("Rounds Tracker v1.0.22 initialized");
		}

		private void Update()
		{
			if (IsDevPreviewEnabled() && Input.GetKeyDown((KeyCode)289) && IsMainMenuOpen())
			{
				((MonoBehaviour)this).StartCoroutine(PreviewEndGameRewards());
			}
		}

		private static bool IsDevPreviewEnabled()
		{
			return false;
		}

		private static bool IsMainMenuOpen()
		{
			try
			{
				return (Object)(object)MainMenuHandler.instance != (Object)null && MainMenuHandler.instance.isOpen;
			}
			catch
			{
				return false;
			}
		}

		private IEnumerator PreviewEndGameRewards()
		{
			LogDebug("PreviewEndGameRewards: F8");
			EndGameStatsOverlay.ShowEloChange(1214f, 1231f, 17f, NotifDuration);
			yield return (object)new WaitForSecondsRealtime(0.35f);
			EndGameStatsOverlay.ShowXpProgress(new XpChangeData
			{
				xp_gained = 420,
				xp_before = 2280,
				xp_after = 2700,
				level_before = 4,
				level_after = 5
			}, NotifDuration);
		}

		private static void BuildMenu(GameObject menu)
		{
			_menuBuilding = true;
			try
			{
				TextMeshProUGUI val = null;
				MenuHandler.CreateText("Rounds Tracker Settings", menu, ref val, 60, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowWinRates, "Show Win Rates on Cards", menu, (UnityAction<bool>)delegate(bool pendingShowWinRates)
				{
					if (!_menuBuilding)
					{
						_pendingShowWinRates = pendingShowWinRates;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowPickRates, "Show Pick Rates on Cards", menu, (UnityAction<bool>)delegate(bool pendingShowPickRates)
				{
					if (!_menuBuilding)
					{
						_pendingShowPickRates = pendingShowPickRates;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowCardScore, "Show Card Power Score", menu, (UnityAction<bool>)delegate(bool pendingShowCardScore)
				{
					if (!_menuBuilding)
					{
						_pendingShowCardScore = pendingShowCardScore;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowCardTags, "Show Card Tags (Strong, Balanced, etc.)", menu, (UnityAction<bool>)delegate(bool pendingShowCardTags)
				{
					if (!_menuBuilding)
					{
						_pendingShowCardTags = pendingShowCardTags;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingEnableTracking, "Enable Tracking", menu, (UnityAction<bool>)delegate(bool pendingEnableTracking)
				{
					if (!_menuBuilding)
					{
						_pendingEnableTracking = pendingEnableTracking;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowUpdateNotif, "Card Update Notifications", menu, (UnityAction<bool>)delegate(bool pendingShowUpdateNotif)
				{
					if (!_menuBuilding)
					{
						_pendingShowUpdateNotif = pendingShowUpdateNotif;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowEloNotif, "ELO Change Notifications", menu, (UnityAction<bool>)delegate(bool pendingShowEloNotif)
				{
					if (!_menuBuilding)
					{
						_pendingShowEloNotif = pendingShowEloNotif;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowXpNotif, "XP & Achievement Notifications", menu, (UnityAction<bool>)delegate(bool pendingShowXpNotif)
				{
					if (!_menuBuilding)
					{
						_pendingShowXpNotif = pendingShowXpNotif;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				MenuHandler.CreateToggle(_pendingShowSendErrorNotif, "Send Error Notifications", menu, (UnityAction<bool>)delegate(bool pendingShowSendErrorNotif)
				{
					if (!_menuBuilding)
					{
						_pendingShowSendErrorNotif = pendingShowSendErrorNotif;
						_configDirty = true;
					}
				}, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
				Slider val2 = default(Slider);
				MenuHandler.CreateSlider("Notification Duration", menu, 50, 1f, 15f, (float)_pendingNotifDuration, (UnityAction<float>)delegate(float num)
				{
					if (!_menuBuilding)
					{
						_pendingNotifDuration = (int)num;
						_configDirty = true;
					}
				}, ref val2, true, (Color?)null, (Direction)0, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
			}
			catch (Exception ex)
			{
				LogError("BuildMenu error: " + ex.Message);
			}
			finally
			{
				_menuBuilding = false;
			}
		}

		private static void FlushConfig()
		{
			EnableTracking.Value = _pendingEnableTracking;
			ShowWinRates.Value = _pendingShowWinRates;
			ShowPickRates.Value = _pendingShowPickRates;
			ShowCardScore.Value = _pendingShowCardScore;
			ShowCardTags.Value = _pendingShowCardTags;
			ShowCardUpdateNotifications.Value = _pendingShowUpdateNotif;
			ShowEloNotifications.Value = _pendingShowEloNotif;
			ShowXpNotifications.Value = _pendingShowXpNotif;
			ShowSendErrorNotifications.Value = _pendingShowSendErrorNotif;
			NotificationDuration.Value = Math.Max(1, Math.Min(15, _pendingNotifDuration));
			_configDirty = false;
			Log("Config saved (from pending)");
		}

		private void ApplyPatches()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			try
			{
				Harmony val = new Harmony("com.rounds.tracker.cardpatches");
				Type typeFromHandle = typeof(Cards);
				MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "RemoveAllCardsFromPlayer", new Type[2]
				{
					typeof(Player),
					typeof(bool)
				}, (Type[])null);
				if (methodInfo != null)
				{
					val.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CardTracker), "RemoveAllCardsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					LogDebug("Patched RemoveAllCardsFromPlayer");
				}
				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)
				{
					val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(CardTracker), "AddCardsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					LogDebug("Patched AddCardsToPlayer");
				}
				MethodInfo methodInfo3 = AccessTools.Method(typeof(CardVisuals), "Start", (Type[])null, (Type[])null);
				if (methodInfo3 != null)
				{
					val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(CardVisualsStartPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log("Patched CardVisuals.Start for WinRate overlay (all clients)");
				}
				else
				{
					LogError("CardVisuals.Start method not found!");
				}
			}
			catch (Exception ex)
			{
				LogError("Patch error: " + ex.Message);
			}
		}

		private IEnumerator OnGameStart(IGameModeHandler gm)
		{
			if (_configDirty)
			{
				FlushConfig();
			}
			try
			{
				Room room = PhotonNetwork.CurrentRoom;
				Log(string.Format("[RT-DEBUG] Room={0}, IsOffline={1}", ((room != null) ? room.Name : null) ?? "null", PhotonNetwork.OfflineMode));
			}
			catch (Exception ex)
			{
				LogError("[RT-DEBUG] Room error: " + ex.Message);
			}
			ResetSession();
			CardTracker.ClearAll();
			_collector.Reset();
			LogDebug("Game started, session reset");
			if (EnableTracking.Value && !PhotonNetwork.OfflineMode)
			{
				Networking.BroadcastSteamId();
				yield return (object)new WaitForSeconds(2f);
				Networking.BroadcastSteamId();
				yield return (object)new WaitForSeconds(1f);
				Networking.CollectSessionPlayers();
			}
		}

		private IEnumerator OnPickStart(IGameModeHandler gm)
		{
			if (_configDirty)
			{
				FlushConfig();
			}
			int currentThreshold = GetRoundsToWinGame().GetValueOrDefault();
			if (_lastKnownThreshold > 0 && currentThreshold > _lastKnownThreshold && !_gameOverSent)
			{
				LogDebug($"PickStart: missed game-over detected (threshold {_lastKnownThreshold}→{currentThreshold}), sending retroactive report");
				_roundNumber++;
				SendRoundReport("PickStart-MissedGameOver");
				_collector.Reset();
				_gameOverSent = true;
				Networking.StartEloPropertyPoll(_roundNumber);
				Networking.StartXpPropertyPoll();
			}
			if (currentThreshold > 0)
			{
				_lastKnownThreshold = currentThreshold;
			}
			if (_gameOverSent)
			{
				_gameContinuedCount++;
				_eloShownThisGame = false;
				_xpShownThisGame = false;
				LogDebug($"PickStart: game continued (count={_gameContinuedCount})");
				_gameOverSent = false;
			}
			yield break;
		}

		private IEnumerator OnPickEnd(IGameModeHandler gm)
		{
			LogDebug($"PickEnd: picks={_collector.Picks.Count}, added={_collector.Added.Count}, removed={_collector.Removed.Count}");
			yield break;
		}

		private IEnumerator OnPointEnd(IGameModeHandler gm)
		{
			if (!EnableTracking.Value || PhotonNetwork.OfflineMode)
			{
				yield break;
			}
			Player winner = PlayerManager.instance.GetLastPlayerAlive();
			Player local = GetLocalPlayer();
			if ((Object)(object)local != (Object)null)
			{
				_collector.PointsPlayed++;
				if ((Object)(object)winner != (Object)null && winner.teamID == local.teamID)
				{
					_collector.PointsWon++;
				}
			}
			LogDebug($"PointEnd: {_collector.PointsWon}/{_collector.PointsPlayed}");
			if (IsGameOver(gm))
			{
				_roundNumber++;
				SendRoundReport("PointEnd-GameOver");
				_collector.Reset();
				_gameOverSent = true;
				Networking.StartEloPropertyPoll(_roundNumber);
				Networking.StartXpPropertyPoll();
			}
		}

		private IEnumerator OnRoundEnd(IGameModeHandler gm)
		{
			if (!EnableTracking.Value || PhotonNetwork.OfflineMode)
			{
				yield break;
			}
			if (_gameOverSent)
			{
				LogDebug("RoundEnd: already sent from PointEnd-GameOver, skip");
				yield break;
			}
			_roundNumber++;
			bool gameOver = IsGameOver(gm);
			string source = (gameOver ? "RoundEnd-GameOver" : "RoundEnd");
			SendRoundReport(source);
			_collector.Reset();
			if (gameOver)
			{
				_gameOverSent = true;
				Networking.StartEloPropertyPoll(_roundNumber);
				Networking.StartXpPropertyPoll();
			}
		}

		private IEnumerator OnGameEnd(IGameModeHandler gm)
		{
			if (EnableTracking.Value && !PhotonNetwork.OfflineMode)
			{
				if (_gameOverSent)
				{
					LogDebug("GameEnd: already sent from PointEnd-GameOver, skip");
				}
				else if (_lastRoundSent < _roundNumber + 1)
				{
					_roundNumber++;
					SendRoundReport("GameEnd");
					_collector.Reset();
					Networking.StartEloPropertyPoll(_roundNumber);
					Networking.StartXpPropertyPoll();
				}
				else
				{
					LogDebug("GameEnd: round already sent, skip");
				}
			}
			yield break;
		}

		internal static void ReportGameOverFromPatch(string source)
		{
			if (!((Object)(object)Instance == (Object)null) && EnableTracking.Value && !PhotonNetwork.OfflineMode)
			{
				((MonoBehaviour)Instance).StartCoroutine(Instance.ReportGameOverFromPatchDelayed(source));
			}
		}

		private IEnumerator ReportGameOverFromPatchDelayed(string source)
		{
			yield return null;
			if (_gameOverSent)
			{
				LogDebug(source + ": game-over report already sent, skip");
			}
			else if (_lastRoundSent < _roundNumber + 1)
			{
				_roundNumber++;
				SendRoundReport(source);
				_collector.Reset();
				_gameOverSent = true;
				Networking.StartEloPropertyPoll(_roundNumber);
				Networking.StartXpPropertyPoll();
			}
			else
			{
				LogDebug(source + ": round already sent, skip");
			}
		}

		private bool IsGameOver(IGameModeHandler gm)
		{
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (gm == null)
				{
					return false;
				}
				GameSettings settings = gm.Settings;
				if (settings == null)
				{
					return false;
				}
				int num = ((_lastKnownThreshold > 0) ? _lastKnownThreshold : (settings.ContainsKey("roundsToWinGame") ? ((int)settings["roundsToWinGame"]) : 0));
				if (num == 0)
				{
					return false;
				}
				HashSet<int> hashSet = new HashSet<int>();
				foreach (Player player in PlayerManager.instance.players)
				{
					if ((Object)(object)player == (Object)null || hashSet.Contains(player.teamID))
					{
						continue;
					}
					hashSet.Add(player.teamID);
					try
					{
						TeamScore teamScore = gm.GetTeamScore(player.teamID);
						if (teamScore.rounds >= num)
						{
							LogDebug($"IsGameOver: team {player.teamID} rounds {teamScore.rounds}/{num} (threshold snapshot={_lastKnownThreshold})");
							return true;
						}
					}
					catch
					{
					}
				}
			}
			catch (Exception ex)
			{
				LogError("IsGameOver error: " + ex.Message);
			}
			return false;
		}

		internal static bool IsLegitimateGameOver()
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				IGameModeHandler currentHandler = GameModeManager.CurrentHandler;
				if (currentHandler == null)
				{
					return false;
				}
				GameSettings settings = currentHandler.Settings;
				if (settings == null)
				{
					return false;
				}
				int num = ((_lastKnownThreshold > 0) ? _lastKnownThreshold : (settings.ContainsKey("roundsToWinGame") ? ((int)settings["roundsToWinGame"]) : 0));
				if (num == 0)
				{
					return false;
				}
				bool flag = false;
				HashSet<int> hashSet = new HashSet<int>();
				foreach (Player player in PlayerManager.instance.players)
				{
					if ((Object)(object)player == (Object)null || hashSet.Contains(player.teamID))
					{
						continue;
					}
					hashSet.Add(player.teamID);
					try
					{
						TeamScore teamScore = currentHandler.GetTeamScore(player.teamID);
						if (teamScore.rounds >= num)
						{
							flag = true;
							break;
						}
					}
					catch
					{
					}
				}
				if (!flag)
				{
					LogDebug("IsLegitimateGameOver: no team reached roundsToWin, likely disconnect");
					return false;
				}
				int count = PlayerManager.instance.players.Count;
				if (SessionPlayers.Count > 0 && count < SessionPlayers.Count)
				{
					LogDebug($"IsLegitimateGameOver: player count dropped ({count} < {SessionPlayers.Count}), likely disconnect");
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				LogError("IsLegitimateGameOver error: " + ex.Message);
				return false;
			}
		}

		private void SendRoundReport(string source)
		{
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			if (_lastRoundSent >= _roundNumber)
			{
				LogDebug($"{source}: round {_roundNumber} already sent, skip");
				return;
			}
			try
			{
				Player localPlayer = GetLocalPlayer();
				if ((Object)(object)localPlayer == (Object)null)
				{
					LogDebug(source + ": no local player");
					return;
				}
				IGameModeHandler currentHandler = GameModeManager.CurrentHandler;
				bool flag = source.Contains("GameOver") || source == "GameEnd";
				bool flag2 = IsLocalRoundWinner(currentHandler, localPlayer, flag);
				Networking.BroadcastSteamId();
				Networking.RefreshSessionPlayers();
				List<CardData> list = new List<CardData>();
				if (localPlayer.data?.currentCards != null)
				{
					foreach (CardInfo currentCard in localPlayer.data.currentCards)
					{
						if ((Object)(object)currentCard != (Object)null)
						{
							list.Add(DC.FromInfo(currentCard));
						}
					}
				}
				PlayerSkin playerSkinColors = PlayerSkinBank.GetPlayerSkinColors(localPlayer.playerID);
				string player_color = "#" + ColorUtility.ToHtmlStringRGB(playerSkinColors.color);
				List<CardData> list2 = new List<CardData>();
				HashSet<string> hashSet = new HashSet<string>();
				foreach (PickEvent pick in _collector.Picks)
				{
					foreach (CardData offered_card in pick.offered_cards)
					{
						string item = offered_card.card_name + "|" + offered_card.mod_name + "|" + offered_card.rarity;
						if (!hashSet.Contains(item))
						{
							hashSet.Add(item);
							list2.Add(offered_card);
						}
					}
				}
				RoundReport report = new RoundReport
				{
					report_id = Guid.NewGuid().ToString(),
					session_id = GetSessionId(),
					round_number = _roundNumber,
					steam_id = GetSteamId(),
					nickname = GetSteamNickname(),
					player_color = player_color,
					is_round_winner = flag2,
					points_won = _collector.PointsWon,
					points_played = _collector.PointsPlayed,
					current_cards = list,
					picks = new List<PickEvent>(_collector.Picks),
					offered_cards = list2,
					added = new List<CardData>(_collector.Added),
					removed = new List<CardData>(_collector.Removed),
					players = new List<PlayerInfo>(SessionPlayers),
					game_mode = GameModeManager.CurrentHandlerID,
					player_count = PlayerManager.instance.players.Count,
					points_to_win_round = GetPointsToWinRound().GetValueOrDefault(),
					rounds_to_win_game = (((source.Contains("GameOver") || source == "GameEnd") && _lastKnownThreshold > 0) ? _lastKnownThreshold : GetRoundsToWinGame().GetValueOrDefault()),
					game_continued_count = _gameContinuedCount,
					picks_to_choose = _picksToChoose,
					draws_per_pick_phase = _drawsPerPickPhase,
					is_game_over = flag,
					is_legitimate_game_over = IsLegitimateGameOver(),
					tracker_version = "1.0.22"
				};
				Api.Send(report);
				_lastRoundSent = _roundNumber;
				Log(string.Format("[{0}] R{1} {2} pts:{3}/{4} ", source, _roundNumber, flag2 ? "WIN" : "LOSS", _collector.PointsWon, _collector.PointsPlayed) + $"cards:{list.Count} picks:{_collector.Picks.Count} offered:{list2.Count} " + $"add:{_collector.Added.Count} rem:{_collector.Removed.Count}");
			}
			catch (Exception ex)
			{
				LogError(source + " error: " + ex.Message + "\n" + ex.StackTrace);
			}
		}

		public static Player GetLocalPlayer()
		{
			foreach (Player player in PlayerManager.instance.players)
			{
				if (player.data.view.IsMine)
				{
					return player;
				}
			}
			return null;
		}

		private bool IsLocalRoundWinner(IGameModeHandler gm, Player local, bool isGameOverReport)
		{
			if ((Object)(object)local == (Object)null)
			{
				return false;
			}
			try
			{
				int[] teamIds = ((gm != null) ? gm.GetRoundWinners() : null);
				if (TeamListContains(teamIds, local.teamID))
				{
					return true;
				}
			}
			catch (Exception ex)
			{
				LogDebug("Round winner lookup failed: " + ex.Message);
			}
			if (isGameOverReport)
			{
				try
				{
					int[] teamIds2 = ((gm != null) ? gm.GetGameWinners() : null);
					if (TeamListContains(teamIds2, local.teamID))
					{
						return true;
					}
				}
				catch (Exception ex2)
				{
					LogDebug("Game winner lookup failed: " + ex2.Message);
				}
				try
				{
					if (gm != null && IsTeamAtGameWinThreshold(gm, local.teamID))
					{
						return true;
					}
				}
				catch (Exception ex3)
				{
					LogDebug("Score winner fallback failed: " + ex3.Message);
				}
			}
			Player lastPlayerAlive = PlayerManager.instance.GetLastPlayerAlive();
			return (Object)(object)lastPlayerAlive != (Object)null && lastPlayerAlive.teamID == local.teamID;
		}

		private static bool TeamListContains(int[] teamIds, int teamId)
		{
			if (teamIds == null)
			{
				return false;
			}
			for (int i = 0; i < teamIds.Length; i++)
			{
				if (teamIds[i] == teamId)
				{
					return true;
				}
			}
			return false;
		}

		private bool IsTeamAtGameWinThreshold(IGameModeHandler gm, int teamId)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			GameSettings settings = gm.Settings;
			if (settings == null)
			{
				return false;
			}
			int num = ((_lastKnownThreshold > 0) ? _lastKnownThreshold : (settings.ContainsKey("roundsToWinGame") ? ((int)settings["roundsToWinGame"]) : 0));
			if (num <= 0)
			{
				return false;
			}
			TeamScore teamScore = gm.GetTeamScore(teamId);
			return teamScore.rounds >= num;
		}

		public static void ResetSession()
		{
			string text = null;
			try
			{
				if (!PhotonNetwork.OfflineMode && PhotonNetwork.CurrentRoom != null)
				{
					text = PhotonNetwork.CurrentRoom.Name;
				}
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(text))
			{
				if (text == _currentRoomName)
				{
					_gameIndexInRoom++;
				}
				else
				{
					_currentRoomName = text;
					_gameIndexInRoom = 0;
				}
				_sessionId = $"R_{text}_{_gameIndexInRoom}";
			}
			else
			{
				_sessionId = Guid.NewGuid().ToString();
			}
			_pickNumber = 0;
			_roundNumber = 0;
			_lastRoundSent = 0;
			_gameOverSent = false;
			_gameContinuedCount = 0;
			_lastKnownThreshold = 0;
			_picksToChoose = 0;
			_drawsPerPickPhase = 0;
			_eloShownThisGame = false;
			_xpShownThisGame = false;
			_collector.Reset();
			PlayerSteamIds.Clear();
			SessionPlayers.Clear();
			LogDebug("ResetSession: id=" + _sessionId);
		}

		public static string GetSessionId()
		{
			return _sessionId;
		}

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

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

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

		private static int CompareVersions(string a, string b)
		{
			string[] array = a.Split(new char[1] { '.' });
			string[] array2 = b.Split(new char[1] { '.' });
			int num = Math.Max(array.Length, array2.Length);
			for (int i = 0; i < num; i++)
			{
				int result;
				int num2 = ((i < array.Length) ? (int.TryParse(array[i], out result) ? result : 0) : 0);
				int result2;
				int num3 = ((i < array2.Length) ? (int.TryParse(array2[i], out result2) ? result2 : 0) : 0);
				if (num2 < num3)
				{
					return -1;
				}
				if (num2 > num3)
				{
					return 1;
				}
			}
			return 0;
		}

		public static int? GetPointsToWinRound()
		{
			try
			{
				IGameModeHandler currentHandler = GameModeManager.CurrentHandler;
				if (currentHandler == null)
				{
					return null;
				}
				GameSettings settings = currentHandler.Settings;
				if (settings != null && settings.ContainsKey("pointsToWinRound"))
				{
					return (int)settings["pointsToWinRound"];
				}
			}
			catch
			{
			}
			return null;
		}

		public static int? GetRoundsToWinGame()
		{
			try
			{
				IGameModeHandler currentHandler = GameModeManager.CurrentHandler;
				if (currentHandler == null)
				{
					return null;
				}
				GameSettings settings = currentHandler.Settings;
				if (settings != null && settings.ContainsKey("roundsToWinGame"))
				{
					return (int)settings["roundsToWinGame"];
				}
			}
			catch
			{
			}
			return null;
		}

		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)("[DBG] " + msg));
				}
			}
		}

		public static void ShowEloChangeNotification(float before, float after, float change, string source)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			if (change != 0f && ShowEloNotifications.Value)
			{
				int num = (int)Math.Round(before);
				int num2 = (int)Math.Round(change);
				int num3 = (int)Math.Round(after);
				string text = ((num2 > 0) ? "+" : "");
				try
				{
					EndGameStatsOverlay.ShowEloChange(before, after, change, NotifDuration);
				}
				catch (Exception ex)
				{
					LogError("ELO animation error: " + ex.Message);
					MessageType val = (MessageType)((num2 > 0) ? 1 : 2);
					GameMessage.Show($"ELO {num} -> {text}{num2} -> {num3}", val, NotifDuration);
				}
				Log($"ELO change ({source}): {before:F1} -> {text}{change:F1} -> {after:F1}");
			}
		}

		public static IEnumerator ShowXpNotification(XpChangeData xp, List<AchievementNotif> achievements)
		{
			if (xp == null || xp.xp_gained <= 0)
			{
				yield break;
			}
			string msg = ((xp.level_after > xp.level_before) ? $"+{xp.xp_gained} XP  Level up! Lv.{xp.level_after}" : $"+{xp.xp_gained} XP  Lv.{xp.level_after}");
			try
			{
				EndGameStatsOverlay.ShowXpProgress(xp, NotifDuration);
			}
			catch (Exception ex)
			{
				LogError("XP animation error: " + ex.Message);
				GameMessage.Show(msg, (MessageType)0, NotifDuration);
			}
			Log("[XP] " + msg);
			if (achievements == null || achievements.Count <= 0)
			{
				yield break;
			}
			yield return (object)new WaitForSeconds(3.2f);
			foreach (AchievementNotif ach in achievements)
			{
				string achMsg = string.Format(arg2: (!string.IsNullOrEmpty(ach.description)) ? (" (" + ach.description + ")") : "", format: "[Achievement] {0}! +{1} XP{2}", arg0: ach.name, arg1: ach.xp);
				GameMessage.Show(achMsg, (MessageType)1, NotifDuration);
				Log(achMsg);
			}
		}

		public static IEnumerator ShowAchievementNotifications(List<AchievementNotif> achievements)
		{
			if (achievements == null || achievements.Count == 0)
			{
				yield break;
			}
			foreach (AchievementNotif ach in achievements)
			{
				string msg = string.Format(arg2: (!string.IsNullOrEmpty(ach.description)) ? (" (" + ach.description + ")") : "", format: "[Achievement] {0}! +{1} XP{2}", arg0: ach.name, arg1: ach.xp);
				GameMessage.Show(msg, (MessageType)1, NotifDuration);
				Log(msg);
			}
		}
	}
	[HarmonyPatch]
	internal class GameOverReportPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type vanillaType = AccessTools.TypeByName("GM_ArmsRace");
			MethodInfo vanillaGameOver = AccessTools.Method(vanillaType, "GameOver", new Type[1] { typeof(int) }, (Type[])null);
			if (vanillaGameOver != null)
			{
				yield return vanillaGameOver;
			}
			Type rwfType = AccessTools.TypeByName("RWF.GameModes.RWFGameMode");
			MethodInfo rwfGameOver = AccessTools.Method(rwfType, "GameOver", new Type[1] { typeof(int[]) }, (Type[])null);
			if (rwfGameOver != null)
			{
				yield return rwfGameOver;
			}
		}

		private static void Postfix(MethodBase __originalMethod)
		{
			string text = __originalMethod?.DeclaringType?.Name ?? "Unknown";
			RT.ReportGameOverFromPatch("GameOverPatch-" + text);
		}
	}
	internal class WinRateLabel : MonoBehaviour
	{
		private bool _setup = false;

		private float _waitTime = 0f;

		private const float MaxWait = 15f;

		private const float MarginH = 20f;

		private const int TagFontSize = 72;

		private void LateUpdate()
		{
			if (_setup)
			{
				return;
			}
			if (!WinRateCache.IsLoaded)
			{
				_waitTime += Time.unscaledDeltaTime;
				if (!(_waitTime < 15f))
				{
					Object.Destroy((Object)(object)this);
				}
				return;
			}
			_setup = true;
			try
			{
				Setup();
			}
			catch (Exception ex)
			{
				RT.LogError("WinRateLabel error: " + ex.Message + "\n" + ex.StackTrace);
			}
			Object.Destroy((Object)(object)this);
		}

		private void Setup()
		{
			//IL_0500: Unknown result type (might be due to invalid IL or missing references)
			//IL_0507: Expected O, but got Unknown
			//IL_052c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0543: Unknown result type (might be due to invalid IL or missing references)
			//IL_055a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0571: Unknown result type (might be due to invalid IL or missing references)
			//IL_0588: Unknown result type (might be due to invalid IL or missing references)
			bool value = RT.ShowWinRates.Value;
			bool value2 = RT.ShowPickRates.Value;
			bool value3 = RT.ShowCardTags.Value;
			bool value4 = RT.ShowCardScore.Value;
			if (!value && !value2 && !value3 && !value4)
			{
				return;
			}
			CardInfo component = ((Component)this).GetComponent<CardInfo>();
			if ((Object)(object)component == (Object)null || string.IsNullOrEmpty(component.cardName) || component.cardName.Trim().Length == 0)
			{
				return;
			}
			string text = DC.ExtractModName(((Object)((Component)this).gameObject).name);
			string text2 = "Common";
			try
			{
				text2 = ((object)Unsafe.As<Rarity, Rarity>(ref component.rarity)/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
			}
			string text3 = WinRateCache.MakeKey(component.cardName, text, text2);
			WinRateEntry winRateEntry = WinRateCache.Get(text3);
			RT.Log("WinRateLabel LOOKUP: card=\"" + component.cardName + "\" obj=\"" + ((Object)((Component)this).gameObject).name + "\" mod=\"" + text + "\" rarity=\"" + text2 + "\" key=\"" + text3 + "\" found=" + (winRateEntry != null) + ((winRateEntry != null) ? (" wr=" + winRateEntry.round_win_rate + " tags=[" + ((winRateEntry.tags != null) ? string.Join(",", winRateEntry.tags) : "") + "]") : ""));
			if (winRateEntry == null)
			{
				List<string>