Decompiled source of RoundsTracker v1.0.6
rounds-tracker.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
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 BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using ModdingUtils.Utils; using Photon.Pun; using Photon.Realtime; 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+013720f88a4117bc5cc81421d963ae6bf92ff28a")] [assembly: AssemblyProduct("rounds-tracker")] [assembly: AssemblyTitle("rounds-tracker")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RoundsTracker { internal static class Api { [CompilerGenerated] private sealed class <SendCoroutine>d__2 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public RoundReport report; public int retry; private string <json>5__1; private string <url>5__2; private UnityWebRequest <req>5__3; private string <responseText>5__4; private int <eloIdx>5__5; private float <before>5__6; private float <change>5__7; private float <after>5__8; private int <displayBefore>5__9; private int <displayChange>5__10; private int <displayAfter>5__11; private string <sign>5__12; private GameMessageType <msgType>5__13; private List<EloPlayerData> <allElo>5__14; private Exception <ex>5__15; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SendCoroutine>d__2(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || (uint)(num - 1) <= 1u) { try { } finally { <>m__Finally1(); } } <json>5__1 = null; <url>5__2 = null; <req>5__3 = null; <responseText>5__4 = null; <sign>5__12 = null; <allElo>5__14 = null; <ex>5__15 = null; <>1__state = -2; } private bool MoveNext() { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <json>5__1 = Json.Serialize(report); <url>5__2 = RT.ApiUrlValue + "/api/rounds"; RT.LogDebug($"Sending {<json>5__1.Length} bytes to {<url>5__2}"); <req>5__3 = new UnityWebRequest(<url>5__2, "POST"); <>1__state = -3; <req>5__3.uploadHandler = (UploadHandler)new UploadHandlerRaw(Encoding.UTF8.GetBytes(<json>5__1)); <req>5__3.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); <req>5__3.SetRequestHeader("Content-Type", "application/json"); <req>5__3.timeout = 15; <>2__current = <req>5__3.SendWebRequest(); <>1__state = 1; return true; case 1: <>1__state = -3; if (<req>5__3.isNetworkError || <req>5__3.isHttpError) { RT.LogError($"Send error: {<req>5__3.error} (attempt {retry + 1})"); if (retry < 3) { <>2__current = (object)new WaitForSeconds(2f * (float)(retry + 1)); <>1__state = 2; return true; } break; } <responseText>5__4 = <req>5__3.downloadHandler.text; RT.Log("Send OK: " + <responseText>5__4.Substring(0, Math.Min(<responseText>5__4.Length, 500))); if (<responseText>5__4 != null && <responseText>5__4.Contains("\"elo\"")) { try { <eloIdx>5__5 = <responseText>5__4.IndexOf("\"elo\""); if (<eloIdx>5__5 >= 0) { <before>5__6 = ExtractFloat(<responseText>5__4, "before", <eloIdx>5__5); <change>5__7 = ExtractFloat(<responseText>5__4, "change", <eloIdx>5__5); <after>5__8 = ExtractFloat(<responseText>5__4, "after", <eloIdx>5__5); if (<change>5__7 != 0f && RT.ShowEloNotifications.Value && !RT._eloShownThisGame) { RT._eloShownThisGame = true; <displayBefore>5__9 = (int)Math.Round(<before>5__6); <displayChange>5__10 = (int)Math.Round(<change>5__7); <displayAfter>5__11 = (int)Math.Round(<after>5__8); <sign>5__12 = ((<displayChange>5__10 > 0) ? "+" : ""); <msgType>5__13 = ((<displayChange>5__10 > 0) ? GameMessageType.Success : GameMessageType.Warning); GameMessage.Show($"ELO {<displayBefore>5__9} → {<sign>5__12}{<displayChange>5__10} → {<displayAfter>5__11}", <msgType>5__13, 5f); RT.Log($"ELO change (server): {<before>5__6:F1} -> {<sign>5__12}{<change>5__7:F1} -> {<after>5__8:F1}"); <sign>5__12 = null; } } if (<responseText>5__4.Contains("\"all_elo\"")) { <allElo>5__14 = ParseAllElo(<responseText>5__4); if (<allElo>5__14.Count > 0) { Networking.BroadcastEloResults(<allElo>5__14); } <allElo>5__14 = null; } } catch (Exception ex) { <ex>5__15 = ex; RT.LogError("ELO parse error: " + <ex>5__15.Message); } } <responseText>5__4 = null; break; case 2: <>1__state = -3; ((MonoBehaviour)RT.Instance).StartCoroutine(SendCoroutine(report, retry + 1)); break; } <>m__Finally1(); <req>5__3 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<req>5__3 != null) { ((IDisposable)<req>5__3).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const int MaxRetries = 3; public static void Send(RoundReport report) { if (RT.EnableTracking.Value && !string.IsNullOrEmpty(report.steam_id)) { ((MonoBehaviour)RT.Instance).StartCoroutine(SendCoroutine(report, 0)); } } [IteratorStateMachine(typeof(<SendCoroutine>d__2))] private static IEnumerator SendCoroutine(RoundReport report, int retry) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SendCoroutine>d__2(0) { report = report, retry = retry }; } private static int ExtractInt(string text, string field, int startIdx = 0) { string text2 = "\"" + field + "\":"; int num = text.IndexOf(text2, startIdx); if (num < 0) { return 0; } int i; for (i = num + text2.Length; i < text.Length && text[i] == ' '; i++) { } int j = i; if (j < text.Length && text[j] == '-') { j++; } for (; j < text.Length && char.IsDigit(text[j]); j++) { } if (j == i) { return 0; } string s = text.Substring(i, j - i); if (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } private static float ExtractFloat(string text, string field, int startIdx = 0) { string text2 = "\"" + field + "\":"; int num = text.IndexOf(text2, startIdx); if (num < 0) { return 0f; } int i; for (i = num + text2.Length; i < text.Length && text[i] == ' '; i++) { } int j = i; if (j < text.Length && text[j] == '-') { j++; } for (; j < text.Length && char.IsDigit(text[j]); j++) { } if (j < text.Length && text[j] == '.') { for (j++; j < text.Length && char.IsDigit(text[j]); j++) { } } if (j == i) { return 0f; } string s = text.Substring(i, j - i); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return 0f; } private static List<EloPlayerData> ParseAllElo(string text) { List<EloPlayerData> list = new List<EloPlayerData>(); int num = text.IndexOf("\"all_elo\""); if (num < 0) { return list; } int num2 = text.IndexOf('[', num); if (num2 < 0) { return list; } int num3 = text.IndexOf(']', num2); if (num3 < 0) { return list; } string text2 = text.Substring(num2, num3 - num2 + 1); int num4 = 0; while (num4 < text2.Length) { int num5 = text2.IndexOf('{', num4); if (num5 < 0) { break; } int num6 = text2.IndexOf('}', num5); if (num6 < 0) { break; } string text3 = text2.Substring(num5, num6 - num5 + 1); num4 = num6 + 1; try { string text4 = ExtractString(text3, "steam_id"); if (!string.IsNullOrEmpty(text4)) { float before = ExtractFloat(text3, "before"); float after = ExtractFloat(text3, "after"); float change = ExtractFloat(text3, "change"); list.Add(new EloPlayerData { steam_id = text4, before = before, after = after, change = change }); } } catch { } } RT.Log($"ParseAllElo: parsed {list.Count} entries"); return list; } private static string ExtractString(string obj, string field) { string text = "\"" + field + "\":\""; int num = obj.IndexOf(text); if (num < 0) { return null; } int num2 = num + text.Length; int num3 = obj.IndexOf('"', num2); if (num3 < 0) { return null; } return obj.Substring(num2, num3 - num2); } } internal static class Json { public static string Serialize(RoundReport r) { StringBuilder stringBuilder = new StringBuilder(8192); stringBuilder.Append("{"); stringBuilder.Append("\"report_id\":\""); stringBuilder.Append(E(r.report_id)); stringBuilder.Append("\","); stringBuilder.Append("\"session_id\":\""); stringBuilder.Append(E(r.session_id)); stringBuilder.Append("\","); stringBuilder.Append("\"round_number\":"); stringBuilder.Append(r.round_number); stringBuilder.Append(","); stringBuilder.Append("\"steam_id\":\""); stringBuilder.Append(E(r.steam_id)); stringBuilder.Append("\","); stringBuilder.Append("\"nickname\":"); stringBuilder.Append(N(r.nickname)); stringBuilder.Append(","); stringBuilder.Append("\"player_color\":"); stringBuilder.Append(N(r.player_color)); stringBuilder.Append(","); stringBuilder.Append("\"is_round_winner\":"); stringBuilder.Append(B(r.is_round_winner)); stringBuilder.Append(","); stringBuilder.Append("\"points_won\":"); stringBuilder.Append(r.points_won); stringBuilder.Append(","); stringBuilder.Append("\"points_played\":"); stringBuilder.Append(r.points_played); stringBuilder.Append(","); stringBuilder.Append("\"current_cards\":["); for (int i = 0; i < r.current_cards.Count; i++) { if (i > 0) { stringBuilder.Append(","); } AppendCard(stringBuilder, r.current_cards[i]); } stringBuilder.Append("],"); stringBuilder.Append("\"picks\":["); for (int j = 0; j < r.picks.Count; j++) { if (j > 0) { stringBuilder.Append(","); } AppendPick(stringBuilder, r.picks[j]); } stringBuilder.Append("],"); stringBuilder.Append("\"offered_cards\":["); for (int k = 0; k < r.offered_cards.Count; k++) { if (k > 0) { stringBuilder.Append(","); } AppendCard(stringBuilder, r.offered_cards[k]); } stringBuilder.Append("],"); stringBuilder.Append("\"added\":["); for (int l = 0; l < r.added.Count; l++) { if (l > 0) { stringBuilder.Append(","); } AppendCard(stringBuilder, r.added[l]); } stringBuilder.Append("],"); stringBuilder.Append("\"removed\":["); for (int m = 0; m < r.removed.Count; m++) { if (m > 0) { stringBuilder.Append(","); } AppendCard(stringBuilder, r.removed[m]); } stringBuilder.Append("],"); stringBuilder.Append("\"game_mode\":"); stringBuilder.Append(N(r.game_mode)); stringBuilder.Append(","); stringBuilder.Append("\"player_count\":"); stringBuilder.Append(r.player_count); stringBuilder.Append(","); stringBuilder.Append("\"players\":["); for (int n = 0; n < r.players.Count; n++) { if (n > 0) { stringBuilder.Append(","); } PlayerInfo playerInfo = r.players[n]; stringBuilder.Append("{\"player_id\":"); stringBuilder.Append(playerInfo.player_id); stringBuilder.Append(",\"steam_id\":"); stringBuilder.Append(N(playerInfo.steam_id)); stringBuilder.Append(",\"nickname\":"); stringBuilder.Append(N(playerInfo.nickname)); stringBuilder.Append(",\"player_color\":"); stringBuilder.Append(N(playerInfo.player_color)); stringBuilder.Append("}"); } stringBuilder.Append("],"); stringBuilder.Append("\"points_to_win_round\":"); stringBuilder.Append(NI(r.points_to_win_round)); stringBuilder.Append(","); stringBuilder.Append("\"rounds_to_win_game\":"); stringBuilder.Append(NI(r.rounds_to_win_game)); stringBuilder.Append(","); stringBuilder.Append("\"game_continued_count\":"); stringBuilder.Append(r.game_continued_count); stringBuilder.Append(","); stringBuilder.Append("\"picks_to_choose\":"); stringBuilder.Append(r.picks_to_choose); stringBuilder.Append(","); stringBuilder.Append("\"draws_per_pick_phase\":"); stringBuilder.Append(r.draws_per_pick_phase); stringBuilder.Append(","); stringBuilder.Append("\"is_game_over\":"); stringBuilder.Append(B(r.is_game_over)); stringBuilder.Append(","); stringBuilder.Append("\"is_legitimate_game_over\":"); stringBuilder.Append(B(r.is_legitimate_game_over)); stringBuilder.Append(","); stringBuilder.Append("\"tracker_version\":\""); stringBuilder.Append(E(r.tracker_version)); stringBuilder.Append("\""); stringBuilder.Append("}"); return stringBuilder.ToString(); } private static void AppendCard(StringBuilder sb, CardData c) { sb.Append("{\"card_name\":\""); sb.Append(E(c.card_name)); sb.Append("\",\"mod_name\":"); sb.Append(N(c.mod_name)); sb.Append(",\"rarity\":"); sb.Append(N(c.rarity)); sb.Append(",\"description\":"); sb.Append(N(c.description)); sb.Append(",\"color_theme\":"); sb.Append(N(c.color_theme)); sb.Append(",\"card_color\":"); sb.Append(N(c.card_color)); sb.Append(",\"card_color_bg\":"); sb.Append(N(c.card_color_bg)); sb.Append(",\"stats\":["); if (c.stats != null) { for (int i = 0; i < c.stats.Count; i++) { if (i > 0) { sb.Append(","); } Stat stat = c.stats[i]; sb.Append("{\"stat\":\""); sb.Append(E(stat.stat)); sb.Append("\",\"amount\":\""); sb.Append(E(stat.amount)); sb.Append("\",\"positive\":"); sb.Append(B(stat.positive)); sb.Append("}"); } } sb.Append("]}"); } private static void AppendPick(StringBuilder sb, PickEvent p) { sb.Append("{\"card_name\":\""); sb.Append(E(p.card_name)); sb.Append("\",\"mod_name\":"); sb.Append(N(p.mod_name)); sb.Append(",\"rarity\":"); sb.Append(N(p.rarity)); sb.Append(",\"description\":"); sb.Append(N(p.description)); sb.Append(",\"color_theme\":"); sb.Append(N(p.color_theme)); sb.Append(",\"card_color\":"); sb.Append(N(p.card_color)); sb.Append(",\"card_color_bg\":"); sb.Append(N(p.card_color_bg)); sb.Append(",\"stats\":["); if (p.stats != null) { for (int i = 0; i < p.stats.Count; i++) { if (i > 0) { sb.Append(","); } Stat stat = p.stats[i]; sb.Append("{\"stat\":\""); sb.Append(E(stat.stat)); sb.Append("\",\"amount\":\""); sb.Append(E(stat.amount)); sb.Append("\",\"positive\":"); sb.Append(B(stat.positive)); sb.Append("}"); } } sb.Append("],\"position\":"); sb.Append(p.position); sb.Append(",\"pick_number\":"); sb.Append(p.pick_number); sb.Append(",\"offered_cards\":["); if (p.offered_cards != null) { for (int j = 0; j < p.offered_cards.Count; j++) { if (j > 0) { sb.Append(","); } AppendCard(sb, p.offered_cards[j]); } } sb.Append("]}"); } private static string N(string v) { return string.IsNullOrEmpty(v) ? "null" : ("\"" + E(v) + "\""); } private static string B(bool v) { return v ? "true" : "false"; } private static string E(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") .Replace("\r", "\\r") .Replace("\t", "\\t"); } private static string NI(int? v) { return v.HasValue ? v.Value.ToString() : "null"; } } internal static class WinRateCache { [CompilerGenerated] private sealed class <LoadCoroutine>d__14 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private float <waited>5__1; private int <lastCardCount>5__2; private float <stableTime>5__3; private float <stabilizeWaited>5__4; private string <modsParam>5__5; private string <url>5__6; private bool <serverSuccess>5__7; private int <currentCount>5__8; private HashSet<string> <modSet>5__9; private List<CardInfo> <allCards>5__10; private List<CardInfo>.Enumerator <>s__11; private CardInfo <card>5__12; private string <modName>5__13; private CardInfo[] <>s__14; private int <>s__15; private CardInfo <card>5__16; private string <modName>5__17; private ReadOnlyCollection<CardInfo> <hidden>5__18; private IEnumerator<CardInfo> <>s__19; private CardInfo <card>5__20; private string <modName>5__21; private List<string> <parts>5__22; private HashSet<string>.Enumerator <>s__23; private string <mod>5__24; private Exception <ex>5__25; private UnityWebRequest <req>5__26; private string <rawText>5__27; private Exception <ex>5__28; private string <cached>5__29; private Exception <ex>5__30; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadCoroutine>d__14(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 3) { try { } finally { <>m__Finally1(); } } <modsParam>5__5 = null; <url>5__6 = null; <modSet>5__9 = null; <allCards>5__10 = null; <>s__11 = default(List<CardInfo>.Enumerator); <card>5__12 = null; <modName>5__13 = null; <>s__14 = null; <card>5__16 = null; <modName>5__17 = null; <hidden>5__18 = null; <>s__19 = null; <card>5__20 = null; <modName>5__21 = null; <parts>5__22 = null; <>s__23 = default(HashSet<string>.Enumerator); <mod>5__24 = null; <ex>5__25 = null; <req>5__26 = null; <rawText>5__27 = null; <ex>5__28 = null; <cached>5__29 = null; <ex>5__30 = null; <>1__state = -2; } private bool MoveNext() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; _loading = true; <waited>5__1 = 0f; goto IL_00c6; case 1: <>1__state = -1; <waited>5__1 += 1f; goto IL_00c6; case 2: <>1__state = -1; <stabilizeWaited>5__4 += 1f; goto IL_0227; case 3: { <>1__state = -3; if (<req>5__26.isNetworkError || <req>5__26.isHttpError) { RT.LogError("WinRateCache load error: " + <req>5__26.error); } else { try { <rawText>5__27 = <req>5__26.downloadHandler.text; RT.Log("WinRateCache: HTTP=" + <req>5__26.responseCode + " length=" + <rawText>5__27.Length + " bytes"); ParseResponse(<rawText>5__27); if (_cache.Count > 0) { _loaded = true; <serverSuccess>5__7 = true; SaveToFile(<rawText>5__27); RT.Log("WinRateCache: updated from server, " + _cache.Count + " cards"); if (RT.ShowCardUpdateNotifications.Value) { GameMessage.Success("Card data updated (" + _cache.Count + " cards)"); } } <rawText>5__27 = null; } catch (Exception ex) { <ex>5__28 = ex; RT.LogError("WinRateCache parse error: " + <ex>5__28.Message); } } <>m__Finally1(); <req>5__26 = null; if (!<serverSuccess>5__7) { <cached>5__29 = LoadFromFile(); if (<cached>5__29 != null) { try { ParseResponse(<cached>5__29); 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)"); } } } catch (Exception ex) { <ex>5__30 = ex; RT.LogError("WinRateCache cached parse error: " + <ex>5__30.Message); } } else { RT.Log("WinRateCache: no local cache available"); if (RT.ShowCardUpdateNotifications.Value) { GameMessage.Error("Card data unavailable"); } } <cached>5__29 = null; } _loading = false; return false; } IL_00c6: if (<waited>5__1 < 45f) { try { if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null && CardChoice.instance.cards.Length > 10) { goto IL_00da; } } catch { } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } goto IL_00da; IL_023d: <modsParam>5__5 = ""; try { <modSet>5__9 = new HashSet<string>(); <modSet>5__9.Add("Vanilla"); try { <allCards>5__10 = Cards.all; if (<allCards>5__10 != null) { <>s__11 = <allCards>5__10.GetEnumerator(); try { while (<>s__11.MoveNext()) { <card>5__12 = <>s__11.Current; if (!((Object)(object)<card>5__12 == (Object)null) && !((Object)(object)((Component)<card>5__12).gameObject == (Object)null)) { <modName>5__13 = DC.ExtractModName(((Object)((Component)<card>5__12).gameObject).name); <modSet>5__9.Add(<modName>5__13); <modName>5__13 = null; <card>5__12 = null; } } } finally { ((IDisposable)<>s__11).Dispose(); } <>s__11 = default(List<CardInfo>.Enumerator); } <allCards>5__10 = null; } catch { if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null) { <>s__14 = CardChoice.instance.cards; for (<>s__15 = 0; <>s__15 < <>s__14.Length; <>s__15++) { <card>5__16 = <>s__14[<>s__15]; if (!((Object)(object)<card>5__16 == (Object)null)) { <modName>5__17 = DC.ExtractModName(((Object)((Component)<card>5__16).gameObject).name); <modSet>5__9.Add(<modName>5__17); <modName>5__17 = null; <card>5__16 = null; } } <>s__14 = null; } } try { <hidden>5__18 = Cards.instance.HiddenCards; if (<hidden>5__18 != null) { <>s__19 = <hidden>5__18.GetEnumerator(); try { while (<>s__19.MoveNext()) { <card>5__20 = <>s__19.Current; if (!((Object)(object)<card>5__20 == (Object)null) && !((Object)(object)((Component)<card>5__20).gameObject == (Object)null)) { <modName>5__21 = DC.ExtractModName(((Object)((Component)<card>5__20).gameObject).name); <modSet>5__9.Add(<modName>5__21); <modName>5__21 = null; <card>5__20 = null; } } } finally { if (<>s__19 != null) { <>s__19.Dispose(); } } <>s__19 = null; } <hidden>5__18 = null; } catch { } RT.Log("WinRateCache: detected " + <modSet>5__9.Count + " installed mods"); if (<modSet>5__9.Count > 0) { <parts>5__22 = new List<string>(); <>s__23 = <modSet>5__9.GetEnumerator(); try { while (<>s__23.MoveNext()) { <mod>5__24 = <>s__23.Current; <parts>5__22.Add(Uri.EscapeDataString(<mod>5__24)); <mod>5__24 = null; } } finally { ((IDisposable)<>s__23).Dispose(); } <>s__23 = default(HashSet<string>.Enumerator); <modsParam>5__5 = "?mods=" + string.Join(",", <parts>5__22.ToArray()); <parts>5__22 = null; } <modSet>5__9 = null; } catch (Exception ex) { <ex>5__25 = ex; RT.LogError("WinRateCache: mod detection error: " + <ex>5__25.Message); } <url>5__6 = RT.ApiUrlValue + "/api/cards/winrates" + <modsParam>5__5; RT.Log("WinRateCache: loading from " + <url>5__6); <serverSuccess>5__7 = false; <req>5__26 = UnityWebRequest.Get(<url>5__6); <>1__state = -3; <req>5__26.timeout = 15; <>2__current = <req>5__26.SendWebRequest(); <>1__state = 3; return true; IL_00da: <lastCardCount>5__2 = 0; <stableTime>5__3 = 0f; <stabilizeWaited>5__4 = 0f; goto IL_0227; IL_0227: if (<stabilizeWaited>5__4 < 45f) { <currentCount>5__8 = 0; try { if ((Object)(object)CardChoice.instance != (Object)null && CardChoice.instance.cards != null) { <currentCount>5__8 = CardChoice.instance.cards.Length; } } catch { } if (<currentCount>5__8 > 0 && <currentCount>5__8 == <lastCardCount>5__2) { <stableTime>5__3 += 1f; if (<stableTime>5__3 >= 15f) { RT.LogDebug("WinRateCache: card count stabilized at " + <currentCount>5__8 + " after " + <stabilizeWaited>5__4 + "s"); goto IL_023d; } } else { <stableTime>5__3 = 0f; <lastCardCount>5__2 = <currentCount>5__8; } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; } goto IL_023d; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<req>5__26 != null) { ((IDisposable)<req>5__26).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static Dictionary<string, WinRateEntry> _cache = new Dictionary<string, WinRateEntry>(); private static bool _loaded = false; private static bool _loading = false; public static bool IsLoaded => _loaded; private static string CacheFilePath { get { string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)RT.Instance).Info.Location); return Path.Combine(directoryName, "winrate_cache.json"); } } public static string MakeKey(string cardName, string modName, string rarity) { return cardName + "|" + (modName ?? "Vanilla") + "|" + (rarity ?? "Common"); } public static WinRateEntry Get(string key) { if (_cache.TryGetValue(key, out var value)) { return value; } return null; } public static List<string> FindSimilar(string cardName, int max) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(cardName)) { return list; } string value = cardName.ToLowerInvariant(); foreach (KeyValuePair<string, WinRateEntry> item in _cache) { if (list.Count >= max) { break; } if (item.Key.ToLowerInvariant().Contains(value)) { list.Add(item.Key + "(wr=" + item.Value.round_win_rate + ")"); } } return list; } public static void Clear() { _cache.Clear(); _loaded = false; } public static void Load() { if (!_loading) { ((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; } [IteratorStateMachine(typeof(<LoadCoroutine>d__14))] private static IEnumerator LoadCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadCoroutine>d__14(0); } private static void ParseResponse(string json) { _cache.Clear(); if (string.IsNullOrEmpty(json) || json.Length < 3) { return; } int num = 0; int num2 = 0; int num3 = 0; while (num < json.Length) { int num4 = json.IndexOf('{', num); if (num4 < 0) { break; } int num5 = json.IndexOf('}', num4); if (num5 < 0) { break; } string text = json.Substring(num4 + 1, num5 - num4 - 1); num = num5 + 1; num2++; try { string text2 = ExtractString(text, "cn"); string text3 = ExtractString(text, "mn"); string text4 = ExtractString(text, "r"); float pick_rate = ExtractFloat(text, "pr"); float round_win_rate = ExtractFloat(text, "wr"); if (num2 <= 3) { RT.Log("WinRateCache PARSE[" + num2 + "] raw: {" + ((text.Length > 200) ? text.Substring(0, 200) : text) + "}"); RT.Log("WinRateCache PARSE[" + num2 + "] => cn=\"" + (text2 ?? "NULL") + "\" mn=\"" + (text3 ?? "NULL") + "\" r=\"" + (text4 ?? "NULL") + "\" pr=" + pick_rate + " wr=" + round_win_rate); } if (!string.IsNullOrEmpty(text2)) { string key = MakeKey(text2, text3, text4); _cache[key] = new WinRateEntry { pick_rate = pick_rate, round_win_rate = round_win_rate }; } else { num3++; } } catch (Exception ex) { num3++; if (num3 <= 3) { RT.LogError("WinRateCache PARSE error #" + num2 + ": " + ex.Message); } } } RT.Log("WinRateCache ParseResponse done: " + num2 + " objects, " + _cache.Count + " cached, " + num3 + " errors"); } private static string ExtractString(string obj, string field) { string text = "\"" + field + "\":\""; int num = obj.IndexOf(text); if (num < 0) { return null; } int num2 = num + text.Length; int num3 = obj.IndexOf('"', num2); if (num3 < 0) { return null; } return obj.Substring(num2, num3 - num2); } private static float ExtractFloat(string obj, string field) { string text = "\"" + field + "\":"; int num = obj.IndexOf(text); if (num < 0) { return 0f; } int i; for (i = num + text.Length; i < obj.Length && obj[i] == ' '; i++) { } if (i < obj.Length && obj[i] == '"') { i++; } int j; for (j = i; j < obj.Length && (char.IsDigit(obj[j]) || obj[j] == '.' || obj[j] == '-'); j++) { } if (j == i) { return 0f; } string s = obj.Substring(i, j - i); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return 0f; } } public class RoundCollector { public List<PickEvent> Picks = new List<PickEvent>(); public List<CardData> Added = new List<CardData>(); public List<CardData> Removed = new List<CardData>(); public int PointsWon; public int PointsPlayed; public void Reset() { Picks.Clear(); Added.Clear(); Removed.Clear(); PointsWon = 0; PointsPlayed = 0; } public void AddPick(PickEvent pick) { Picks.Add(pick); RT.LogDebug($"Collector: Added pick {pick.card_name}, total picks: {Picks.Count}"); } public void AddAdded(CardData card) { Added.Add(card); RT.LogDebug($"Collector: Added card {card.card_name}, total added: {Added.Count}"); } public void AddRemoved(CardData card) { Removed.Add(card); RT.LogDebug($"Collector: Removed card {card.card_name}, total removed: {Removed.Count}"); } } [HarmonyPatch(typeof(CardChoice), "Pick")] internal class CardChoicePatch { private static FieldInfo SpawnedCardsField = AccessTools.Field(typeof(CardChoice), "spawnedCards"); private static FieldInfo PickerTypeField = AccessTools.Field(typeof(CardChoice), "pickerType"); private static void Postfix(CardChoice __instance, GameObject pickedCard, int ___pickrID) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 if (!RT.EnableTracking.Value || (Object)(object)pickedCard == (Object)null || PhotonNetwork.OfflineMode) { return; } try { PickerType val = (PickerType)(PickerTypeField?.GetValue(__instance)); Player val2 = null; if ((int)val == 0) { Player[] playersInTeam = PlayerManager.instance.GetPlayersInTeam(___pickrID); val2 = ((playersInTeam != null && playersInTeam.Length != 0) ? playersInTeam[0] : null); } else if (___pickrID < PlayerManager.instance.players.Count) { val2 = PlayerManager.instance.players[___pickrID]; } if ((Object)(object)val2 == (Object)null || !val2.data.view.IsMine) { return; } CardData cardData = DC.FromGO(pickedCard); if (cardData == null || string.IsNullOrEmpty(cardData.card_name)) { return; } List<GameObject> list = SpawnedCardsField?.GetValue(__instance) as List<GameObject>; List<CardData> list2 = new List<CardData>(); int num = 0; if (list != null) { for (int i = 0; i < list.Count; i++) { GameObject val3 = list[i]; if ((Object)(object)val3 == (Object)null) { continue; } CardData cardData2 = DC.FromGO(val3); if (cardData2 != null && !string.IsNullOrEmpty(cardData2.card_name)) { list2.Add(cardData2); if ((Object)(object)val3 == (Object)(object)pickedCard) { num = list2.Count - 1; } } } } PickEvent pick = new PickEvent { card_name = cardData.card_name, mod_name = cardData.mod_name, rarity = cardData.rarity, description = cardData.description, color_theme = cardData.color_theme, stats = cardData.stats, position = num, pick_number = RT.NextPickNumber(), offered_cards = list2 }; RT.Collector.AddPick(pick); if (RT._picksToChoose == 0 && list2.Count > 0) { RT._picksToChoose = list2.Count; RT.LogDebug($"Detected picks_to_choose={RT._picksToChoose}"); } RT.Log($"Pick: {cardData.card_name} (pos {num + 1} of {list2.Count})"); } catch (Exception ex) { RT.LogError("CardChoice.Pick error: " + ex.Message); } } } [HarmonyPatch(typeof(CardChoice), "StartPick")] internal class StartPickPatch { private static void Prefix(int picksToSet) { if (!RT.EnableTracking.Value || PhotonNetwork.OfflineMode) { return; } try { if (RT._drawsPerPickPhase == 0 && picksToSet > 0) { RT._drawsPerPickPhase = picksToSet; RT.LogDebug($"Detected draws_per_pick_phase={picksToSet}"); } } catch (Exception ex) { RT.LogError("StartPick patch error: " + ex.Message); } } } [HarmonyPatch] internal class AssignCardPatch { private static MethodBase TargetMethod() { Type typeFromHandle = typeof(Cards); MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "RPCA_AssignCard", new Type[7] { typeof(string), typeof(int), typeof(bool), typeof(string), typeof(float), typeof(float), typeof(bool) }, (Type[])null); if (methodInfo == null) { methodInfo = AccessTools.Method(typeFromHandle, "RPCA_AssignCard", new Type[6] { typeof(string), typeof(int), typeof(bool), typeof(string), typeof(float), typeof(float) }, (Type[])null); } return methodInfo; } private static void Postfix(string cardObjectName, int playerID, bool reassign) { if (!RT.EnableTracking.Value || reassign || PhotonNetwork.OfflineMode) { return; } try { Player val = PlayerManager.instance.players.Find((Player p) => p.playerID == playerID); if (!((Object)(object)val == (Object)null) && val.data.view.IsMine) { CardData cardData = DC.FromObjectName(cardObjectName); if (cardData != null && !string.IsNullOrEmpty(cardData.card_name)) { RT.Collector.AddAdded(cardData); RT.Log("Added: " + cardData.card_name); } } } catch (Exception ex) { RT.LogError("RPCA_AssignCard error: " + ex.Message); } } } internal static class CardTracker { private static Dictionary<string, List<CardInfo>> _savedCardsById = new Dictionary<string, List<CardInfo>>(); private static int _callCounter = 0; private static Stack<string> _callIdStack = new Stack<string>(); public static void ClearAll() { _savedCardsById.Clear(); _callCounter = 0; _callIdStack.Clear(); } public static void RemoveAllCardsPrefix(Player player, bool clearBar) { if ((Object)(object)player == (Object)null || !((Object)(object)player.data?.view != (Object)null) || !player.data.view.IsMine || !RT.EnableTracking.Value || PhotonNetwork.OfflineMode) { return; } try { _callCounter++; string text = $"{player.playerID}_{_callCounter}_{Time.frameCount}"; _callIdStack.Push(text); List<CardInfo> list = new List<CardInfo>(); List<string> list2 = new List<string>(); if (player.data?.currentCards != null) { foreach (CardInfo currentCard in player.data.currentCards) { if ((Object)(object)currentCard != (Object)null) { list.Add(currentCard); list2.Add(currentCard.cardName); } } } _savedCardsById[text] = list; if (_savedCardsById.Count > 100) { List<string> list3 = _savedCardsById.Keys.Take(_savedCardsById.Count - 50).ToList(); foreach (string item in list3) { _savedCardsById.Remove(item); } } RT.LogDebug(string.Format("RemoveAllCardsPrefix: callId={0}, stack={1}, cards=[{2}]", text, _callIdStack.Count, string.Join(", ", list2))); } catch (Exception ex) { RT.LogError("RemoveAllCardsPrefix error: " + ex.Message); } } public static void AddCardsPostfix(Player player, CardInfo[] cards, bool[] reassigns, string[] twoLetterCodes, float[] forceDisplays, float[] forceDisplayDelays, bool addToCardBar) { if ((Object)(object)player == (Object)null) { return; } string text = null; if (_callIdStack.Count > 0) { text = _callIdStack.Pop(); } if (!((Object)(object)player.data?.view != (Object)null) || !player.data.view.IsMine) { return; } List<CardInfo> value = null; if (!string.IsNullOrEmpty(text) && _savedCardsById.TryGetValue(text, out value)) { _savedCardsById.Remove(text); } List<string> list = new List<string>(); if (cards != null) { for (int i = 0; i < cards.Length; i++) { if ((Object)(object)cards[i] != (Object)null) { bool flag = reassigns != null && i < reassigns.Length && reassigns[i]; list.Add(cards[i].cardName + "(" + (flag ? "R" : "N") + ")"); } } } List<string> values = value?.Select((CardInfo c) => c.cardName).ToList() ?? new List<string>(); RT.LogDebug(string.Format("AddCardsPostfix: callId={0}, stack={1}, saved=[{2}], new=[{3}]", text, _callIdStack.Count, string.Join(", ", values), string.Join(", ", list))); if (!RT.EnableTracking.Value) { return; } if (cards == null || value == null || value.Count == 0) { RT.LogDebug("AddCardsPostfix: No data to process"); } else { if (PhotonNetwork.OfflineMode) { return; } try { Dictionary<string, int> dictionary = new Dictionary<string, int>(); for (int j = 0; j < cards.Length; j++) { if ((Object)(object)cards[j] != (Object)null && reassigns != null && j < reassigns.Length && reassigns[j]) { string name = ((Object)cards[j]).name; if (!dictionary.ContainsKey(name)) { dictionary[name] = 0; } dictionary[name]++; } } foreach (CardInfo item in value) { string name2 = ((Object)item).name; if (dictionary.ContainsKey(name2) && dictionary[name2] > 0) { dictionary[name2]--; continue; } RT.LogDebug("AddCardsPostfix: REMOVED " + item.cardName + " (name=" + name2 + ")"); GameObject gameObject = ((Component)item).gameObject; CardData cardData = DC.FromInfo(item, (gameObject != null) ? ((Object)gameObject).name : null); if (cardData != null && !string.IsNullOrEmpty(cardData.card_name)) { RT.Collector.AddRemoved(cardData); RT.Log("Removed: " + cardData.card_name); } } } catch (Exception ex) { RT.LogError("AddCardsPostfix error: " + ex.Message); } } } } internal static class DC { public static CardData FromGO(GameObject obj) { if ((Object)(object)obj == (Object)null) { return null; } CardInfo component = obj.GetComponent<CardInfo>(); return ((Object)(object)component != (Object)null) ? FromInfo(component, ((Object)obj).name) : null; } public static CardData FromObjectName(string objectName) { if (string.IsNullOrEmpty(objectName)) { return null; } try { CardInfo cardWithObjectName = Cards.instance.GetCardWithObjectName(objectName); if ((Object)(object)cardWithObjectName != (Object)null) { return FromInfo(cardWithObjectName, objectName); } } catch { } return ParseObjectName(objectName); } public static CardData ParseObjectName(string objectName) { string mod_name = ExtractModName(objectName); string card_name = objectName; if (objectName.StartsWith("__")) { string[] array = objectName.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries); if (array.Length >= 2) { card_name = array[1]; } } return new CardData { card_name = card_name, mod_name = mod_name, rarity = "Common", description = null, color_theme = null, card_color = null, stats = new List<Stat>() }; } 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_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) if ((Object)(object)info == (Object)null) { return null; } string mod_name = "Vanilla"; object obj = objectName; if (obj == null) { GameObject gameObject = ((Component)info).gameObject; obj = ((gameObject != null) ? ((Object)gameObject).name : null) ?? ""; } string text = (string)obj; if (text.StartsWith("__")) { string[] array = text.Split(new string[1] { "__" }, StringSplitOptions.RemoveEmptyEntries); if (array.Length >= 1) { mod_name = array[0]; } } List<Stat> list = new List<Stat>(); try { if (info.cardStats != null) { CardInfoStat[] cardStats = info.cardStats; foreach (CardInfoStat val in cardStats) { list.Add(new Stat { stat = (val.stat ?? ""), amount = (val.amount ?? ""), positive = val.positive }); } } } catch { } string description = null; try { description = info.cardDestription; } catch { } string rarity = "Common"; try { rarity = ((object)(Rarity)(ref info.rarity)).ToString(); } catch { } string color_theme = null; try { color_theme = ((object)(CardThemeColorType)(ref info.colorTheme)).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 { } 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, stats = list }; } } 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 List<Stat> stats = new List<Stat>(); } public class PickEvent : CardData { public int position; public int pick_number; public List<CardData> offered_cards = new List<CardData>(); } public class Stat { public string stat; public string amount; public bool positive; } public class RoundReport { public string report_id; public string session_id; public int round_number; public string steam_id; public string nickname; public string player_color; public bool is_round_winner; public int points_won; public int points_played; public List<CardData> current_cards = new List<CardData>(); public List<PickEvent> picks = new List<PickEvent>(); public List<CardData> offered_cards = new List<CardData>(); public List<CardData> added = new List<CardData>(); public List<CardData> removed = new List<CardData>(); public List<PlayerInfo> players = new List<PlayerInfo>(); public string game_mode; public int player_count; public int? points_to_win_round; public int? rounds_to_win_game; public int game_continued_count; public int picks_to_choose; public int draws_per_pick_phase; public bool is_game_over; public bool is_legitimate_game_over; public string tracker_version; } public class PlayerInfo { public int player_id; public string steam_id; public string nickname; public string player_color; } public class WinRateEntry { public float pick_rate; public float round_win_rate; } public class EloPlayerData { public string steam_id; public float before; public float after; public float change; } internal static class Networking { [CompilerGenerated] private sealed class <FetchLocalEloCoroutine>d__12 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string steamId; private string <url>5__1; private UnityWebRequest <req>5__2; private string <text>5__3; private float <elo>5__4; private int <games>5__5; private int <displayElo>5__6; private Exception <ex>5__7; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FetchLocalEloCoroutine>d__12(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <url>5__1 = null; <req>5__2 = null; <text>5__3 = null; <ex>5__7 = null; <>1__state = -2; } private bool MoveNext() { bool result; try { switch (<>1__state) { default: result = false; break; case 0: <>1__state = -1; <url>5__1 = RT.ApiUrlValue + "/api/elo/me?steam_id=" + steamId; <req>5__2 = UnityWebRequest.Get(<url>5__1); <>1__state = -3; <req>5__2.timeout = 10; <>2__current = <req>5__2.SendWebRequest(); <>1__state = 1; result = true; break; case 1: <>1__state = -3; if (<req>5__2.isNetworkError || <req>5__2.isHttpError) { RT.LogDebug("FetchLocalElo error: " + <req>5__2.error); result = false; } else { <text>5__3 = <req>5__2.downloadHandler.text; if (!string.IsNullOrEmpty(<text>5__3)) { try { <elo>5__4 = ExtractFloat(<text>5__3, "elo"); <games>5__5 = ExtractInt(<text>5__3, "elo_games"); if (<elo>5__4 > 0f) { <displayElo>5__6 = (int)Math.Round(<elo>5__4); GameMessage.Show($"ELO: {<displayElo>5__6} ({<games>5__5} games)", GameMessageType.Info, 4f); RT.Log($"Current ELO: {<elo>5__4:F1} ({<games>5__5} games)"); } } catch (Exception ex) { <ex>5__7 = ex; RT.LogError("FetchLocalElo parse: " + <ex>5__7.Message); } <text>5__3 = null; <>m__Finally1(); <req>5__2 = null; result = false; break; } result = false; } <>m__Finally1(); break; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<req>5__2 != null) { ((IDisposable)<req>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <PollEloFromRoomProperties>d__5 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private string <mySteamId>5__1; private string <myKey>5__2; private float <elapsed>5__3; private Hashtable <roomProps>5__4; private string <value>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PollEloFromRoomProperties>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <mySteamId>5__1 = null; <myKey>5__2 = null; <roomProps>5__4 = null; <value>5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <mySteamId>5__1 = RT.GetSteamId(); if (string.IsNullOrEmpty(<mySteamId>5__1)) { return false; } <myKey>5__2 = "rt_elo_" + <mySteamId>5__1; <elapsed>5__3 = 0f; RT.LogDebug("ELO poll: waiting for room prop '" + <myKey>5__2 + "'"); break; case 1: <>1__state = -1; <elapsed>5__3 += 0.5f; <roomProps>5__4 = null; break; } if (<elapsed>5__3 < 10f) { if (RT._eloShownThisGame) { return false; } if (PhotonNetwork.CurrentRoom == null) { RT.LogDebug("ELO poll: room is null, stopping"); return false; } <roomProps>5__4 = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; if (<roomProps>5__4 != null && ((Dictionary<object, object>)(object)<roomProps>5__4).ContainsKey((object)<myKey>5__2)) { <value>5__5 = <roomProps>5__4[(object)<myKey>5__2] as string; if (!string.IsNullOrEmpty(<value>5__5)) { ProcessEloPropertyValue(<value>5__5, <mySteamId>5__1); return false; } <value>5__5 = null; } <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } RT.LogDebug("ELO poll: timeout, falling back to HTTP check"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string ELO_PROP_PREFIX = "rt_elo_"; private const float ELO_POLL_INTERVAL = 0.5f; private const float ELO_POLL_TIMEOUT = 10f; public static void BroadcastEloResults(List<EloPlayerData> allElo) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown if (PhotonNetwork.OfflineMode || allElo == null || allElo.Count == 0) { return; } if (PhotonNetwork.CurrentRoom == null) { RT.LogError("BroadcastEloResults: no current room"); return; } Hashtable val = new Hashtable(); int num = 0; foreach (EloPlayerData item in allElo) { if (!string.IsNullOrEmpty(item.steam_id)) { string text = "rt_elo_" + item.steam_id; string text3 = (string)(val[(object)text] = 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)) + " = " + text3); } } if (num > 0) { PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); RT.Log($"BroadcastElo: set {num} room properties"); } } public static void StartEloPropertyPoll() { if (!PhotonNetwork.OfflineMode && RT.EnableTracking.Value && RT.ShowEloNotifications.Value && !RT._eloShownThisGame) { ((MonoBehaviour)RT.Instance).StartCoroutine(PollEloFromRoomProperties()); } } [IteratorStateMachine(typeof(<PollEloFromRoomProperties>d__5))] private static IEnumerator PollEloFromRoomProperties() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PollEloFromRoomProperties>d__5(0); } 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 (!RT._eloShownThisGame) { RT._eloShownThisGame = true; if (num3 != 0f && RT.ShowEloNotifications.Value) { int num4 = (int)Math.Round(num); int num5 = (int)Math.Round(num3); int num6 = (int)Math.Round(num2); string text = ((num5 > 0) ? "+" : ""); GameMessageType type = ((num5 > 0) ? GameMessageType.Success : GameMessageType.Warning); GameMessage.Show($"ELO {num4} > {text}{num5} > {num6}", type, 5f); RT.Log($"ELO change (room prop): {num:F1} > {text}{num3:F1} > {num2:F1}"); } } } catch (Exception ex) { RT.LogError("ELO 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)); } } [IteratorStateMachine(typeof(<FetchLocalEloCoroutine>d__12))] private static IEnumerator FetchLocalEloCoroutine(string steamId) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FetchLocalEloCoroutine>d__12(0) { steamId = steamId }; } private static float ExtractFloat(string text, string field) { string text2 = "\"" + field + "\":"; int num = text.IndexOf(text2); if (num < 0) { return 0f; } int i; for (i = num + text2.Length; i < text.Length && text[i] == ' '; i++) { } int j = i; if (j < text.Length && text[j] == '-') { j++; } for (; j < text.Length && char.IsDigit(text[j]); j++) { } if (j < text.Length && text[j] == '.') { for (j++; j < text.Length && char.IsDigit(text[j]); j++) { } } if (j == i) { return 0f; } string s = text.Substring(i, j - i); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return 0f; } private static int ExtractInt(string text, string field) { string text2 = "\"" + field + "\":"; int num = text.IndexOf(text2); if (num < 0) { return 0; } int i; for (i = num + text2.Length; i < text.Length && text[i] == ' '; i++) { } int j = i; if (j < text.Length && text[j] == '-') { j++; } for (; j < text.Length && char.IsDigit(text[j]); j++) { } if (j == i) { return 0; } string s = text.Substring(i, j - i); if (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } 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"); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.rounds.tracker", "Rounds Tracker", "1.0.6")] [BepInProcess("Rounds.exe")] public class RT : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__32_0; public static UnityAction<bool> <>9__33_0; public static UnityAction<bool> <>9__33_1; public static UnityAction<bool> <>9__33_2; public static UnityAction<bool> <>9__33_3; internal void <Start>b__32_0() { } internal void <BuildMenu>b__33_0(bool val) { ShowWinRates.Value = val; } internal void <BuildMenu>b__33_1(bool val) { EnableTracking.Value = val; } internal void <BuildMenu>b__33_2(bool val) { ShowCardUpdateNotifications.Value = val; } internal void <BuildMenu>b__33_3(bool val) { ShowEloNotifications.Value = val; } } [CompilerGenerated] private sealed class <OnGameEnd>d__40 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnGameEnd>d__40(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if (!EnableTracking.Value || PhotonNetwork.OfflineMode) { return false; } if (_gameOverSent) { LogDebug("GameEnd: already sent from PointEnd-GameOver, skip"); return false; } if (_lastRoundSent < _roundNumber + 1) { _roundNumber++; <>4__this.SendRoundReport("GameEnd"); _collector.Reset(); Networking.StartEloPropertyPoll(); } else { LogDebug("GameEnd: round already sent, skip"); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <OnGameStart>d__35 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; private Room <room>5__1; private Exception <ex>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnGameStart>d__35(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <room>5__1 = null; <ex>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; try { <room>5__1 = PhotonNetwork.CurrentRoom; Room obj = <room>5__1; Log(string.Format("[RT-DEBUG] Room={0}, IsOffline={1}", ((obj != null) ? obj.Name : null) ?? "null", PhotonNetwork.OfflineMode)); <room>5__1 = null; } catch (Exception ex) { <ex>5__2 = ex; LogError("[RT-DEBUG] Room error: " + <ex>5__2.Message); } ResetSession(); CardTracker.ClearAll(); _collector.Reset(); LogDebug("Game started, session reset"); if (!EnableTracking.Value || PhotonNetwork.OfflineMode) { return false; } Networking.BroadcastSteamId(); Networking.FetchLocalElo(); <>2__current = (object)new WaitForSeconds(2f); <>1__state = 1; return true; case 1: <>1__state = -1; Networking.BroadcastSteamId(); <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; case 2: <>1__state = -1; Networking.CollectSessionPlayers(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <OnPickEnd>d__37 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnPickEnd>d__37(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; LogDebug($"PickEnd: picks={_collector.Picks.Count}, added={_collector.Added.Count}, removed={_collector.Removed.Count}"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <OnPickStart>d__36 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnPickStart>d__36(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if (_gameOverSent) { _gameContinuedCount++; _eloShownThisGame = false; LogDebug($"PickStart: game continued (count={_gameContinuedCount})"); _gameOverSent = false; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <OnPointEnd>d__38 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; private Player <winner>5__1; private Player <local>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnPointEnd>d__38(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <winner>5__1 = null; <local>5__2 = null; <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if (!EnableTracking.Value || PhotonNetwork.OfflineMode) { return false; } <winner>5__1 = PlayerManager.instance.GetLastPlayerAlive(); <local>5__2 = GetLocalPlayer(); if ((Object)(object)<local>5__2 != (Object)null) { _collector.PointsPlayed++; if ((Object)(object)<winner>5__1 != (Object)null && <winner>5__1.data.view.IsMine) { _collector.PointsWon++; } } LogDebug($"PointEnd: {_collector.PointsWon}/{_collector.PointsPlayed}"); if (<>4__this.IsGameOver(gm)) { _roundNumber++; <>4__this.SendRoundReport("PointEnd-GameOver"); _collector.Reset(); _gameOverSent = true; Networking.StartEloPropertyPoll(); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <OnRoundEnd>d__39 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IGameModeHandler gm; public RT <>4__this; private bool <gameOver>5__1; private string <source>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <OnRoundEnd>d__39(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <source>5__2 = null; <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if (!EnableTracking.Value || PhotonNetwork.OfflineMode) { return false; } if (_gameOverSent) { LogDebug("RoundEnd: already sent from PointEnd-GameOver, skip"); return false; } _roundNumber++; <gameOver>5__1 = <>4__this.IsGameOver(gm); <source>5__2 = (<gameOver>5__1 ? "RoundEnd-GameOver" : "RoundEnd"); <>4__this.SendRoundReport(<source>5__2); _collector.Reset(); if (<gameOver>5__1) { _gameOverSent = true; Networking.StartEloPropertyPoll(); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string ModId = "com.rounds.tracker"; public const string ModName = "Rounds Tracker"; public const string Version = "1.0.6"; public const bool ENABLE_LOGS = false; public static ConfigEntry<bool> EnableTracking; public static ConfigEntry<bool> ShowWinRates; public static ConfigEntry<bool> ShowCardUpdateNotifications; public static ConfigEntry<bool> ShowEloNotifications; public static ConfigEntry<bool> DebugLogging; 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; private static int _gameContinuedCount = 0; internal static int _picksToChoose = 0; internal static int _drawsPerPickPhase = 0; internal static bool _eloShownThisGame = false; internal static Dictionary<int, string> PlayerSteamIds = new Dictionary<int, string>(); internal static List<PlayerInfo> SessionPlayers = new List<PlayerInfo>(); public static string ApiUrlValue = "http://77.246.107.74:8000"; public static RT Instance { get; private set; } public static RoundCollector Collector => _collector; private void Awake() { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) Instance = this; EnableTracking = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTracking", true, "Enable tracking"); ShowWinRates = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowWinRates", true, "Show card win rates during pick phase"); ShowCardUpdateNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowCardUpdateNotifications", true, "Show notification when card data is updated"); ShowEloNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "ShowEloNotifications", true, "Show notification when ELO rating changes"); DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", true, "Debug logging"); 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__32_0; if (obj == null) { UnityAction val = delegate { }; <>c.<>9__32_0 = val; obj = (object)val; } Unbound.RegisterMenu("Rounds Tracker", (UnityAction)obj, (Action<GameObject>)BuildMenu, (GameObject)null, false); GameModeManager.AddHook("GameStart", (Func<IGameModeHandler, IEnumerator>)OnGameStart); GameModeManager.AddHook("PointEnd", (Func<IGameModeHandler, IEnumerator>)OnPointEnd); GameModeManager.AddHook("RoundEnd", (Func<IGameModeHandler, IEnumerator>)OnRoundEnd); GameModeManager.AddHook("GameEnd", (Func<IGameModeHandler, IEnumerator>)OnGameEnd); GameModeManager.AddHook("PickStart", (Func<IGameModeHandler, IEnumerator>)OnPickStart); GameModeManager.AddHook("PickEnd", (Func<IGameModeHandler, IEnumerator>)OnPickEnd); ApplyPatches(); if (ShowWinRates.Value) { WinRateCache.Load(); } Log("Rounds Tracker v1.0.6 initialized"); } private static void BuildMenu(GameObject menu) { try { TextMeshProUGUI val2 = null; MenuHandler.CreateText("Rounds Tracker Settings", menu, ref val2, 60, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateToggle(ShowWinRates.Value, "Show Win Rates on Cards", menu, (UnityAction<bool>)delegate(bool val) { ShowWinRates.Value = val; }, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateToggle(EnableTracking.Value, "Enable Tracking", menu, (UnityAction<bool>)delegate(bool val) { EnableTracking.Value = val; }, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateToggle(ShowCardUpdateNotifications.Value, "Card Update Notifications", menu, (UnityAction<bool>)delegate(bool val) { ShowCardUpdateNotifications.Value = val; }, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateToggle(ShowEloNotifications.Value, "ELO Change Notifications", menu, (UnityAction<bool>)delegate(bool val) { ShowEloNotifications.Value = val; }, 50, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); } catch (Exception ex) { LogError("BuildMenu error: " + ex.Message); } } 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); } } [IteratorStateMachine(typeof(<OnGameStart>d__35))] private IEnumerator OnGameStart(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnGameStart>d__35(0) { <>4__this = this, gm = gm }; } [IteratorStateMachine(typeof(<OnPickStart>d__36))] private IEnumerator OnPickStart(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnPickStart>d__36(0) { <>4__this = this, gm = gm }; } [IteratorStateMachine(typeof(<OnPickEnd>d__37))] private IEnumerator OnPickEnd(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnPickEnd>d__37(0) { <>4__this = this, gm = gm }; } [IteratorStateMachine(typeof(<OnPointEnd>d__38))] private IEnumerator OnPointEnd(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnPointEnd>d__38(0) { <>4__this = this, gm = gm }; } [IteratorStateMachine(typeof(<OnRoundEnd>d__39))] private IEnumerator OnRoundEnd(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnRoundEnd>d__39(0) { <>4__this = this, gm = gm }; } [IteratorStateMachine(typeof(<OnGameEnd>d__40))] private IEnumerator OnGameEnd(IGameModeHandler gm) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <OnGameEnd>d__40(0) { <>4__this = this, gm = gm }; } private bool IsGameOver(IGameModeHandler gm) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: 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 || !settings.ContainsKey("roundsToWinGame")) { return false; } int num = (int)settings["roundsToWinGame"]; HashSet<int> hashSet = new HashSet<int>(); foreach (Player player in PlayerManager.instance.players) { if ((Object)(object)player == (Object)null || hashSet.Contains(player.teamID)) { continue; } hashSet.Add(player.teamID); try { TeamScore teamScore = gm.GetTeamScore(player.teamID); if (teamScore.rounds >= num) { LogDebug($"IsGameOver: team {player.teamID} rounds {teamScore.rounds}/{num}"); return true; } } catch { } } } catch (Exception ex) { LogError("IsGameOver error: " + ex.Message); } return false; } internal static bool IsLegitimateGameOver() { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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) try { IGameModeHandler currentHandler = GameModeManager.CurrentHandler; if (currentHandler == null) { return false; } GameSettings settings = currentHandler.Settings; if (settings == null || !settings.ContainsKey("roundsToWinGame")) { return false; } int num = (int)settings["roundsToWinGame"]; bool flag = false; HashSet<int> hashSet = new HashSet<int>(); foreach (Player player in PlayerManager.instance.players) { if ((Object)(object)player == (Object)null || hashSet.Contains(player.teamID)) { continue; } hashSet.Add(player.teamID); try { TeamScore teamScore = currentHandler.GetTeamScore(player.teamID); if (teamScore.rounds >= num) { flag = true; break; } } catch { } } if (!flag) { LogDebug("IsLegitimateGameOver: no team reached roundsToWin, likely disconnect"); return false; } int count = PlayerManager.instance.players.Count; if (SessionPlayers.Count > 0 && count < SessionPlayers.Count) { LogDebug($"IsLegitimateGameOver: player count dropped ({count} < {SessionPlayers.Count}), likely disconnect"); return false; } return true; } catch (Exception ex) { LogError("IsLegitimateGameOver error: " + ex.Message); return false; } } private void SendRoundReport(string source) { //IL_0127: 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; } Player lastPlayerAlive = PlayerManager.instance.GetLastPlayerAlive(); bool flag = (Object)(object)lastPlayerAlive != (Object)null && lastPlayerAlive.data.view.IsMine; Networking.BroadcastSteamId(); Networking.RefreshSessionPlayers(); List<CardData> list = new List<CardData>(); if (localPlayer.data?.currentCards != null) { foreach (CardInfo currentCard in localPlayer.data.currentCards) { if ((Object)(object)currentCard != (Object)null) { list.Add(DC.FromInfo(currentCard)); } } } PlayerSkin playerSkinColors = PlayerSkinBank.GetPlayerSkinColors(localPlayer.playerID); string player_color = "#" + ColorUtility.ToHtmlStringRGB(playerSkinColors.color); List<CardData> list2 = new List<CardData>(); HashSet<string> hashSet = new HashSet<string>(); foreach (PickEvent pick in _collector.Picks) { foreach (CardData offered_card in pick.offered_cards) { string item = offered_card.card_name + "|" + offered_card.mod_name + "|" + offered_card.rarity; if (!hashSet.Contains(item)) { hashSet.Add(item); list2.Add(offered_card); } } } RoundReport report = new RoundReport { report_id = Guid.NewGuid().ToString(), session_id = GetSessionId(), round_number = _roundNumber, steam_id = GetSteamId(), nickname = GetSteamNickname(), player_color = player_color, is_round_winner = flag, points_won = _collector.PointsWon, points_played = _collector.PointsPlayed, current_cards = list, picks = new List<PickEvent>(_collector.Picks), offered_cards = list2, added = new List<CardData>(_collector.Added), removed = new List<CardData>(_collector.Removed), players = new List<PlayerInfo>(SessionPlayers), game_mode = GameModeManager.CurrentHandlerID, player_count = PlayerManager.instance.players.Count, points_to_win_round = GetPointsToWinRound(), rounds_to_win_game = GetRoundsToWinGame(), game_continued_count = _gameContinuedCount, picks_to_choose = _picksToChoose, draws_per_pick_phase = _drawsPerPickPhase, is_game_over = (source.Contains("GameOver") || source == "GameEnd"), is_legitimate_game_over = IsLegitimateGameOver(), tracker_version = "1.0.6" }; Api.Send(report); _lastRoundSent = _roundNumber; Log(string.Format("[{0}] R{1} {2} pts:{3}/{4} ", source, _roundNumber, flag ? "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; } 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; _picksToChoose = 0; _drawsPerPickPhase = 0; _eloShownThisGame = 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; } 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) { bool flag = true; } public static void LogError(string msg) { bool flag = true; } public static void LogDebug(string msg) { bool flag = true; } } internal class WinRateLabel : MonoBehaviour { private bool _setup = false; private float _waitTime = 0f; private const float MaxWait = 15f; 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_0247: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Expected O, but got Unknown //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) if (!RT.ShowWinRates.Value) { return; } CardInfo component = ((Component)this).GetComponent<CardInfo>(); if ((Object)(object)component == (Object)null || string.IsNullOrEmpty(component.cardName) || component.cardName.Trim().Length == 0) { return; } string text = DC.ExtractModName(((Object)((Component)this).gameObject).name); string text2 = "Common"; try { text2 = ((object)(Rarity)(ref component.rarity)).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 + " pr=" + winRateEntry.pick_rate) : "")); if (winRateEntry == null) { List<string> list = WinRateCache.FindSimilar(component.cardName, 3); if (list.Count > 0) { RT.Log("WinRateLabel SIMILAR: " + string.Join(" | ", list)); } return; } string text4 = $"WR: {winRateEntry.round_win_rate:F1}%"; 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<CardVisuals>(); 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)) { GameObject val2 = new GameObject("RT_WinRate"); val2.transform.SetParent(val, false); RectTransform val3 = val2.AddComponent<RectTransform>(); val3.anchorMin = new Vector2(0.5f, 0f); val3.anchorMax = new Vector2(0.5f, 0f); val3.pivot = new Vector2(0.5f, 1f); val3.anchoredPosition = new Vector2(0f, -15f); val3.sizeDelta = new Vector2(400f, 80f); TextMeshProUGUI val4 = val2.AddComponent<TextMeshProUGUI>(); ((TMP_Text)val4).text = text4; ((Graphic)val4).color = new Color(1f, 1f, 1f, 0f); ((TMP_Text)val4).fontSize = 72f; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; ((TMP_Text)val4).enableWordWrapping = false; ((TMP_Text)val4).overflowMode = (TextOverflowModes)0; ((Graphic)val4).raycastTarget = false; ((TMP_Text)val4).fontStyle = (FontStyles)1; WinRateFlicker winRateFlicker = val2.AddComponent<WinRateFlicker>(); winRateFlicker.cardRoot = ((Component)this).gameObject; RT.Log("WinRateLabel CREATED: " + component.cardName + " => " + text4); } } } internal class WinRateFlicker : MonoBehaviour { public GameObject cardRoot; private TextMeshProUGUI _text; 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; private void Awake() { _text = ((Component)this).GetComponent<TextMeshProUGUI>(); } private void Update() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_text == (Object)null) { return; } if (!_cvSearched && (Object)(object)cardRoot != (Object)null) { _cardVisuals = cardRoot.GetComponentInChildren<CardVisuals>(); _cvSearched = true; } if (!_revealed) { if ((Object)(object)_cardVisuals == (Object)null || !_cardVisuals.isSelected) { ((Graphic)_text).color = new Color(1f, 1f, 1f, 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 num6; float num7; 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; num6 = Mathf.Lerp(0.1f, num5, _brightnessLerp) * _revealProgress; num7 = num4 * 0.15f * _brightnessLerp; } else { num6 = 0.1f * _revealProgress; num7 = 0f; } ((Graphic)_text).color = new Color(1f - num7 * 0.5f, 1f - num7 * 0.3f, 1f, num6); } } 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 CardVisualsStartPatch { public static void Postfix(CardVisuals __instance) { if (PhotonNetwork.OfflineMode || !RT.ShowWinRates.Value) { return; } try { GameObject gameObject = ((Component)((Component)__instance).transform.root).gameObject; PhotonView component = gameObject.GetComponent<PhotonView>(); if (!((Object)(object)component == (Object)null)) { CardInfo component2 = gameObject.GetComponent<CardInfo>(); if (!((Object)(object)component2 == (Object)null) && !((Object)(object)gameObjec