using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("GsValheimStatsEmitter")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0")] [assembly: AssemblyProduct("GsValheimStatsEmitter")] [assembly: AssemblyTitle("GsValheimStatsEmitter")] [assembly: AssemblyVersion("0.2.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 GsValheimStatsEmitter { [BepInPlugin("net.cproudlock.gsvalheimstats", "GsValheimStatsEmitter", "0.2.0")] public class Plugin : BaseUnityPlugin { private class Fight { public float start; public string firstBlood; public Dictionary dmg = new Dictionary(); } [HarmonyPatch(typeof(Character), "Damage")] private static class Patch_Damage { private static void Postfix(Character __instance, HitData hit) { if (!IsServer() || (Object)(object)__instance == (Object)null || hit == null) { return; } try { string text = ResolvePlayerName(hit); if (Time.unscaledTime - lastDbg > 2f) { lastDbg = Time.unscaledTime; ManualLogSource log = Log; if (log != null) { object[] obj = new object[4] { CleanName(__instance), ((Object)(object)hit.GetAttacker() == (Object)null) ? "null" : ((object)hit.GetAttacker()).GetType().Name, text ?? "?", null }; ZNet instance = ZNet.instance; obj[3] = ((instance == null) ? null : instance.GetPeers()?.Count).GetValueOrDefault(-1); log.LogInfo((object)string.Format("[gs-dbg] dmg victim={0} attacker={1} resolved={2} peers={3}", obj)); } } if (text == null || IsPlayerCharacter(__instance)) { return; } float totalDamage = hit.GetTotalDamage(); int instanceID = ((Object)__instance).GetInstanceID(); lastHitter[instanceID] = text; string text2 = SafeSkill(hit); lastWeapon[instanceID] = text2; Instance?.AddWeaponDamage(text, text2, totalDamage); string key = CleanName(__instance); if (Bosses.TryGetValue(key, out var value)) { if (!fights.TryGetValue(value, out var value2)) { value2 = new Fight { start = Time.time, firstBlood = text }; fights[value] = value2; } value2.dmg.TryGetValue(text, out var value3); value2.dmg[text] = value3 + (double)totalDamage; Instance?.AddBossDamage(value, text, totalDamage); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[gs] dmg patch: " + ex.Message)); } } } } [HarmonyPatch(typeof(Character), "OnDeath")] private static class Patch_OnDeath { private static void Postfix(Character __instance) { if (!IsServer() || (Object)(object)__instance == (Object)null) { return; } try { if (IsPlayerCharacter(__instance)) { return; } int instanceID = ((Object)__instance).GetInstanceID(); lastHitter.TryGetValue(instanceID, out var value); lastWeapon.TryGetValue(instanceID, out var value2); string text = CleanName(__instance); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[gs-dbg] death victim=" + text + " killer=" + (value ?? "?") + " weapon=" + (value2 ?? "?"))); } if (Bosses.TryGetValue(text, out var value3)) { if (!string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(value2)) { Instance?.AddWeaponKill(value, value2); } Instance?.OnBossKilled(value3); } else if (!string.IsNullOrEmpty(value)) { Instance?.AddCreatureKill(value, text); if (!string.IsNullOrEmpty(value2)) { Instance?.AddWeaponKill(value, value2); } } lastHitter.Remove(instanceID); lastWeapon.Remove(instanceID); } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[gs] death patch: " + ex.Message)); } } } } public const string GUID = "net.cproudlock.gsvalheimstats"; public const string NAME = "GsValheimStatsEmitter"; public const string VERSION = "0.2.0"; internal static ManualLogSource Log; private static readonly HttpClient http = new HttpClient { Timeout = TimeSpan.FromSeconds(8.0) }; private static ConfigEntry cfgUrl; private static ConfigEntry cfgToken; private static ConfigEntry cfgWorld; private static ConfigEntry cfgEmitInterval; private static readonly Dictionary Bosses = new Dictionary { { "Eikthyr", "Eikthyr" }, { "gd_king", "gd_king" }, { "Bonemass", "Bonemass" }, { "Dragon", "Dragon" }, { "GoblinKing", "GoblinKing" }, { "SeekerQueen", "SeekerQueen" }, { "Fader", "Fader" } }; private static readonly Dictionary KeyLabels = new Dictionary { { "defeated_eikthyr", "Eikthyr defeated" }, { "defeated_gdking", "The Elder defeated" }, { "defeated_bonemass", "Bonemass defeated" }, { "defeated_dragon", "Moder defeated" }, { "defeated_goblinking", "Yagluth defeated" }, { "defeated_queen", "The Queen defeated" }, { "defeated_fader", "Fader defeated" }, { "KilledTroll", "First troll slain" }, { "killed_surtling", "First surtling slain" }, { "killed_bonemass", "Bonemass slain" }, { "Hildir1", "Hildir's bounty: Howling Cavern" }, { "Hildir2", "Hildir's bounty: Plains Fortress" }, { "Hildir3", "Hildir's bounty: Crypt" } }; private readonly Dictionary> bossDamage = new Dictionary>(); private readonly Dictionary> bossKills = new Dictionary>(); private readonly Dictionary> creatureKills = new Dictionary>(); private readonly Dictionary> weaponDamage = new Dictionary>(); private readonly Dictionary> weaponKills = new Dictionary>(); private readonly Dictionary> weaponMaxHit = new Dictionary>(); private readonly HashSet knownKeys = new HashSet(); private static readonly Dictionary fights = new Dictionary(); private static readonly Dictionary lastHitter = new Dictionary(); private static readonly Dictionary lastWeapon = new Dictionary(); private readonly List pendingBossKills = new List(); private readonly List pendingMilestones = new List(); private static Plugin Instance; private string statePath; private string serverStartedUtc; private float lastEmit; private float lastKeyPoll; private static float lastDbg; private void Awake() { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; cfgUrl = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Url", "http://localhost:3001/api/valheim/ingest", "gs dashboard ingest endpoint."); cfgToken = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Token", "", "Bearer token required by the gs dashboard."); cfgWorld = ((BaseUnityPlugin)this).Config.Bind("General", "World", "vhserver3", "World name this server runs (scopes all stats in the dashboard)."); cfgEmitInterval = ((BaseUnityPlugin)this).Config.Bind("General", "EmitIntervalSeconds", 120, "How often to POST the cumulative snapshot."); serverStartedUtc = DateTime.UtcNow.ToString("O"); statePath = Path.Combine(Paths.ConfigPath, "net.cproudlock.gsvalheimstats.state.tsv"); LoadState(); Harmony val = new Harmony("net.cproudlock.gsvalheimstats"); try { MethodInfo methodInfo = AccessTools.Method(typeof(Character), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Character), "OnDeath", (Type[])null, (Type[])null); if (methodInfo != null) { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Patch_Damage), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null) { val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Patch_OnDeath), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.LogInfo((object)$"[gs] patched Character.RPC_Damage={methodInfo != null} Character.OnDeath={methodInfo2 != null}"); } catch (Exception arg) { Log.LogError((object)$"[gs] patch error: {arg}"); } Log.LogInfo((object)("GsValheimStatsEmitter 0.2.0 loaded, posting to " + cfgUrl.Value + " (token " + (string.IsNullOrEmpty(cfgToken.Value) ? "MISSING" : "set") + ")")); } private static bool IsServer() { try { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); } catch { return false; } } private static string CleanName(Character c) { if ((Object)(object)c == (Object)null) { return "unknown"; } string text = (((Object)(object)((Component)c).gameObject != (Object)null) ? ((Object)((Component)c).gameObject).name : ((Object)c).name); if (string.IsNullOrEmpty(text)) { return "unknown"; } int num = text.IndexOf('('); if (num > 0) { text = text.Substring(0, num); } return text.Trim(); } private static string ResolvePlayerName(HitData hit) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) try { Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { string playerName = val.GetPlayerName(); if (!string.IsNullOrEmpty(playerName) && playerName != "...") { return playerName; } } } catch { } try { ZDOID attacker2 = hit.m_attacker; if ((Object)(object)ZNet.instance != (Object)null && attacker2 != ZDOID.None) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_characterID == attacker2 && !string.IsNullOrEmpty(peer.m_playerName)) { return peer.m_playerName; } } } } catch { } return null; } private static bool IsPlayerCharacter(Character c) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) try { if (c is Player) { return true; } ZNetView component = ((Component)c).GetComponent(); ZDO val = (((Object)(object)component != (Object)null) ? component.GetZDO() : null); if (val != null && (Object)(object)ZNet.instance != (Object)null) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_characterID == val.m_uid) { return true; } } } } catch { } return false; } private void AddBossDamage(string boss, string player, double dmg) { if (!bossDamage.TryGetValue(boss, out var value)) { value = new Dictionary(); bossDamage[boss] = value; } value.TryGetValue(player, out var value2); value[player] = value2 + dmg; } private void AddCreatureKill(string player, string creature) { if (!creatureKills.TryGetValue(player, out var value)) { value = new Dictionary(); creatureKills[player] = value; } value.TryGetValue(creature, out var value2); value[creature] = value2 + 1; } private static string SafeSkill(HitData hit) { try { string text = ((object)(SkillType)(ref hit.m_skill)).ToString(); if (string.IsNullOrEmpty(text) || text == "None" || text == "All") { return "Unarmed"; } return text; } catch { return "Unarmed"; } } private void AddWeaponDamage(string player, string weapon, double dmg) { if (!string.IsNullOrEmpty(weapon)) { if (!weaponDamage.TryGetValue(player, out var value)) { value = new Dictionary(); weaponDamage[player] = value; } value.TryGetValue(weapon, out var value2); value[weapon] = value2 + dmg; if (!weaponMaxHit.TryGetValue(player, out var value3)) { value3 = new Dictionary(); weaponMaxHit[player] = value3; } value3.TryGetValue(weapon, out var value4); if (dmg > value4) { value3[weapon] = dmg; } } } private void AddWeaponKill(string player, string weapon) { if (!string.IsNullOrEmpty(weapon)) { if (!weaponKills.TryGetValue(player, out var value)) { value = new Dictionary(); weaponKills[player] = value; } value.TryGetValue(weapon, out var value2); value[weapon] = value2 + 1; } } private void OnBossKilled(string boss) { fights.TryGetValue(boss, out var value); Dictionary dictionary = value?.dmg ?? new Dictionary(); string v = value?.firstBlood; float num = ((value != null) ? (Time.time - value.start) : 0f); string text = null; double num2 = 0.0; foreach (KeyValuePair item in dictionary) { if (item.Value > num2) { num2 = item.Value; text = item.Key; } } if (!bossKills.TryGetValue(boss, out var value2)) { value2 = new Dictionary(); bossKills[boss] = value2; } foreach (string key in dictionary.Keys) { value2.TryGetValue(key, out var value3); value2[key] = value3 + 1; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); J(stringBuilder, "boss", boss); C(stringBuilder); stringBuilder.Append("\"fightSec\":").Append(Mathf.RoundToInt(num)); C(stringBuilder); J(stringBuilder, "firstBlood", v); C(stringBuilder); J(stringBuilder, "topDamagePlayer", text); C(stringBuilder); stringBuilder.Append("\"topDamage\":").Append(Math.Round(num2)); C(stringBuilder); stringBuilder.Append("\"participants\":").Append(dictionary.Count); C(stringBuilder); J(stringBuilder, "tsUtc", DateTime.UtcNow.ToString("O")); stringBuilder.Append('}'); pendingBossKills.Add(stringBuilder.ToString()); fights.Remove(boss); Log.LogInfo((object)$"[gs] boss killed: {boss} (MVP {text}, {dictionary.Count} participant(s))"); SaveState(); } private void Update() { if (!IsServer()) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime - lastKeyPoll > 10f) { lastKeyPoll = unscaledTime; try { List list = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetGlobalKeys() : null); if (list != null) { foreach (string item in list) { if (!string.IsNullOrEmpty(item) && !knownKeys.Contains(item)) { knownKeys.Add(item); string value; string text = (KeyLabels.TryGetValue(item, out value) ? value : item); string v = (item.StartsWith("defeated_") ? "boss" : (item.StartsWith("Hildir") ? "bounty" : "progression")); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); J(stringBuilder, "key", item); C(stringBuilder); J(stringBuilder, "label", text); C(stringBuilder); J(stringBuilder, "kind", v); C(stringBuilder); J(stringBuilder, "tsUtc", DateTime.UtcNow.ToString("O")); stringBuilder.Append('}'); pendingMilestones.Add(stringBuilder.ToString()); Log.LogInfo((object)("[gs] milestone: " + item + " (" + text + ")")); } } } } catch (Exception ex) { Log.LogWarning((object)("[gs] key poll: " + ex.Message)); } } if (!(unscaledTime - lastEmit > (float)Mathf.Max(15, cfgEmitInterval.Value))) { return; } lastEmit = unscaledTime; try { Emit(); } catch (Exception arg) { Log.LogError((object)$"[gs] emit: {arg}"); } } private void Emit() { HashSet hashSet = new HashSet(); foreach (Dictionary value12 in bossDamage.Values) { foreach (string key in value12.Keys) { hashSet.Add(key); } } foreach (Dictionary value13 in bossKills.Values) { foreach (string key2 in value13.Keys) { hashSet.Add(key2); } } foreach (string key3 in creatureKills.Keys) { hashSet.Add(key3); } foreach (string key4 in weaponDamage.Keys) { hashSet.Add(key4); } foreach (string key5 in weaponKills.Keys) { hashSet.Add(key5); } StringBuilder stringBuilder = new StringBuilder(2048); stringBuilder.Append('{'); J(stringBuilder, "schemaVersion", 1); C(stringBuilder); J(stringBuilder, "game", "valheim"); C(stringBuilder); J(stringBuilder, "source", "server"); C(stringBuilder); J(stringBuilder, "world", cfgWorld.Value); C(stringBuilder); J(stringBuilder, "hostName", "Proudlock VH Server 3"); C(stringBuilder); J(stringBuilder, "serverStartedAtUtc", serverStartedUtc); C(stringBuilder); J(stringBuilder, "emittedAtUtc", DateTime.UtcNow.ToString("O")); C(stringBuilder); J(stringBuilder, "snapshotIdLocal", Guid.NewGuid().ToString("N")); C(stringBuilder); stringBuilder.Append("\"players\":["); bool flag = true; foreach (string item in hashSet) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{'); J(stringBuilder, "name", item); C(stringBuilder); stringBuilder.Append("\"boss\":["); bool flag2 = true; foreach (string item2 in Bosses.Values.Distinct()) { Dictionary value; double value2; double num = ((bossDamage.TryGetValue(item2, out value) && value.TryGetValue(item, out value2)) ? value2 : 0.0); Dictionary value3; int value4; int num2 = ((bossKills.TryGetValue(item2, out value3) && value3.TryGetValue(item, out value4)) ? value4 : 0); if (!(num <= 0.0) || num2 > 0) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append('{'); J(stringBuilder, "boss", item2); C(stringBuilder); stringBuilder.Append("\"kills\":").Append(num2); C(stringBuilder); stringBuilder.Append("\"damageDealt\":").Append(Math.Round(num)); stringBuilder.Append('}'); } } stringBuilder.Append("],"); stringBuilder.Append("\"weapons\":["); bool flag3 = true; Dictionary value5; Dictionary dictionary = (weaponDamage.TryGetValue(item, out value5) ? value5 : null); Dictionary value6; Dictionary dictionary2 = (weaponKills.TryGetValue(item, out value6) ? value6 : null); Dictionary value7; Dictionary dictionary3 = (weaponMaxHit.TryGetValue(item, out value7) ? value7 : null); HashSet hashSet2 = new HashSet(); if (dictionary != null) { foreach (string key6 in dictionary.Keys) { hashSet2.Add(key6); } } if (dictionary2 != null) { foreach (string key7 in dictionary2.Keys) { hashSet2.Add(key7); } } if (dictionary3 != null) { foreach (string key8 in dictionary3.Keys) { hashSet2.Add(key8); } } foreach (string item3 in hashSet2) { double value8; double a = ((dictionary != null && dictionary.TryGetValue(item3, out value8)) ? value8 : 0.0); int value9; int value10 = ((dictionary2 != null && dictionary2.TryGetValue(item3, out value9)) ? value9 : 0); double value11; double a2 = ((dictionary3 != null && dictionary3.TryGetValue(item3, out value11)) ? value11 : 0.0); if (!flag3) { stringBuilder.Append(','); } flag3 = false; stringBuilder.Append('{'); J(stringBuilder, "weapon", item3); C(stringBuilder); stringBuilder.Append("\"damageDealt\":").Append(Math.Round(a)); C(stringBuilder); stringBuilder.Append("\"kills\":").Append(value10); C(stringBuilder); stringBuilder.Append("\"hardestHit\":").Append(Math.Round(a2)); stringBuilder.Append('}'); } stringBuilder.Append("]"); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"bossKillEvents\":[").Append(string.Join(",", pendingBossKills)).Append("],"); stringBuilder.Append("\"milestones\":[").Append(string.Join(",", pendingMilestones)).Append("],"); stringBuilder.Append("\"mods\":["); AppendMods(stringBuilder); stringBuilder.Append(']'); stringBuilder.Append('}'); Post(stringBuilder.ToString()); } private void Post(string json) { try { StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, cfgUrl.Value) { Content = content }; if (!string.IsNullOrEmpty(cfgToken.Value)) { httpRequestMessage.Headers.TryAddWithoutValidation("Authorization", "Bearer " + cfgToken.Value); } http.SendAsync(httpRequestMessage).ContinueWith(delegate(Task t) { if (t.IsFaulted) { Log.LogWarning((object)("[gs] post failed: " + t.Exception?.GetBaseException().Message)); } else { int statusCode = (int)t.Result.StatusCode; if (statusCode >= 200 && statusCode < 300) { pendingBossKills.Clear(); pendingMilestones.Clear(); } Log.LogInfo((object)$"[gs] ingest status: {statusCode}"); } }); } catch (Exception ex) { Log.LogWarning((object)("[gs] post: " + ex.Message)); } } private void AppendMods(StringBuilder sb) { bool flag = true; try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (((value != null) ? value.Metadata : null) == null) { continue; } string text = ""; string v = ""; try { string directoryName = Path.GetDirectoryName(value.Location ?? ""); if (!string.IsNullOrEmpty(directoryName)) { text = Path.GetFileName(directoryName); int num = text.IndexOf('-'); if (num > 0) { v = text.Substring(0, num); } } } catch { } if (!flag) { sb.Append(','); } flag = false; sb.Append('{'); J(sb, "guid", value.Metadata.GUID ?? ""); C(sb); J(sb, "name", value.Metadata.Name ?? ""); C(sb); J(sb, "version", value.Metadata.Version?.ToString() ?? ""); C(sb); J(sb, "author", v); C(sb); J(sb, "folder", text); sb.Append('}'); } } catch (Exception ex) { Log.LogWarning((object)("[gs] mods: " + ex.Message)); } } private void SaveState() { try { List list = new List(); foreach (KeyValuePair> item in bossDamage) { foreach (KeyValuePair item2 in item.Value) { list.Add("bossdmg\t" + item.Key + "\t" + item2.Key + "\t" + item2.Value.ToString(CultureInfo.InvariantCulture)); } } foreach (KeyValuePair> bossKill in bossKills) { foreach (KeyValuePair item3 in bossKill.Value) { list.Add($"bosskill\t{bossKill.Key}\t{item3.Key}\t{item3.Value}"); } } foreach (KeyValuePair> creatureKill in creatureKills) { foreach (KeyValuePair item4 in creatureKill.Value) { list.Add($"ckill\t{creatureKill.Key}\t{item4.Key}\t{item4.Value}"); } } foreach (KeyValuePair> item5 in weaponDamage) { foreach (KeyValuePair item6 in item5.Value) { list.Add("wdmg\t" + item5.Key + "\t" + item6.Key + "\t" + item6.Value.ToString(CultureInfo.InvariantCulture)); } } foreach (KeyValuePair> weaponKill in weaponKills) { foreach (KeyValuePair item7 in weaponKill.Value) { list.Add($"wkill\t{weaponKill.Key}\t{item7.Key}\t{item7.Value}"); } } foreach (KeyValuePair> item8 in weaponMaxHit) { foreach (KeyValuePair item9 in item8.Value) { list.Add("wmax\t" + item8.Key + "\t" + item9.Key + "\t" + item9.Value.ToString(CultureInfo.InvariantCulture)); } } foreach (string knownKey in knownKeys) { list.Add("gkey\t" + knownKey); } File.WriteAllLines(statePath, list); } catch (Exception ex) { Log.LogWarning((object)("[gs] save state: " + ex.Message)); } } private void LoadState() { try { if (!File.Exists(statePath)) { return; } string[] array = File.ReadAllLines(statePath); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('\t'); if (array2.Length == 0) { continue; } switch (array2[0]) { case "bossdmg": if (array2.Length == 4) { if (!bossDamage.TryGetValue(array2[1], out var value5)) { value5 = new Dictionary(); bossDamage[array2[1]] = value5; } value5[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } break; case "bosskill": if (array2.Length == 4) { if (!bossKills.TryGetValue(array2[1], out var value3)) { value3 = new Dictionary(); bossKills[array2[1]] = value3; } value3[array2[2]] = int.Parse(array2[3]); } break; case "ckill": if (array2.Length == 4) { if (!creatureKills.TryGetValue(array2[1], out var value)) { value = new Dictionary(); creatureKills[array2[1]] = value; } value[array2[2]] = int.Parse(array2[3]); } break; case "wdmg": if (array2.Length == 4) { if (!weaponDamage.TryGetValue(array2[1], out var value4)) { value4 = new Dictionary(); weaponDamage[array2[1]] = value4; } value4[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } break; case "wkill": if (array2.Length == 4) { if (!weaponKills.TryGetValue(array2[1], out var value6)) { value6 = new Dictionary(); weaponKills[array2[1]] = value6; } value6[array2[2]] = int.Parse(array2[3]); } break; case "wmax": if (array2.Length == 4) { if (!weaponMaxHit.TryGetValue(array2[1], out var value2)) { value2 = new Dictionary(); weaponMaxHit[array2[1]] = value2; } value2[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } break; case "gkey": if (array2.Length == 2) { knownKeys.Add(array2[1]); } break; } } Log.LogInfo((object)$"[gs] loaded state: {bossDamage.Count} boss(es), {creatureKills.Count} player(s), {knownKeys.Count} key(s)"); } catch (Exception ex) { Log.LogWarning((object)("[gs] load state: " + ex.Message)); } } private static void J(StringBuilder sb, string k, string v) { sb.Append('"').Append(Esc(k)).Append("\":"); if (v == null) { sb.Append("null"); } else { sb.Append('"').Append(Esc(v)).Append('"'); } } private static void J(StringBuilder sb, string k, int v) { sb.Append('"').Append(Esc(k)).Append("\":") .Append(v); } private static void C(StringBuilder sb) { sb.Append(','); } private static string Esc(string s) { if (string.IsNullOrEmpty(s)) { return s ?? ""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } }