Decompiled source of GsValheimStatsEmitter v0.2.0

GsValheimStatsEmitter.dll

Decompiled 2 hours ago
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<string, double> dmg = new Dictionary<string, double>();
		}

		[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<string> cfgUrl;

		private static ConfigEntry<string> cfgToken;

		private static ConfigEntry<string> cfgWorld;

		private static ConfigEntry<int> cfgEmitInterval;

		private static readonly Dictionary<string, string> Bosses = new Dictionary<string, string>
		{
			{ "Eikthyr", "Eikthyr" },
			{ "gd_king", "gd_king" },
			{ "Bonemass", "Bonemass" },
			{ "Dragon", "Dragon" },
			{ "GoblinKing", "GoblinKing" },
			{ "SeekerQueen", "SeekerQueen" },
			{ "Fader", "Fader" }
		};

		private static readonly Dictionary<string, string> KeyLabels = new Dictionary<string, string>
		{
			{ "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<string, Dictionary<string, double>> bossDamage = new Dictionary<string, Dictionary<string, double>>();

		private readonly Dictionary<string, Dictionary<string, int>> bossKills = new Dictionary<string, Dictionary<string, int>>();

		private readonly Dictionary<string, Dictionary<string, int>> creatureKills = new Dictionary<string, Dictionary<string, int>>();

		private readonly Dictionary<string, Dictionary<string, double>> weaponDamage = new Dictionary<string, Dictionary<string, double>>();

		private readonly Dictionary<string, Dictionary<string, int>> weaponKills = new Dictionary<string, Dictionary<string, int>>();

		private readonly Dictionary<string, Dictionary<string, double>> weaponMaxHit = new Dictionary<string, Dictionary<string, double>>();

		private readonly HashSet<string> knownKeys = new HashSet<string>();

		private static readonly Dictionary<string, Fight> fights = new Dictionary<string, Fight>();

		private static readonly Dictionary<int, string> lastHitter = new Dictionary<int, string>();

		private static readonly Dictionary<int, string> lastWeapon = new Dictionary<int, string>();

		private readonly List<string> pendingBossKills = new List<string>();

		private readonly List<string> pendingMilestones = new List<string>();

		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<string>("Ingest", "Url", "http://localhost:3001/api/valheim/ingest", "gs dashboard ingest endpoint.");
			cfgToken = ((BaseUnityPlugin)this).Config.Bind<string>("Ingest", "Token", "", "Bearer token required by the gs dashboard.");
			cfgWorld = ((BaseUnityPlugin)this).Config.Bind<string>("General", "World", "vhserver3", "World name this server runs (scopes all stats in the dashboard).");
			cfgEmitInterval = ((BaseUnityPlugin)this).Config.Bind<int>("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<ZNetView>();
				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<string, double>();
				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<string, int>();
				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<string, double>();
					weaponDamage[player] = value;
				}
				value.TryGetValue(weapon, out var value2);
				value[weapon] = value2 + dmg;
				if (!weaponMaxHit.TryGetValue(player, out var value3))
				{
					value3 = new Dictionary<string, double>();
					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<string, int>();
					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<string, double> dictionary = value?.dmg ?? new Dictionary<string, double>();
			string v = value?.firstBlood;
			float num = ((value != null) ? (Time.time - value.start) : 0f);
			string text = null;
			double num2 = 0.0;
			foreach (KeyValuePair<string, double> item in dictionary)
			{
				if (item.Value > num2)
				{
					num2 = item.Value;
					text = item.Key;
				}
			}
			if (!bossKills.TryGetValue(boss, out var value2))
			{
				value2 = new Dictionary<string, int>();
				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<string> 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<string> hashSet = new HashSet<string>();
			foreach (Dictionary<string, double> value12 in bossDamage.Values)
			{
				foreach (string key in value12.Keys)
				{
					hashSet.Add(key);
				}
			}
			foreach (Dictionary<string, int> 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<string, double> value;
					double value2;
					double num = ((bossDamage.TryGetValue(item2, out value) && value.TryGetValue(item, out value2)) ? value2 : 0.0);
					Dictionary<string, int> 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<string, double> value5;
				Dictionary<string, double> dictionary = (weaponDamage.TryGetValue(item, out value5) ? value5 : null);
				Dictionary<string, int> value6;
				Dictionary<string, int> dictionary2 = (weaponKills.TryGetValue(item, out value6) ? value6 : null);
				Dictionary<string, double> value7;
				Dictionary<string, double> dictionary3 = (weaponMaxHit.TryGetValue(item, out value7) ? value7 : null);
				HashSet<string> hashSet2 = new HashSet<string>();
				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<HttpResponseMessage> 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<string, PluginInfo> 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<string> list = new List<string>();
				foreach (KeyValuePair<string, Dictionary<string, double>> item in bossDamage)
				{
					foreach (KeyValuePair<string, double> item2 in item.Value)
					{
						list.Add("bossdmg\t" + item.Key + "\t" + item2.Key + "\t" + item2.Value.ToString(CultureInfo.InvariantCulture));
					}
				}
				foreach (KeyValuePair<string, Dictionary<string, int>> bossKill in bossKills)
				{
					foreach (KeyValuePair<string, int> item3 in bossKill.Value)
					{
						list.Add($"bosskill\t{bossKill.Key}\t{item3.Key}\t{item3.Value}");
					}
				}
				foreach (KeyValuePair<string, Dictionary<string, int>> creatureKill in creatureKills)
				{
					foreach (KeyValuePair<string, int> item4 in creatureKill.Value)
					{
						list.Add($"ckill\t{creatureKill.Key}\t{item4.Key}\t{item4.Value}");
					}
				}
				foreach (KeyValuePair<string, Dictionary<string, double>> item5 in weaponDamage)
				{
					foreach (KeyValuePair<string, double> item6 in item5.Value)
					{
						list.Add("wdmg\t" + item5.Key + "\t" + item6.Key + "\t" + item6.Value.ToString(CultureInfo.InvariantCulture));
					}
				}
				foreach (KeyValuePair<string, Dictionary<string, int>> weaponKill in weaponKills)
				{
					foreach (KeyValuePair<string, int> item7 in weaponKill.Value)
					{
						list.Add($"wkill\t{weaponKill.Key}\t{item7.Key}\t{item7.Value}");
					}
				}
				foreach (KeyValuePair<string, Dictionary<string, double>> item8 in weaponMaxHit)
				{
					foreach (KeyValuePair<string, double> 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<string, double>();
								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<string, int>();
								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<string, int>();
								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<string, double>();
								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<string, int>();
								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<string, double>();
								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();
		}
	}
}