using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; 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.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("GsValheimStatsClient")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.8.0")] [assembly: AssemblyInformationalVersion("0.2.8")] [assembly: AssemblyProduct("GsValheimStatsClient")] [assembly: AssemblyTitle("GsValheimStatsClient")] [assembly: AssemblyVersion("0.2.8.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GsValheimStatsClient { [BepInPlugin("net.cproudlock.gsvalheimstatsclient", "GsValheimStatsClient", "0.2.9")] public class Plugin : BaseUnityPlugin { private class Combat { public readonly Dictionary wDmg = new Dictionary(); public readonly Dictionary wKill = new Dictionary(); public readonly Dictionary wMax = new Dictionary(); public readonly Dictionary wSwing = new Dictionary(); public readonly Dictionary iDmg = new Dictionary(); public readonly Dictionary iKill = new Dictionary(); public readonly Dictionary iMax = new Dictionary(); public readonly Dictionary bDmg = new Dictionary(); public readonly Dictionary bEng = new Dictionary(); public readonly Dictionary bLast = new Dictionary(); public string swW; public float swT; public double swD; public void Weapon(string w, double dmg) { wDmg.TryGetValue(w, out var value); wDmg[w] = value + dmg; wMax.TryGetValue(w, out var value2); if (dmg > value2) { wMax[w] = dmg; } float time = Time.time; if (w == swW && time - swT <= 0.2f) { swD += dmg; } else { swW = w; swD = dmg; } swT = time; wSwing.TryGetValue(w, out var value3); if (swD > value3) { wSwing[w] = swD; } } public void Item(string it, double dmg) { iDmg.TryGetValue(it, out var value); iDmg[it] = value + dmg; iMax.TryGetValue(it, out var value2); if (dmg > value2) { iMax[it] = dmg; } } public void Boss(string b, double dmg) { bDmg.TryGetValue(b, out var value); bDmg[b] = value + dmg; float time = Time.time; if (bLast.TryGetValue(b, out var value2) && time - value2 <= 15f) { bEng.TryGetValue(b, out var value3); bEng[b] = value3 + (double)(time - value2); } bLast[b] = time; } } private class BossFight { public float start; public string firstBlood; public readonly Dictionary dmg = new Dictionary(); } [HarmonyPatch(typeof(Character), "RPC_Damage")] private static class Patch_LocalDamage { private static void Prefix(Character __instance, HitData hit) { chAttacker = null; try { if ((Object)(object)__instance == (Object)null || hit == null || (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { return; } ZNetView component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsOwner()) { Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (!((Object)(object)val == (Object)null)) { chAttacker = val; chVictimId = ((Object)__instance).GetInstanceID(); chWeapon = SafeSkill(hit); chItem = ItemOf((Character)(object)val); string text = CleanName(__instance); chBossKey = ((text != null && BossNames.Contains(text)) ? text : null); } } } catch { chAttacker = null; } } private static void Postfix(Character __instance, HitData hit) { try { if ((Object)(object)__instance == (Object)null || hit == null) { return; } if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null && !(attacker is Player)) { lastKiller = CleanName(attacker); lastHitType = null; } else { lastHitType = ((object)Unsafe.As(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString(); lastKiller = null; } return; } ZNetView component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsOwner()) { return; } double num = hit.GetTotalDamage(); string text = CleanName(__instance); bool flag = text != null && BossNames.Contains(text); Character attacker2 = hit.GetAttacker(); Player val = (Player)(object)((attacker2 is Player) ? attacker2 : null); if ((Object)(object)val == (Object)null) { if (flag && num > 0.0) { Instance?.AddBossSelfDamage(text, num); } chAttacker = null; return; } int instanceID = ((Object)__instance).GetInstanceID(); string text2 = SafeSkill(hit); lastWeaponByVictim[instanceID] = text2; if ((Object)(object)val == (Object)(object)Player.m_localPlayer) { lastAttackerByVictim.Remove(instanceID); Instance?.AddWeaponDamage(text2, num); string text3 = CurrentWeaponItem(); lastItemByVictim[instanceID] = text3; Instance?.AddItemDamage(text3, num); if (flag) { Instance?.AddBossDamage(text, num); Instance?.RecordBossFight(text, val.GetPlayerName(), num); } return; } string playerName = val.GetPlayerName(); if (!string.IsNullOrEmpty(playerName) && !(playerName == "...")) { lastAttackerByVictim[instanceID] = playerName; string text4 = ItemOf((Character)(object)val); lastItemByVictim[instanceID] = text4; Combat combat = Instance?.Other(playerName); combat?.Weapon(text2, num); combat?.Item(text4, num); if (flag) { combat?.Boss(text, num); Instance?.RecordBossFight(text, playerName, num); } } } catch { } finally { chAttacker = null; } } } [HarmonyPatch(typeof(Character), "OnDeath")] private static class Patch_CreatureDeath { private static void Postfix(Character __instance) { try { if (!((Object)(object)__instance == (Object)null) && !(__instance is Player)) { Instance?.CreditWeaponKill(((Object)__instance).GetInstanceID()); string text = CleanName(__instance); if (text != null && BossNames.Contains(text)) { Instance?.FinalizeBossKill(text); } } } catch { } } } [HarmonyPatch(typeof(Character), "AddFireDamage")] private static class Patch_FireDoT { private static void Postfix(Character __instance, float damage) { CreditDoT(__instance, damage); } } [HarmonyPatch(typeof(Character), "AddSpiritDamage")] private static class Patch_SpiritDoT { private static void Postfix(Character __instance, float damage) { CreditDoT(__instance, damage); } } [HarmonyPatch(typeof(Character), "AddPoisonDamage")] private static class Patch_PoisonDoT { private static void Postfix(Character __instance, float damage) { CreditDoT(__instance, damage); } } [HarmonyPatch(typeof(Player), "OnDeath")] private static class Patch_Death { private static void Postfix(Player __instance) { try { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { Instance?.OnLocalDeath(__instance); } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[gs] death: " + ex.Message)); } } } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] private static class Patch_AddItem { private static void Postfix(Inventory __instance, ItemData item) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 try { if (item?.m_shared != null && !((Object)(object)Player.m_localPlayer == (Object)null) && __instance == ((Humanoid)Player.m_localPlayer).GetInventory() && (int)item.m_shared.m_itemType == 1) { string n = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name); n = CleanPrefab(n); Instance.materials.TryGetValue(n, out var value); Instance.materials[n] = value + Math.Max(1, item.m_stack); } } catch { } } } public const string GUID = "net.cproudlock.gsvalheimstatsclient"; public const string NAME = "GsValheimStatsClient"; public const string VERSION = "0.2.9"; 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 Plugin Instance; private readonly Dictionary crafts = new Dictionary(); private readonly Dictionary materials = new Dictionary(); private readonly Dictionary weaponDamage = new Dictionary(); private readonly Dictionary weaponKills = new Dictionary(); private readonly Dictionary weaponMaxHit = new Dictionary(); private readonly Dictionary weaponBiggestSwing = new Dictionary(); private static string swingWeapon; private static float swingTime; private static double swingDamage; private readonly Dictionary bossDamage = new Dictionary(); private readonly Dictionary bossEngageSec = new Dictionary(); private static readonly Dictionary bossLastHit = new Dictionary(); private readonly Dictionary itemDamage = new Dictionary(); private readonly Dictionary itemKills = new Dictionary(); private readonly Dictionary itemMaxHit = new Dictionary(); private static readonly Dictionary lastWeaponByVictim = new Dictionary(); private static readonly Dictionary lastItemByVictim = new Dictionary(); private string weaponsPath; private readonly Dictionary othersCombat = new Dictionary(); private static readonly Dictionary lastAttackerByVictim = new Dictionary(); private static readonly HashSet BossNames = new HashSet { "Eikthyr", "gd_king", "Bonemass", "Dragon", "GoblinKing", "SeekerQueen", "Fader" }; private bool lifeActive; private double lifeStartPlaySec; private double lifeStartKills; private int longestLifeSec; private int bestKillsBeforeDeath; private string currentLifeStartedUtc; private static string lastKiller; private static string lastHitType; private readonly List pendingDeaths = new List(); private float lastEmit; private static bool dirty; private static Player chAttacker; private static int chVictimId; private static string chWeapon; private static string chItem; private static string chBossKey; private readonly Dictionary bossSelfDamage = new Dictionary(); private readonly Dictionary activeFights = new Dictionary(); private readonly List pendingBossKills = new List(); private static readonly HashSet BossNorm = new HashSet { "eikthyr", "gdking", "bonemass", "dragon", "goblinking", "seekerqueen", "fader" }; private static MethodInfo getStatMethod; private static Array statTypeValues; private const BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private Combat Other(string name) { if (!othersCombat.TryGetValue(name, out var value)) { value = new Combat(); othersCombat[name] = value; } return value; } private static string NormBoss(string s) { if (s != null) { return s.Replace("$enemy_", "").Replace("_", "").ToLowerInvariant(); } return ""; } private void Awake() { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; cfgUrl = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Url", "https://gs.proudtech.net/api/valheim/ingest", "gs dashboard ingest endpoint."); cfgToken = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Token", "", "Bearer token from the dashboard owner."); cfgWorld = ((BaseUnityPlugin)this).Config.Bind("General", "World", "vhserver3", "World name to file your stats under (must match the server's world)."); cfgEmitInterval = ((BaseUnityPlugin)this).Config.Bind("General", "EmitIntervalSeconds", 120, "How often to POST your stats."); weaponsPath = Path.Combine(Paths.ConfigPath, "net.cproudlock.gsvalheimstatsclient." + cfgWorld.Value + ".weapons.tsv"); LoadWeapons(); new Harmony("net.cproudlock.gsvalheimstatsclient").PatchAll(); Log.LogInfo((object)("GsValheimStatsClient 0.2.9 loaded, posting to " + cfgUrl.Value + " (token " + (string.IsNullOrEmpty(cfgToken.Value) ? "MISSING" : "set") + ")")); } private static string CleanName(Character c) { if ((Object)(object)c == (Object)null) { return null; } string text = (((Object)(object)((Component)c).gameObject != (Object)null) ? ((Object)((Component)c).gameObject).name : ((Object)c).name); if (string.IsNullOrEmpty(text)) { return null; } int num = text.IndexOf('('); if (num > 0) { text = text.Substring(0, num); } return text.Trim(); } private static void CreditDoT(Character victim, float damage) { try { if ((Object)(object)victim == (Object)null || damage <= 0f) { return; } if ((Object)(object)chAttacker != (Object)null && ((Object)victim).GetInstanceID() == chVictimId) { Instance?.AddElementalDamage(chAttacker, chWeapon, chItem, chBossKey, damage); return; } string text = CleanName(victim); if (text != null && BossNames.Contains(text)) { Instance?.AddBossSelfDamage(text, damage); } } catch { } } private void AddElementalDamage(Player atk, string weapon, string item, string bossKey, double dmg) { if ((Object)(object)atk == (Object)(object)Player.m_localPlayer) { if (!string.IsNullOrEmpty(weapon)) { weaponDamage.TryGetValue(weapon, out var value); weaponDamage[weapon] = value + dmg; } if (!string.IsNullOrEmpty(item)) { itemDamage.TryGetValue(item, out var value2); itemDamage[item] = value2 + dmg; } if (bossKey != null) { bossDamage.TryGetValue(bossKey, out var value3); bossDamage[bossKey] = value3 + dmg; RecordBossFight(bossKey, atk.GetPlayerName(), dmg); } return; } string playerName = atk.GetPlayerName(); if (!string.IsNullOrEmpty(playerName)) { Combat combat = Other(playerName); if (!string.IsNullOrEmpty(weapon)) { combat.wDmg.TryGetValue(weapon, out var value4); combat.wDmg[weapon] = value4 + dmg; } if (!string.IsNullOrEmpty(item)) { combat.iDmg.TryGetValue(item, out var value5); combat.iDmg[item] = value5 + dmg; } if (bossKey != null) { combat.bDmg.TryGetValue(bossKey, out var value6); combat.bDmg[bossKey] = value6 + dmg; RecordBossFight(bossKey, playerName, dmg); } } } private void AddBossSelfDamage(string boss, double dmg) { if (!string.IsNullOrEmpty(boss)) { bossSelfDamage.TryGetValue(boss, out var value); bossSelfDamage[boss] = value + dmg; } } private void RecordBossFight(string boss, string attacker, double dmg) { if (!string.IsNullOrEmpty(boss) && !string.IsNullOrEmpty(attacker)) { if (!activeFights.TryGetValue(boss, out var value)) { value = new BossFight { start = Time.time, firstBlood = attacker }; activeFights[boss] = value; } value.dmg.TryGetValue(attacker, out var value2); value.dmg[attacker] = value2 + dmg; } } private void FinalizeBossKill(string boss) { if (!activeFights.TryGetValue(boss, out var value)) { return; } activeFights.Remove(boss); string v = null; double num = 0.0; foreach (KeyValuePair item in value.dmg) { if (item.Value > num) { num = item.Value; v = item.Key; } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); J(stringBuilder, "boss", boss); C(stringBuilder); stringBuilder.Append("\"fightSec\":").Append((int)Math.Round(Time.time - value.start)); C(stringBuilder); J(stringBuilder, "firstBlood", value.firstBlood); C(stringBuilder); J(stringBuilder, "topDamagePlayer", v); C(stringBuilder); stringBuilder.Append("\"topDamage\":").Append(Math.Round(num)); C(stringBuilder); stringBuilder.Append("\"participants\":").Append(value.dmg.Count); C(stringBuilder); J(stringBuilder, "tsUtc", DateTime.UtcNow.ToString("O")); stringBuilder.Append('}'); pendingBossKills.Add(stringBuilder.ToString()); dirty = true; } private static string SafeSkill(HitData hit) { try { string text = ((object)Unsafe.As(ref hit.m_skill)/*cast due to .constrained prefix*/).ToString(); if (string.IsNullOrEmpty(text) || text == "None" || text == "All") { return "Unarmed"; } return text; } catch { return "Unarmed"; } } private static string CurrentWeaponItem() { return ItemOf((Character)(object)Player.m_localPlayer); } private static string ItemOf(Character c) { try { Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null); ItemData val2 = (((Object)(object)val != (Object)null) ? val.GetCurrentWeapon() : null); string text = ((val2 != null && val2.m_shared != null) ? val2.m_shared.m_name : null); return string.IsNullOrEmpty(text) ? "Unarmed" : text; } catch { return "Unarmed"; } } private void AddWeaponDamage(string weapon, double dmg) { if (!string.IsNullOrEmpty(weapon)) { weaponDamage.TryGetValue(weapon, out var value); weaponDamage[weapon] = value + dmg; weaponMaxHit.TryGetValue(weapon, out var value2); if (dmg > value2) { weaponMaxHit[weapon] = dmg; dirty = true; } float time = Time.time; if (weapon == swingWeapon && time - swingTime <= 0.2f) { swingDamage += dmg; } else { swingWeapon = weapon; swingDamage = dmg; } swingTime = time; weaponBiggestSwing.TryGetValue(weapon, out var value3); if (swingDamage > value3) { weaponBiggestSwing[weapon] = swingDamage; } } } private void AddItemDamage(string item, double dmg) { if (!string.IsNullOrEmpty(item)) { itemDamage.TryGetValue(item, out var value); itemDamage[item] = value + dmg; itemMaxHit.TryGetValue(item, out var value2); if (dmg > value2) { itemMaxHit[item] = dmg; } } } private void AddBossDamage(string boss, double dmg) { if (!string.IsNullOrEmpty(boss)) { bossDamage.TryGetValue(boss, out var value); bossDamage[boss] = value + dmg; float time = Time.time; if (bossLastHit.TryGetValue(boss, out var value2) && time - value2 <= 15f) { bossEngageSec.TryGetValue(boss, out var value3); bossEngageSec[boss] = value3 + (double)(time - value2); } bossLastHit[boss] = time; } } private void CreditWeaponKill(int victimId) { lastWeaponByVictim.TryGetValue(victimId, out var value); lastItemByVictim.TryGetValue(victimId, out var value2); lastAttackerByVictim.TryGetValue(victimId, out var value3); if (string.IsNullOrEmpty(value3)) { if (value != null) { weaponKills.TryGetValue(value, out var value4); weaponKills[value] = value4 + 1; } if (value2 != null) { itemKills.TryGetValue(value2, out var value5); itemKills[value2] = value5 + 1; } } else { Combat combat = Other(value3); if (value != null) { combat.wKill.TryGetValue(value, out var value6); combat.wKill[value] = value6 + 1; } if (value2 != null) { combat.iKill.TryGetValue(value2, out var value7); combat.iKill[value2] = value7 + 1; } } lastWeaponByVictim.Remove(victimId); lastItemByVictim.Remove(victimId); lastAttackerByVictim.Remove(victimId); dirty = true; SaveWeapons(); } private void LoadWeapons() { try { if (!File.Exists(weaponsPath)) { return; } string[] array = File.ReadAllLines(weaponsPath); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('\t'); if (array2.Length >= 6 && array2[0] == "life") { lifeActive = array2[1] == "1"; lifeStartPlaySec = double.Parse(array2[2], CultureInfo.InvariantCulture); lifeStartKills = double.Parse(array2[3], CultureInfo.InvariantCulture); longestLifeSec = int.Parse(array2[4]); bestKillsBeforeDeath = int.Parse(array2[5]); currentLifeStartedUtc = ((array2.Length > 6 && array2[6].Length > 0) ? array2[6] : null); } else if (array2.Length == 3) { if (array2[0] == "wdmg") { weaponDamage[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "wkill") { weaponKills[array2[1]] = int.Parse(array2[2]); } else if (array2[0] == "wmax") { weaponMaxHit[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "wsw") { weaponBiggestSwing[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "bdmg") { bossDamage[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "bself") { bossSelfDamage[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "beng") { bossEngageSec[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "idmg") { itemDamage[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } else if (array2[0] == "ikill") { itemKills[array2[1]] = int.Parse(array2[2]); } else if (array2[0] == "imax") { itemMaxHit[array2[1]] = double.Parse(array2[2], CultureInfo.InvariantCulture); } } else if (array2.Length == 4) { Combat combat = Other(array2[1]); if (array2[0] == "owdmg") { combat.wDmg[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "owkill") { combat.wKill[array2[2]] = int.Parse(array2[3]); } else if (array2[0] == "owmax") { combat.wMax[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "owsw") { combat.wSwing[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "oidmg") { combat.iDmg[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "oikill") { combat.iKill[array2[2]] = int.Parse(array2[3]); } else if (array2[0] == "oimax") { combat.iMax[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "obdmg") { combat.bDmg[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } else if (array2[0] == "obeng") { combat.bEng[array2[2]] = double.Parse(array2[3], CultureInfo.InvariantCulture); } } } } catch (Exception ex) { Log.LogWarning((object)("[gs] load weapons: " + ex.Message)); } } private void SaveWeapons() { try { List list = new List(); foreach (KeyValuePair item in weaponDamage) { list.Add("wdmg\t" + item.Key + "\t" + item.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair weaponKill in weaponKills) { list.Add($"wkill\t{weaponKill.Key}\t{weaponKill.Value}"); } foreach (KeyValuePair item2 in weaponMaxHit) { list.Add("wmax\t" + item2.Key + "\t" + item2.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item3 in weaponBiggestSwing) { list.Add("wsw\t" + item3.Key + "\t" + item3.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item4 in bossDamage) { list.Add("bdmg\t" + item4.Key + "\t" + item4.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item5 in bossSelfDamage) { list.Add("bself\t" + item5.Key + "\t" + item5.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item6 in bossEngageSec) { list.Add("beng\t" + item6.Key + "\t" + item6.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item7 in itemDamage) { list.Add("idmg\t" + item7.Key + "\t" + item7.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair itemKill in itemKills) { list.Add($"ikill\t{itemKill.Key}\t{itemKill.Value}"); } foreach (KeyValuePair item8 in itemMaxHit) { list.Add("imax\t" + item8.Key + "\t" + item8.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item9 in othersCombat) { string key = item9.Key; Combat value = item9.Value; foreach (KeyValuePair item10 in value.wDmg) { list.Add("owdmg\t" + key + "\t" + item10.Key + "\t" + item10.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item11 in value.wKill) { list.Add($"owkill\t{key}\t{item11.Key}\t{item11.Value}"); } foreach (KeyValuePair item12 in value.wMax) { list.Add("owmax\t" + key + "\t" + item12.Key + "\t" + item12.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item13 in value.wSwing) { list.Add("owsw\t" + key + "\t" + item13.Key + "\t" + item13.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item14 in value.iDmg) { list.Add("oidmg\t" + key + "\t" + item14.Key + "\t" + item14.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item15 in value.iKill) { list.Add($"oikill\t{key}\t{item15.Key}\t{item15.Value}"); } foreach (KeyValuePair item16 in value.iMax) { list.Add("oimax\t" + key + "\t" + item16.Key + "\t" + item16.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item17 in value.bDmg) { list.Add("obdmg\t" + key + "\t" + item17.Key + "\t" + item17.Value.ToString(CultureInfo.InvariantCulture)); } foreach (KeyValuePair item18 in value.bEng) { list.Add("obeng\t" + key + "\t" + item18.Key + "\t" + item18.Value.ToString(CultureInfo.InvariantCulture)); } } list.Add(string.Format("life\t{0}\t{1}\t{2}\t{3}\t{4}\t{5}", lifeActive ? 1 : 0, lifeStartPlaySec.ToString(CultureInfo.InvariantCulture), lifeStartKills.ToString(CultureInfo.InvariantCulture), longestLifeSec, bestKillsBeforeDeath, currentLifeStartedUtc ?? "")); File.WriteAllLines(weaponsPath, list); } catch (Exception ex) { Log.LogWarning((object)("[gs] save weapons: " + ex.Message)); } } private static string CleanPrefab(string n) { if (string.IsNullOrEmpty(n)) { return "unknown"; } int num = n.IndexOf('('); if (num > 0) { n = n.Substring(0, num); } return n.Trim(); } private void OnLocalDeath(Player p) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) int num = (lifeActive ? ((int)Math.Max(0.0, ReadStat("EnemyKills") - lifeStartKills)) : 0); int num2 = (lifeActive ? ((int)Math.Max(0.0, ReadPlaySec() - lifeStartPlaySec)) : 0); if (num2 > longestLifeSec) { longestLifeSec = num2; } if (num > bestKillsBeforeDeath) { bestKillsBeforeDeath = num; } string text = lastKiller ?? MapHitType(lastHitType); string v = "unknown"; try { v = ((object)Heightmap.FindBiome(((Component)p).transform.position)/*cast due to .constrained prefix*/).ToString(); } catch { } Vector3 position = ((Component)p).transform.position; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); J(stringBuilder, "playerName", p.GetPlayerName()); C(stringBuilder); J(stringBuilder, "killer", text); C(stringBuilder); J(stringBuilder, "biome", v); C(stringBuilder); stringBuilder.Append("\"posX\":").Append(Mathf.RoundToInt(position.x)); C(stringBuilder); stringBuilder.Append("\"posY\":").Append(Mathf.RoundToInt(position.y)); C(stringBuilder); stringBuilder.Append("\"posZ\":").Append(Mathf.RoundToInt(position.z)); C(stringBuilder); stringBuilder.Append("\"lifeSec\":").Append(num2); C(stringBuilder); stringBuilder.Append("\"killsThisLife\":").Append(num); C(stringBuilder); J(stringBuilder, "tsUtc", DateTime.UtcNow.ToString("O")); stringBuilder.Append('}'); pendingDeaths.Add(stringBuilder.ToString()); lifeActive = false; try { SaveWeapons(); } catch { } Log.LogInfo((object)$"[gs] death by {text} after {num2}s active, {num} kills this life"); try { Emit(); } catch { } } private static string MapHitType(string h) { if (string.IsNullOrEmpty(h)) { return "unknown"; } return h switch { "Fall" => "fall", "Drowning" => "drowning", "Burning" => "fire", "Freezing" => "freezing", "Poisoned" => "poison", "Smoke" => "smoke", "Tree" => "tree", "Self" => "self", "EdgeOfWorld" => "edge of world", "Water" => "water", _ => h.ToLowerInvariant(), }; } private void Update() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } if (!lifeActive && !((Character)localPlayer).IsDead()) { lifeActive = true; lifeStartPlaySec = ReadPlaySec(); lifeStartKills = ReadStat("EnemyKills"); currentLifeStartedUtc = DateTime.UtcNow.ToString("O"); try { SaveWeapons(); } catch { } } float unscaledTime = Time.unscaledTime; if ((!dirty || !(unscaledTime - lastEmit > 15f)) && !(unscaledTime - lastEmit > (float)Mathf.Max(30, cfgEmitInterval.Value))) { return; } lastEmit = unscaledTime; dirty = false; try { Emit(); } catch (Exception arg) { Log.LogError((object)$"[gs] emit: {arg}"); } } private static double ToD(object o) { try { return Convert.ToDouble(o); } catch { return 0.0; } } private static IDictionary GetDict(object obj, string field) { try { return obj?.GetType().GetField(field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) as IDictionary; } catch { return null; } } private static void ReadProfile(Dictionary statsKv, Dictionary enemyKills, Dictionary craftStats, Dictionary pickups) { try { Game instance = Game.instance; PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null); if (val == null) { return; } IDictionary dict = GetDict(((object)val).GetType().GetField("m_playerStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(val), "m_stats"); if (dict != null) { foreach (DictionaryEntry item in dict) { double num = ToD(item.Value); if (num > 0.0) { statsKv["vh_" + item.Key] = (long)num; } } } IDictionary dict2 = GetDict(val, "m_enemyStats"); if (dict2 != null) { foreach (DictionaryEntry item2 in dict2) { int num2 = (int)ToD(item2.Value); if (num2 > 0) { enemyKills[(string)item2.Key] = num2; } } } IDictionary dict3 = GetDict(val, "m_itemCraftStats"); if (dict3 != null) { foreach (DictionaryEntry item3 in dict3) { int num3 = (int)ToD(item3.Value); if (num3 > 0) { craftStats[(string)item3.Key] = num3; } } } IDictionary dict4 = GetDict(val, "m_itemPickupStats"); if (dict4 == null) { return; } foreach (DictionaryEntry item4 in dict4) { int num4 = (int)ToD(item4.Value); if (num4 > 0) { pickups[(string)item4.Key] = num4; } } } catch (Exception ex) { Log.LogWarning((object)("[gs] profile read: " + ex.Message)); } } private static double ReadStat(string statName) { try { Game instance = Game.instance; PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null); IDictionary dict = GetDict(((object)val)?.GetType().GetField("m_playerStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(val), "m_stats"); if (dict != null) { foreach (DictionaryEntry item in dict) { if (item.Key.ToString() == statName) { return ToD(item.Value); } } } } catch { } return 0.0; } private static double ReadPlaySec() { return ReadStat("TimeInBase") + ReadStat("TimeOutOfBase"); } private void Emit() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } string playerName = localPlayer.GetPlayerName(); if (string.IsNullOrEmpty(playerName) || playerName == "...") { return; } Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); Dictionary dictionary3 = new Dictionary(); Dictionary dictionary4 = new Dictionary(); ReadProfile(dictionary, dictionary2, dictionary3, dictionary4); long value2; long value = (dictionary.TryGetValue("vh_Deaths", out value2) ? value2 : 0); int num = 0; int num2 = 0; foreach (KeyValuePair item in dictionary2) { num += item.Value; if (BossNorm.Contains(NormBoss(item.Key))) { num2 += item.Value; } } StringBuilder stringBuilder = new StringBuilder(2048); stringBuilder.Append('{'); J(stringBuilder, "schemaVersion", 1); C(stringBuilder); J(stringBuilder, "game", "valheim"); C(stringBuilder); J(stringBuilder, "source", "client"); C(stringBuilder); J(stringBuilder, "reporter", playerName); C(stringBuilder); J(stringBuilder, "world", cfgWorld.Value); C(stringBuilder); J(stringBuilder, "emittedAtUtc", DateTime.UtcNow.ToString("O")); C(stringBuilder); J(stringBuilder, "snapshotIdLocal", Guid.NewGuid().ToString("N")); C(stringBuilder); stringBuilder.Append("\"players\":[{"); J(stringBuilder, "name", playerName); C(stringBuilder); try { J(stringBuilder, "platformId", localPlayer.GetPlayerID().ToString()); C(stringBuilder); } catch { } stringBuilder.Append("\"kills\":").Append(num); C(stringBuilder); stringBuilder.Append("\"bossKills\":").Append(num2); C(stringBuilder); stringBuilder.Append("\"deaths\":").Append(value); C(stringBuilder); stringBuilder.Append("\"longestLifeSec\":").Append(longestLifeSec); C(stringBuilder); stringBuilder.Append("\"bestKillsBeforeDeath\":").Append(bestKillsBeforeDeath); C(stringBuilder); J(stringBuilder, "currentLifeStartedUtc", currentLifeStartedUtc); C(stringBuilder); stringBuilder.Append("\"stats\":{"); bool flag = true; foreach (KeyValuePair item2 in dictionary) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('"').Append(Esc(item2.Key)).Append("\":") .Append(item2.Value); } stringBuilder.Append("},"); stringBuilder.Append("\"skills\":["); bool flag2 = true; try { foreach (Skill skill in ((Character)localPlayer).GetSkills().GetSkillList()) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append('{'); J(stringBuilder, "skill", ((object)Unsafe.As(ref skill.m_info.m_skill)/*cast due to .constrained prefix*/).ToString()); C(stringBuilder); stringBuilder.Append("\"level\":").Append(Mathf.FloorToInt(skill.m_level)); stringBuilder.Append('}'); } } catch (Exception ex) { Log.LogWarning((object)("[gs] skills: " + ex.Message)); } stringBuilder.Append("],"); stringBuilder.Append("\"creatureKills\":["); bool flag3 = true; foreach (KeyValuePair item3 in dictionary2) { if (!flag3) { stringBuilder.Append(','); } flag3 = false; stringBuilder.Append('{'); J(stringBuilder, "creature", item3.Key); C(stringBuilder); stringBuilder.Append("\"kills\":").Append(item3.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"crafts\":["); bool flag4 = true; foreach (KeyValuePair item4 in dictionary3) { if (!flag4) { stringBuilder.Append(','); } flag4 = false; stringBuilder.Append('{'); J(stringBuilder, "item", item4.Key); C(stringBuilder); stringBuilder.Append("\"count\":").Append(item4.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"materials\":["); bool flag5 = true; foreach (KeyValuePair material in materials) { if (!flag5) { stringBuilder.Append(','); } flag5 = false; stringBuilder.Append('{'); J(stringBuilder, "material", material.Key); C(stringBuilder); stringBuilder.Append("\"amount\":").Append(material.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"weapons\":["); bool flag6 = true; HashSet hashSet = new HashSet(); foreach (string key in weaponDamage.Keys) { hashSet.Add(key); } foreach (string key2 in weaponKills.Keys) { hashSet.Add(key2); } foreach (string item5 in hashSet) { double value3; double a = (weaponDamage.TryGetValue(item5, out value3) ? value3 : 0.0); int value5; int value4 = (weaponKills.TryGetValue(item5, out value5) ? value5 : 0); if (!flag6) { stringBuilder.Append(','); } flag6 = false; double value6; double a2 = (weaponMaxHit.TryGetValue(item5, out value6) ? value6 : 0.0); double value7; double a3 = (weaponBiggestSwing.TryGetValue(item5, out value7) ? value7 : 0.0); stringBuilder.Append('{'); J(stringBuilder, "weapon", item5); C(stringBuilder); stringBuilder.Append("\"damageDealt\":").Append(Math.Round(a)); C(stringBuilder); stringBuilder.Append("\"kills\":").Append(value4); C(stringBuilder); stringBuilder.Append("\"hardestHit\":").Append(Math.Round(a2)); C(stringBuilder); stringBuilder.Append("\"biggestSwing\":").Append(Math.Round(a3)); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"weaponItems\":["); bool flag7 = true; HashSet hashSet2 = new HashSet(); foreach (string key3 in itemDamage.Keys) { hashSet2.Add(key3); } foreach (string key4 in itemKills.Keys) { hashSet2.Add(key4); } foreach (string item6 in hashSet2) { double value8; double a4 = (itemDamage.TryGetValue(item6, out value8) ? value8 : 0.0); int value10; int value9 = (itemKills.TryGetValue(item6, out value10) ? value10 : 0); double value11; double a5 = (itemMaxHit.TryGetValue(item6, out value11) ? value11 : 0.0); if (!flag7) { stringBuilder.Append(','); } flag7 = false; stringBuilder.Append('{'); J(stringBuilder, "item", item6); C(stringBuilder); stringBuilder.Append("\"damageDealt\":").Append(Math.Round(a4)); C(stringBuilder); stringBuilder.Append("\"kills\":").Append(value9); C(stringBuilder); stringBuilder.Append("\"hardestHit\":").Append(Math.Round(a5)); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"pickups\":["); bool flag8 = true; foreach (KeyValuePair item7 in dictionary4) { if (!flag8) { stringBuilder.Append(','); } flag8 = false; stringBuilder.Append('{'); J(stringBuilder, "item", item7.Key); C(stringBuilder); stringBuilder.Append("\"count\":").Append(item7.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"boss\":["); bool flag9 = true; foreach (KeyValuePair item8 in bossDamage) { if (!flag9) { stringBuilder.Append(','); } flag9 = false; double value13; int value12 = (bossEngageSec.TryGetValue(item8.Key, out value13) ? ((int)Math.Round(value13)) : 0); stringBuilder.Append('{'); J(stringBuilder, "boss", item8.Key); C(stringBuilder); stringBuilder.Append("\"damageDealt\":").Append(Math.Round(item8.Value)); C(stringBuilder); stringBuilder.Append("\"fightSec\":").Append(value12); stringBuilder.Append('}'); } stringBuilder.Append("]"); stringBuilder.Append('}'); foreach (KeyValuePair item9 in othersCombat) { stringBuilder.Append(",{"); J(stringBuilder, "name", item9.Key); C(stringBuilder); AppendCombat(stringBuilder, item9.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"deathEvents\":[").Append(string.Join(",", pendingDeaths)).Append("],"); stringBuilder.Append("\"bossKillEvents\":[").Append(string.Join(",", pendingBossKills)).Append("],"); stringBuilder.Append("\"bossSelfDamage\":["); bool flag10 = true; foreach (KeyValuePair item10 in bossSelfDamage) { if (!flag10) { stringBuilder.Append(','); } flag10 = false; stringBuilder.Append('{'); J(stringBuilder, "boss", item10.Key); C(stringBuilder); stringBuilder.Append("\"damage\":").Append(Math.Round(item10.Value)); stringBuilder.Append('}'); } stringBuilder.Append(']'); stringBuilder.Append('}'); SaveWeapons(); Post(stringBuilder.ToString()); } private static void AppendCombat(StringBuilder sb, Combat c) { sb.Append("\"weapons\":["); bool flag = true; HashSet hashSet = new HashSet(); foreach (string key in c.wDmg.Keys) { hashSet.Add(key); } foreach (string key2 in c.wKill.Keys) { hashSet.Add(key2); } foreach (string item in hashSet) { double value; double a = (c.wDmg.TryGetValue(item, out value) ? value : 0.0); int value3; int value2 = (c.wKill.TryGetValue(item, out value3) ? value3 : 0); double value4; double a2 = (c.wMax.TryGetValue(item, out value4) ? value4 : 0.0); double value5; double a3 = (c.wSwing.TryGetValue(item, out value5) ? value5 : 0.0); if (!flag) { sb.Append(','); } flag = false; sb.Append('{'); J(sb, "weapon", item); C(sb); sb.Append("\"damageDealt\":").Append(Math.Round(a)); C(sb); sb.Append("\"kills\":").Append(value2); C(sb); sb.Append("\"hardestHit\":").Append(Math.Round(a2)); C(sb); sb.Append("\"biggestSwing\":").Append(Math.Round(a3)); sb.Append('}'); } sb.Append("],"); sb.Append("\"weaponItems\":["); bool flag2 = true; HashSet hashSet2 = new HashSet(); foreach (string key3 in c.iDmg.Keys) { hashSet2.Add(key3); } foreach (string key4 in c.iKill.Keys) { hashSet2.Add(key4); } foreach (string item2 in hashSet2) { double value6; double a4 = (c.iDmg.TryGetValue(item2, out value6) ? value6 : 0.0); int value8; int value7 = (c.iKill.TryGetValue(item2, out value8) ? value8 : 0); double value9; double a5 = (c.iMax.TryGetValue(item2, out value9) ? value9 : 0.0); if (!flag2) { sb.Append(','); } flag2 = false; sb.Append('{'); J(sb, "item", item2); C(sb); sb.Append("\"damageDealt\":").Append(Math.Round(a4)); C(sb); sb.Append("\"kills\":").Append(value7); C(sb); sb.Append("\"hardestHit\":").Append(Math.Round(a5)); sb.Append('}'); } sb.Append("],"); sb.Append("\"boss\":["); bool flag3 = true; foreach (KeyValuePair item3 in c.bDmg) { if (!flag3) { sb.Append(','); } flag3 = false; double value11; int value10 = (c.bEng.TryGetValue(item3.Key, out value11) ? ((int)Math.Round(value11)) : 0); sb.Append('{'); J(sb, "boss", item3.Key); C(sb); sb.Append("\"damageDealt\":").Append(Math.Round(item3.Value)); C(sb); sb.Append("\"fightSec\":").Append(value10); sb.Append('}'); } sb.Append("]"); } 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) { pendingDeaths.Clear(); pendingBossKills.Clear(); } Log.LogInfo((object)$"[gs] ingest status: {statusCode}"); } }); } catch (Exception ex) { Log.LogWarning((object)("[gs] post: " + 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(); } } }