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 list = new List(); 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 list = new List(); 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 list = new List(); 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 _cache = new Dictionary(); 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 FindSimilar(string cardName, int max) { List list = new List(); if (string.IsNullOrEmpty(cardName)) { return list; } string value = cardName.ToLowerInvariant(); foreach (KeyValuePair 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 modSet = new HashSet { "Vanilla" }; try { List 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 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 parts = new List(); 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 Picks = new List(); public List Added = new List(); public List Removed = new List(); 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 list = SpawnedCardsField?.GetValue(__instance) as List; List list2 = new List(); 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> _savedCardsById = new Dictionary>(); private static int _callCounter = 0; private static Stack _callIdStack = new Stack(); 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 list = new List(); List list2 = new List(); 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 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 value = null; if (!string.IsNullOrEmpty(text) && _savedCardsById.TryGetValue(text, out value)) { _savedCardsById.Remove(text); } List list = new List(); 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 values = value?.Select((CardInfo c) => c.cardName).ToList() ?? new List(); 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 dictionary = new Dictionary(); 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(); 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(), 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 list = new List(); 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(ref info.rarity)/*cast due to .constrained prefix*/).ToString(); } catch { } string color_theme = null; try { color_theme = ((object)Unsafe.As(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 stats = new List(); public bool? allow_multiple; } [Serializable] public class PickEvent : CardData { public int position; public int pick_number; public List offered_cards = new List(); } [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 current_cards = new List(); public List picks = new List(); public List offered_cards = new List(); public List added = new List(); public List removed = new List(); public List players = new List(); 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 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)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 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)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(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 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 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 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 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 <>9__62_0; public static UnityAction <>9__62_1; public static UnityAction <>9__62_2; public static UnityAction <>9__62_3; public static UnityAction <>9__62_4; public static UnityAction <>9__62_5; public static UnityAction <>9__62_6; public static UnityAction <>9__62_7; public static UnityAction <>9__62_8; public static UnityAction <>9__62_9; internal void 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 b__62_0(bool val) { if (!_menuBuilding) { _pendingShowWinRates = val; _configDirty = true; } } internal void b__62_1(bool val) { if (!_menuBuilding) { _pendingShowPickRates = val; _configDirty = true; } } internal void b__62_2(bool val) { if (!_menuBuilding) { _pendingShowCardScore = val; _configDirty = true; } } internal void b__62_3(bool val) { if (!_menuBuilding) { _pendingShowCardTags = val; _configDirty = true; } } internal void b__62_4(bool val) { if (!_menuBuilding) { _pendingEnableTracking = val; _configDirty = true; } } internal void b__62_5(bool val) { if (!_menuBuilding) { _pendingShowUpdateNotif = val; _configDirty = true; } } internal void b__62_6(bool val) { if (!_menuBuilding) { _pendingShowEloNotif = val; _configDirty = true; } } internal void b__62_7(bool val) { if (!_menuBuilding) { _pendingShowXpNotif = val; _configDirty = true; } } internal void b__62_8(bool val) { if (!_menuBuilding) { _pendingShowSendErrorNotif = val; _configDirty = true; } } internal void 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 EnableTracking; public static ConfigEntry ShowWinRates; public static ConfigEntry ShowPickRates; public static ConfigEntry ShowCardScore; public static ConfigEntry ShowCardTags; public static ConfigEntry ShowCardUpdateNotifications; public static ConfigEntry ShowEloNotifications; public static ConfigEntry ShowXpNotifications; public static ConfigEntry ShowSendErrorNotifications; public static ConfigEntry NotificationDuration; public static ConfigEntry 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 PlayerSteamIds = new Dictionary(); internal static List SessionPlayers = new List(); 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 val = ((BaseUnityPlugin)this).Config.Bind("Meta", "LastResetVersion", "1", "Settings reset version, do not edit"); EnableTracking = ((BaseUnityPlugin)this).Config.Bind("General", "EnableTracking", true, "Enable tracking"); ShowWinRates = ((BaseUnityPlugin)this).Config.Bind("General", "ShowWinRates", false, "Show card win rates during pick phase"); ShowPickRates = ((BaseUnityPlugin)this).Config.Bind("General", "ShowPickRates", false, "Show card pick rates during pick phase"); ShowCardTags = ((BaseUnityPlugin)this).Config.Bind("General", "ShowCardTags", true, "Show card classification tags (Strong, Balanced, etc.) during pick phase"); ShowCardScore = ((BaseUnityPlugin)this).Config.Bind("General", "ShowCardScore", true, "Show card power score during pick phase"); ShowCardUpdateNotifications = ((BaseUnityPlugin)this).Config.Bind("Notifications", "ShowCardUpdateNotifications", true, "Show notification when card data is updated"); ShowEloNotifications = ((BaseUnityPlugin)this).Config.Bind("Notifications", "ShowEloNotifications", true, "Show notification when ELO rating changes"); ShowXpNotifications = ((BaseUnityPlugin)this).Config.Bind("Notifications", "ShowXpNotifications", true, "Show XP and achievement notifications at game end"); ShowSendErrorNotifications = ((BaseUnityPlugin)this).Config.Bind("Notifications", "ShowSendErrorNotifications", true, "Show notification when data fails to send after all retries"); NotificationDuration = ((BaseUnityPlugin)this).Config.Bind("Notifications", "NotificationDuration", 10, "Duration (seconds) of in-game notifications. Range: 1-15."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind("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)BuildMenu, (GameObject)null, false); GameModeManager.AddHook("GameStart", (Func)OnGameStart); GameModeManager.AddHook("PointEnd", (Func)OnPointEnd); GameModeManager.AddHook("RoundEnd", (Func)OnRoundEnd); GameModeManager.AddHook("GameEnd", (Func)OnGameEnd); GameModeManager.AddHook("PickStart", (Func)OnPickStart); GameModeManager.AddHook("PickEnd", (Func)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)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)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)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)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)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)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)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)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)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)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 hashSet = new HashSet(); 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 hashSet = new HashSet(); 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 list = new List(); 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 list2 = new List(); HashSet hashSet = new HashSet(); 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(_collector.Picks), offered_cards = list2, added = new List(_collector.Added), removed = new List(_collector.Removed), players = new List(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 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 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 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 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(); 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(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 list = WinRateCache.FindSimilar(component.cardName, 3); if (list.Count > 0) { RT.Log("WinRateLabel SIMILAR: " + string.Join(" | ", list)); } return; } Transform val = null; if ((Object)(object)component.cardBase != (Object)null) { val = component.cardBase.transform.Find("Canvas"); } if ((Object)(object)val == (Object)null) { CardVisuals componentInChildren = ((Component)this).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { val = ((Component)componentInChildren).transform.Find("Canvas"); if ((Object)(object)val == (Object)null) { val = ((Component)componentInChildren).transform; } } } if ((Object)(object)val == (Object)null) { return; } string text4 = ""; if (value) { string text5 = ((int)Math.Round(winRateEntry.round_win_rate)).ToString(); text4 = "WR: " + text5 + "%"; } if (value2) { string text6 = ((int)Math.Round(winRateEntry.pick_rate)).ToString(); string text7 = "PR: " + text6 + "%"; text4 = ((text4.Length <= 0) ? text7 : (text4 + ", " + text7)); } if (value4 && !value3 && winRateEntry.score.HasValue) { string text8 = ((int)Math.Round(winRateEntry.score.Value)).ToString(); string text9 = "PS: " + text8 + ""; text4 = ((text4.Length <= 0) ? text9 : (text4 + ", " + text9)); } if (value3 && winRateEntry.tags != null) { bool flag = false; string[] tags = winRateEntry.tags; foreach (string text10 in tags) { if (!string.IsNullOrEmpty(text10)) { string tagHex = GetTagHex(text10); string text11 = text10; if (!flag && value4 && winRateEntry.score.HasValue && IsPowerTag(text10)) { string text12 = ((int)Math.Round(winRateEntry.score.Value)).ToString(); text11 = text10 + " (" + text12 + ")"; flag = true; } text4 = ((text4.Length <= 0) ? (text4 + "" + text11 + "") : (text4 + ", " + text11 + "")); } } } if (!string.IsNullOrEmpty(text4)) { GameObject val2 = new GameObject("RT_WinRate"); val2.transform.SetParent(val, false); RectTransform val3 = val2.AddComponent(); val3.anchorMin = new Vector2(0f, 0f); val3.anchorMax = new Vector2(1f, 0f); val3.pivot = new Vector2(0.5f, 1f); val3.anchoredPosition = new Vector2(0f, -15f); val3.sizeDelta = new Vector2(-40f, 200f); WinRateFlicker winRateFlicker = val2.AddComponent(); winRateFlicker.cardRoot = ((Component)this).gameObject; TextMeshProUGUI val4 = val2.AddComponent(); ((TMP_Text)val4).text = text4; ((TMP_Text)val4).alpha = 0f; ((TMP_Text)val4).fontSize = 72f; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; ((TMP_Text)val4).enableWordWrapping = true; ((TMP_Text)val4).overflowMode = (TextOverflowModes)0; ((Graphic)val4).raycastTarget = false; ((TMP_Text)val4).fontStyle = (FontStyles)1; ((TMP_Text)val4).richText = true; winRateFlicker.AddTagElement(val4, null, 0f); RT.Log("WinRateLabel CREATED: " + component.cardName + " text=" + text4); } } private static bool IsPowerTag(string tag) { int result; switch (tag) { default: result = ((tag == "Nerf") ? 1 : 0); break; case "Buff": case "Weak": case "Balanced": case "Strong": result = 1; break; } return (byte)result != 0; } private static string GetTagHex(string tag) { return tag switch { "Nerf" => "#ff6b6b", "Strong" => "#ff9640", "Balanced" => "#73cf40", "Weak" => "#3dd9d4", "Buff" => "#6699ff", "Niche" => "#c77aff", "Trap" => "#ff5cad", "New" => "#f5b830", "Stable" => "#a8e030", _ => "#c8c8c8", }; } } internal class WinRateFlicker : MonoBehaviour { private struct TagElement { public TextMeshProUGUI text; public Image bg; public float bgBaseAlpha; } public GameObject cardRoot; private readonly List _elements = new List(); private float _time; private bool _revealed = false; private float _revealProgress = 0f; private const float RevealDuration = 0.3f; private const float DimAlpha = 0.1f; private const float BrightAlphaMin = 0.6f; private const float BrightAlphaMax = 1f; private const float TransitionSpeed = 3.5f; private float _brightnessLerp = 0f; private CardVisuals _cardVisuals; private bool _cvSearched = false; public bool IsRevealed => _revealed; public float RevealProgress => _revealProgress; public void AddTagElement(TextMeshProUGUI text, Image bg, float bgBaseAlpha) { _elements.Add(new TagElement { text = text, bg = bg, bgBaseAlpha = bgBaseAlpha }); } private void Update() { if (_elements.Count == 0) { return; } if (!_cvSearched && (Object)(object)cardRoot != (Object)null) { _cardVisuals = cardRoot.GetComponentInChildren(); _cvSearched = true; } if (!_revealed) { if ((Object)(object)_cardVisuals == (Object)null || !_cardVisuals.isSelected) { SetAllAlpha(0f); return; } _revealed = true; _revealProgress = 0f; _brightnessLerp = 1f; } if (_revealProgress < 1f) { _revealProgress += Time.unscaledDeltaTime / 0.3f; if (_revealProgress > 1f) { _revealProgress = 1f; } } _time += Time.unscaledDeltaTime; float num = (((Object)(object)_cardVisuals != (Object)null && _cardVisuals.isSelected) ? 1f : 0f); _brightnessLerp = Mathf.MoveTowards(_brightnessLerp, num, 3.5f * Time.unscaledDeltaTime); float allAlpha; if (_brightnessLerp > 0.01f) { float num2 = Mathf.Sin(_time * 3.7f) * 0.5f + 0.5f; float num3 = Mathf.Sin(_time * 5.3f) * 0.5f + 0.5f; float num4 = num2 * 0.6f + num3 * 0.4f; float num5 = 0.6f + num4 * 0.39999998f; allAlpha = Mathf.Lerp(0.1f, num5, _brightnessLerp) * _revealProgress; } else { allAlpha = 0.1f * _revealProgress; } SetAllAlpha(allAlpha); } private void SetAllAlpha(float alpha) { //IL_004d: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) foreach (TagElement element in _elements) { if ((Object)(object)element.text != (Object)null) { ((TMP_Text)element.text).alpha = alpha; } if ((Object)(object)element.bg != (Object)null) { Color color = ((Graphic)element.bg).color; ((Graphic)element.bg).color = new Color(color.r, color.g, color.b, element.bgBaseAlpha * alpha); } } } } internal class WinRateParticleCtrl : MonoBehaviour { public WinRateFlicker flicker; public GeneralParticleSystem ps; private bool _started = false; private void Update() { if (!_started && !((Object)(object)flicker == (Object)null) && !((Object)(object)ps == (Object)null) && flicker.IsRevealed) { _started = true; ps.StartLooping(); } } } internal static class EndGameStatsOverlay { private static GameObject _canvasGO; private static Transform _container; private static EndGameRewardsPanel _activePanel; public const float XpAnimationLeadTime = 3.2f; public static void ShowEloChange(float before, float after, float change, float duration) { EnsureCanvas(); EndGameRewardsPanel orCreatePanel = GetOrCreatePanel(Mathf.Max(4f, duration)); orCreatePanel.SetElo(before, after, change); } public static void ShowXpProgress(XpChangeData xp, float duration) { if (xp != null) { EnsureCanvas(); EndGameRewardsPanel orCreatePanel = GetOrCreatePanel(Mathf.Max(4f, duration)); orCreatePanel.SetXp(xp); } } private static void EnsureCanvas() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_canvasGO != (Object)null)) { _canvasGO = new GameObject("RT_EndGameStatsCanvas"); Object.DontDestroyOnLoad((Object)(object)_canvasGO); Canvas val = _canvasGO.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 9998; CanvasScaler val2 = _canvasGO.AddComponent(); val2.uiScaleMode = (ScaleMode)1; val2.referenceResolution = new Vector2(1920f, 1080f); val2.matchWidthOrHeight = 0.5f; _canvasGO.AddComponent(); GameObject val3 = new GameObject("StatsContainer"); val3.transform.SetParent(_canvasGO.transform, false); RectTransform val4 = val3.AddComponent(); val4.anchorMin = new Vector2(1f, 0f); val4.anchorMax = new Vector2(1f, 0f); val4.pivot = new Vector2(1f, 0f); val4.anchoredPosition = new Vector2(-36f, 36f); val4.sizeDelta = new Vector2(480f, 190f); _container = val3.transform; } } private static EndGameRewardsPanel GetOrCreatePanel(float duration) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_activePanel != (Object)null) { _activePanel.Extend(duration); return _activePanel; } GameObject panel = new GameObject("RT_MatchRewards"); panel.transform.SetParent(_container, false); RectTransform val = panel.AddComponent(); val.anchorMin = new Vector2(1f, 0f); val.anchorMax = new Vector2(1f, 0f); val.pivot = new Vector2(1f, 0f); val.anchoredPosition = Vector2.zero; val.sizeDelta = new Vector2(460f, 172f); Image val2 = panel.AddComponent(); ((Graphic)val2).color = new Color(0.055f, 0.065f, 0.085f, 0.96f); ((Graphic)val2).raycastTarget = false; CanvasGroup val3 = panel.AddComponent(); val3.alpha = 0f; val3.interactable = false; val3.blocksRaycasts = false; _activePanel = panel.AddComponent(); _activePanel.Init(duration, delegate { if ((Object)(object)_activePanel != (Object)null && (Object)(object)((Component)_activePanel).gameObject == (Object)(object)panel) { _activePanel = null; } }); return _activePanel; } } internal class EndGameRewardsPanel : MonoBehaviour { private CanvasGroup _group; private RectTransform _rt; private TextMeshProUGUI _title; private TextMeshProUGUI _eloLabel; private TextMeshProUGUI _eloValue; private TextMeshProUGUI _eloDelta; private TextMeshProUGUI _xpLabel; private TextMeshProUGUI _xpValue; private TextMeshProUGUI _xpSub; private RectTransform _eloLabelRT; private RectTransform _eloValueRT; private RectTransform _eloDeltaRT; private RectTransform _xpLabelRT; private RectTransform _xpValueRT; private RectTransform _xpSubRT; private RectTransform _barRT; private RectTransform _fillRT; private Image _barBg; private Image _barFill; private Action _onClosed; private float _timer; private float _duration; private bool _hasElo; private float _eloBefore; private float _eloAfter; private float _eloChange; private float _eloStartTime; private XpChangeData _xp; private float _xpStartTime; private const float IntroTime = 0.25f; private const float FadeTime = 0.35f; private const float EloAnimTime = 1.9f; private const float XpAnimTime = 2.8f; private const int LevelK = 300; public void Init(float duration, Action onClosed) { _group = ((Component)this).GetComponent(); _rt = ((Component)this).GetComponent(); _duration = duration; _onClosed = onClosed; Build(); } public void Extend(float duration) { float num = _duration - _timer; if (num < duration * 0.65f) { _duration = _timer + duration * 0.65f; } } public void SetElo(float before, float after, float change) { _hasElo = true; _eloBefore = before; _eloAfter = after; _eloChange = change; _eloStartTime = _timer; SetEloAlpha(1f); ReflowRows(); UpdateElo(0f); } public void SetXp(XpChangeData xp) { if (xp != null) { _xp = xp; _xpStartTime = _timer; SetXpAlpha(1f); ReflowRows(); UpdateXp(0f); } } private void Build() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Expected O, but got Unknown //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_046b: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Unknown result type (might be due to invalid IL or missing references) _title = AddText("Title", 20f, 10f, 416f, 24f, 14, (TextAlignmentOptions)4097); ((TMP_Text)_title).text = "Rounds Tracker: Match Rewards"; ((Graphic)_title).color = new Color(0.82f, 0.88f, 1f, 1f); ((TMP_Text)_title).fontStyle = (FontStyles)1; ((TMP_Text)_title).overflowMode = (TextOverflowModes)0; _eloLabel = AddText("EloLabel", 22f, 44f, 64f, 30f, 14, (TextAlignmentOptions)4097); _eloLabelRT = ((Component)_eloLabel).GetComponent(); ((TMP_Text)_eloLabel).text = "ELO"; ((Graphic)_eloLabel).color = new Color(0.55f, 0.6f, 0.7f, 1f); _eloValue = AddText("EloValue", 92f, 38f, 170f, 42f, 30, (TextAlignmentOptions)4097); _eloValueRT = ((Component)_eloValue).GetComponent(); ((TMP_Text)_eloValue).fontStyle = (FontStyles)1; ((Graphic)_eloValue).color = Color.white; _eloDelta = AddText("EloDelta", 286f, 38f, 150f, 42f, 22, (TextAlignmentOptions)4100); _eloDeltaRT = ((Component)_eloDelta).GetComponent(); _eloDeltaRT.pivot = new Vector2(1f, 1f); _eloDeltaRT.anchoredPosition = new Vector2(436f, -38f); ((TMP_Text)_eloDelta).fontStyle = (FontStyles)1; _xpLabel = AddText("XpLabel", 22f, 89f, 64f, 32f, 14, (TextAlignmentOptions)4097); _xpLabelRT = ((Component)_xpLabel).GetComponent(); ((TMP_Text)_xpLabel).text = "LEVEL"; ((Graphic)_xpLabel).color = new Color(0.55f, 0.6f, 0.7f, 1f); _xpValue = AddText("XpValue", 92f, 86f, 160f, 38f, 24, (TextAlignmentOptions)4097); _xpValueRT = ((Component)_xpValue).GetComponent(); ((TMP_Text)_xpValue).fontStyle = (FontStyles)1; ((Graphic)_xpValue).color = Color.white; _xpSub = AddText("XpSub", 256f, 86f, 180f, 38f, 16, (TextAlignmentOptions)4100); _xpSubRT = ((Component)_xpSub).GetComponent(); _xpSubRT.pivot = new Vector2(1f, 1f); _xpSubRT.anchoredPosition = new Vector2(436f, -86f); ((Graphic)_xpSub).color = new Color(0.67f, 0.81f, 1f, 1f); ((TMP_Text)_xpSub).fontStyle = (FontStyles)1; GameObject val = AddRect("XpBar", 92f, 124f, 344f, 16f); _barRT = val.GetComponent(); _barBg = val.AddComponent(); ((Graphic)_barBg).color = new Color(0.02f, 0.03f, 0.05f, 0.95f); ((Graphic)_barBg).raycastTarget = false; GameObject val2 = new GameObject("Fill"); val2.transform.SetParent(val.transform, false); _fillRT = val2.AddComponent(); _fillRT.anchorMin = new Vector2(0f, 0f); _fillRT.anchorMax = new Vector2(0f, 1f); _fillRT.pivot = new Vector2(0f, 0.5f); _fillRT.offsetMin = Vector2.zero; _fillRT.offsetMax = Vector2.zero; _barFill = val2.AddComponent(); ((Graphic)_barFill).color = new Color(0.32f, 0.61f, 1f, 1f); ((Graphic)_barFill).raycastTarget = false; SetEloAlpha(0f); SetXpAlpha(0f); ((TMP_Text)_eloValue).text = ""; ((TMP_Text)_eloDelta).text = ""; ((TMP_Text)_xpValue).text = ""; ((TMP_Text)_xpSub).text = ""; _fillRT.anchorMax = new Vector2(0f, 1f); } private TextMeshProUGUI AddText(string name, float x, float y, float w, float h, int fontSize, TextAlignmentOptions align) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) GameObject val = AddRect(name, x, y, w, h); TextMeshProUGUI val2 = val.AddComponent(); ((TMP_Text)val2).fontSize = fontSize; ((TMP_Text)val2).alignment = align; ((TMP_Text)val2).enableWordWrapping = false; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; ((Graphic)val2).raycastTarget = false; return val2; } private GameObject AddRect(string name, float x, float y, float w, float h) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(((Component)this).transform, false); RectTransform val2 = val.AddComponent(); val2.anchorMin = new Vector2(0f, 1f); val2.anchorMax = new Vector2(0f, 1f); val2.pivot = new Vector2(0f, 1f); val2.anchoredPosition = new Vector2(x, 0f - y); val2.sizeDelta = new Vector2(w, h); return val; } private void Update() { //IL_005f: 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_00c2: Unknown result type (might be due to invalid IL or missing references) _timer += Time.unscaledDeltaTime; float num = 1f; if (_timer < 0.25f) { float t = Mathf.Clamp01(_timer / 0.25f); num = Smooth(t); _rt.anchoredPosition = new Vector2(Mathf.Lerp(24f, 0f, num), 0f); } else if (_timer > _duration - 0.35f) { float num2 = Mathf.Clamp01((_timer - (_duration - 0.35f)) / 0.35f); num = 1f - num2; _rt.anchoredPosition = new Vector2(num2 * 16f, 0f); } else { _rt.anchoredPosition = Vector2.zero; } if ((Object)(object)_group != (Object)null) { _group.alpha = num; } if (_hasElo) { UpdateElo(CounterEase(Mathf.Clamp01((_timer - _eloStartTime) / 1.9f))); } if (_xp != null) { UpdateXp(CounterEase(Mathf.Clamp01((_timer - _xpStartTime) / 2.8f))); } if (_timer >= _duration) { _onClosed?.Invoke(); Object.Destroy((Object)(object)((Component)this).gameObject); } } private void UpdateElo(float t) { //IL_006d: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) int num = (int)Math.Round(Mathf.Lerp(_eloBefore, _eloAfter, t)); int num2 = (int)Math.Round(_eloChange); string text = ((num2 > 0) ? "+" : ""); Color color = ((num2 >= 0) ? new Color(0.55f, 0.93f, 0.55f, 1f) : new Color(0.97f, 0.84f, 0.35f, 1f)); ((TMP_Text)_eloValue).text = num.ToString(); ((TMP_Text)_eloDelta).text = text + num2; ((Graphic)_eloDelta).color = color; if ((Object)(object)_eloValueRT != (Object)null) { float num3 = 0f; if (t > 0.72f && t < 1f) { float num4 = Mathf.Clamp01((t - 0.72f) / 0.28f); num3 = Mathf.Sin(num4 * MathF.PI) * 0.08f; } ((Transform)_eloValueRT).localScale = Vector3.one * (1f + num3); } if ((Object)(object)_eloDeltaRT != (Object)null) { float num5 = 0f; if (t > 0.72f && t < 1f) { float num6 = Mathf.Clamp01((t - 0.72f) / 0.28f); num5 = Mathf.Sin(num6 * MathF.PI) * 0.16f; } ((Transform)_eloDeltaRT).localScale = Vector3.one * (1f + num5); } } private void UpdateXp(float t) { //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) if (_xp == null) { return; } bool flag = _xp.level_after > _xp.level_before; int num10; float num11; if (flag) { int num = LevelThreshold(_xp.level_before); int num2 = LevelThreshold(_xp.level_before + 1); int num3 = Math.Max(1, num2 - num); float num4 = Mathf.Clamp01((float)(_xp.xp_before - num) / (float)num3); int num5 = LevelThreshold(_xp.level_after); int num6 = LevelThreshold(_xp.level_after + 1); int num7 = Math.Max(1, num6 - num5); float num8 = Mathf.Clamp01((float)(_xp.xp_after - num5) / (float)num7); if (t < 0.58f) { float num9 = CounterEase(t / 0.58f); num10 = _xp.level_before; num11 = Mathf.Lerp(num4, 1f, num9); } else if (t < 0.7f) { float num12 = Mathf.Clamp01((t - 0.58f) / 0.12f); num10 = _xp.level_after; num11 = 1f - num12; } else { float num13 = CounterEase((t - 0.7f) / 0.3f); num10 = _xp.level_after; num11 = Mathf.Lerp(0f, num8, num13); } } else { int num14 = (int)Math.Round(Mathf.Lerp((float)_xp.xp_before, (float)_xp.xp_after, t)); num10 = _xp.level_after; int num15 = LevelThreshold(num10); int num16 = LevelThreshold(num10 + 1); int num17 = Math.Max(1, num16 - num15); int num18 = Math.Max(0, num14 - num15); if (num18 > num17) { num18 = num17; } num11 = Mathf.Clamp01((float)num18 / (float)num17); } if ((Object)(object)_fillRT != (Object)null) { _fillRT.anchorMax = new Vector2(num11, 1f); } ((TMP_Text)_xpValue).text = "Lvl " + num10; int num19 = (int)Math.Round(Mathf.Lerp(0f, (float)_xp.xp_gained, t)); if (flag) { ((TMP_Text)_xpSub).text = "LEVEL UP +" + num19 + " XP"; } else { ((TMP_Text)_xpSub).text = "+" + num19 + " XP"; } if ((Object)(object)_xpValueRT != (Object)null) { float num20 = 0f; if (flag && t > 0.58f && t < 0.82f) { float num21 = Mathf.Clamp01((t - 0.58f) / 0.24f); num20 = Mathf.Sin(num21 * MathF.PI) * 0.12f; } ((Transform)_xpValueRT).localScale = Vector3.one * (1f + num20); } } private void ReflowRows() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) bool flag = _xp != null; if ((Object)(object)_rt != (Object)null) { _rt.sizeDelta = new Vector2(_rt.sizeDelta.x, (_hasElo && flag) ? 172f : 132f); } if (_hasElo && flag) { MoveEloRow(44f, 38f, 38f); MoveXpRow(89f, 86f, 86f, 124f); } else if (_hasElo) { MoveEloRow(64f, 58f, 58f); } else if (flag) { MoveXpRow(53f, 50f, 50f, 96f); } } private void MoveEloRow(float labelY, float valueY, float deltaY) { SetY(_eloLabelRT, labelY); SetY(_eloValueRT, valueY); SetY(_eloDeltaRT, deltaY); } private void MoveXpRow(float labelY, float valueY, float subY, float barY) { SetY(_xpLabelRT, labelY); SetY(_xpValueRT, valueY); SetY(_xpSubRT, subY); SetY(_barRT, barY); } private static void SetY(RectTransform rt, float y) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)rt == (Object)null)) { rt.anchoredPosition = new Vector2(rt.anchoredPosition.x, 0f - y); } } private void SetEloAlpha(float alpha) { SetTextAlpha(_eloLabel, alpha); SetTextAlpha(_eloValue, alpha); SetTextAlpha(_eloDelta, alpha); } private void SetXpAlpha(float alpha) { SetTextAlpha(_xpLabel, alpha); SetTextAlpha(_xpValue, alpha); SetTextAlpha(_xpSub, alpha); SetImageAlpha(_barBg, (alpha > 0f) ? 0.95f : 0f); SetImageAlpha(_barFill, alpha); } private static void SetTextAlpha(TextMeshProUGUI text, float alpha) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)text == (Object)null)) { Color color = ((Graphic)text).color; ((Graphic)text).color = new Color(color.r, color.g, color.b, alpha); } } private static void SetImageAlpha(Image image, float alpha) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)image == (Object)null)) { Color color = ((Graphic)image).color; ((Graphic)image).color = new Color(color.r, color.g, color.b, alpha); } } private static int CalcLevel(int xp) { int result = 1; for (int i = 1; i < 1000 && xp >= LevelThreshold(i + 1); i++) { result = i + 1; } return result; } private static int LevelThreshold(int level) { int num = Math.Max(0, level - 1); return 300 * num * (num + 1) / 2; } private static float Smooth(float t) { t = Mathf.Clamp01(t); return t * t * (3f - 2f * t); } private static float CounterEase(float t) { t = Mathf.Clamp01(t); return t * t * t * (t * (6f * t - 15f) + 10f); } } internal static class CardVisualsStartPatch { public static void Postfix(CardVisuals __instance) { if (!RT.ShowWinRates.Value && !RT.ShowPickRates.Value && !RT.ShowCardTags.Value && !RT.ShowCardScore.Value) { return; } try { GameObject gameObject = ((Component)((Component)__instance).transform.root).gameObject; PhotonView component = gameObject.GetComponent(); if (!((Object)(object)component == (Object)null)) { CardInfo component2 = gameObject.GetComponent(); if (!((Object)(object)component2 == (Object)null) && !((Object)(object)gameObject.GetComponent() != (Object)null)) { gameObject.AddComponent(); } } } catch (Exception ex) { RT.LogError("CardVisualsStartPatch error: " + ex.Message); } } } }