Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EilifCompanionClient v0.4.6
EilifCompanionClient.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; 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(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("EilifCompanionClient")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.6.0")] [assembly: AssemblyInformationalVersion("0.4.6+ef4ea9c22bb085a36b73df4dfb6813557761d77a")] [assembly: AssemblyProduct("EilifCompanionClient")] [assembly: AssemblyTitle("EilifCompanionClient")] [assembly: AssemblyVersion("0.4.6.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 EilifCompanionClient { internal static class EilifDeathReporter { private static readonly FieldInfo LastHitField = AccessTools.Field(typeof(Character), "m_lastHit"); internal static void Report(Player who) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)who == (Object)null || (Object)(object)who != (Object)(object)Player.m_localPlayer || !EilifMapTrackerPlugin.IsOnServer()) { return; } if (LastHitField == null) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)"[EilifDeath] Character.m_lastHit not found — the game's field layout changed; no death reported."); } return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } object? value = LastHitField.GetValue(who); HitData val = (HitData)((value is HitData) ? value : null); if (val == null) { ManualLogSource log2 = EilifMapTrackerPlugin.Log; if (log2 != null) { log2.LogWarning((object)"[EilifDeath] no m_lastHit at death — nothing to report."); } return; } string text = ((object)Unsafe.As<HitType, HitType>(ref val.m_hitType)/*cast due to .constrained prefix*/).ToString(); string text2 = ResolveAttacker(val); string text3 = ResolveBiome(who); string playerName = who.GetPlayerName(); string text4 = instance.GetWorldName() ?? ""; if (!string.IsNullOrEmpty(playerName)) { string playerName2 = Player.m_localPlayer.GetPlayerName(); Vector3 position = ((Component)who).transform.position; string s = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"eilif-death\",\"world\":" + EilifMapTrackerPlugin.JsonStr(text4) + ",\"player\":" + EilifMapTrackerPlugin.JsonStr(playerName) + ",\"reporter\":" + EilifMapTrackerPlugin.JsonStr(playerName2) + ",\"tsUtc\":" + EilifMapTrackerPlugin.JsonStr(s) + ",\"hitType\":" + EilifMapTrackerPlugin.JsonStr(text) + ",\"attacker\":" + ((text2 == null) ? "null" : EilifMapTrackerPlugin.JsonStr(text2)) + ",\"biome\":" + EilifMapTrackerPlugin.JsonStr(text3) + ",\"pos\":{\"x\":" + Fmt(position.x) + ",\"z\":" + Fmt(position.z) + "}}"; ManualLogSource log3 = EilifMapTrackerPlugin.Log; if (log3 != null) { log3.LogInfo((object)("[EilifDeath] " + playerName + " died in " + text4 + ": hitType=" + text + ", attacker=" + (text2 ?? "(none)") + ", biome=" + text3)); } EilifMapTrackerPlugin.PostJson(json, "[EilifDeath]", "[EilifDeath] reported " + text + ((text2 == null) ? "" : (" by " + text2)) + " for " + playerName); } } private static string ResolveAttacker(HitData hit) { try { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker == (Object)null) { return null; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val != (Object)null) { string playerName = val.GetPlayerName(); return string.IsNullOrEmpty(playerName) ? null : playerName; } string name = attacker.m_name; if (!string.IsNullOrEmpty(name)) { return name; } return PrefabName(((Component)attacker).gameObject); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifDeath] attacker lookup failed: " + ex.Message)); } return null; } } private unsafe static string ResolveBiome(Player who) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { Vector3 position = ((Component)who).transform.position; Biome val = Heightmap.FindBiome(position); if ((int)val == 0 && WorldGenerator.instance != null) { val = WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false); } return ((object)(*(Biome*)(&val))/*cast due to .constrained prefix*/).ToString(); } catch { return "None"; } } private static string PrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } string name = ((Object)go).name; if (string.IsNullOrEmpty(name)) { return null; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num <= 0) { return name; } return name.Substring(0, num); } private static string Fmt(float v) { return ((float)Math.Round(v, 1)).ToString("0.#", CultureInfo.InvariantCulture); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class Patch_PlayerOnDeath { private static void Postfix(Player __instance) { try { EilifDeathReporter.Report(__instance); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifDeath] death hook failed: " + ex.Message)); } } } } [BepInPlugin("net.eilif.companionclient", "Eilif Companion Client", "0.4.6")] public class EilifMapTrackerPlugin : BaseUnityPlugin { public const string PluginGuid = "net.eilif.companionclient"; public const string PluginName = "Eilif Companion Client"; public const string PluginVersion = "0.4.6"; internal static ManualLogSource Log; internal static EilifMapTrackerPlugin Instance; internal static string IngestUrl = ""; internal static string IngestToken = ""; private ConfigEntry<string> _url; private ConfigEntry<string> _token; private ConfigEntry<int> _intervalSeconds; private ConfigEntry<string> _keepItemTypes; private ConfigEntry<bool> _statsEnabled; private ConfigEntry<string> _pumpkinCreatures; private static readonly HttpClient Http = new HttpClient { Timeout = TimeSpan.FromSeconds(20.0) }; private int _postInFlight; private float _postTimer; private bool _wasConnected; private float _lastPct = -1f; private static bool _warnedExploredShape; private static readonly FieldInfo ExploredField = AccessTools.Field(typeof(Minimap), "m_explored"); private static readonly FieldInfo TextureSizeField = AccessTools.Field(typeof(Minimap), "m_textureSize"); private static readonly string[] ExpectedPatchClasses = new string[4] { "Patch_GameLogout", "Patch_PlayerOnDeath", "Patch_MoveInventoryToGrave", "Patch_PumpkinDropFilter" }; private const double FaultCooldownSeconds = 60.0; private DateTime _faultLastUtc = DateTime.MinValue; private int _faultSuppressed; private string _lastCachedPlayer = ""; private string _lastCachedWorld = ""; private void Awake() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Instance = this; _url = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Url", "https://valheim-dashboard.vercel.app/api/gs-ingest", "Dashboard ingest endpoint. The explored-map % is POSTed here as source:'client-map'."); _token = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Token", "", "Optional Bearer token. If set it is sent as 'Authorization: Bearer <token>'. Blank is fine (pilot)."); _intervalSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Map", "IntervalSeconds", 300, new ConfigDescription("Seconds between map-% posts while connected to a server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(60, 3600), Array.Empty<object>())); _keepItemTypes = ((BaseUnityPlugin)this).Config.Bind<string>("Death", "KeepItemTypes", "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Ammo, AmmoNonEquipable", "Item types that stay with you on death IN ADDITION to equipped gear (comma-separated ItemDrop.ItemData.ItemType names). Only active on a world whose deathkeepequip global key is set (i.e. this server); on other servers deaths are pure vanilla. Empty = off."); _statsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stats", "Enabled", true, "Post the local player's raw profile counters (kills, deaths, builds, crafts, distance) to the dashboard ingest on the same cadence as the map-% post. This is what makes the kill/death/build/distance/craft boards and the Great Deeds work on Valheim 1.0, where GsValheimStatsClient no longer fills the stats map. Posts source:'client' to the same Url/Token above."); _pumpkinCreatures = ((BaseUnityPlugin)this).Config.Bind<string>("Halloween", "PumpkinDropCreatures", "Draugr,Draugr_Ranged,Draugr_Elite", "Which creatures can drop HalloweenPieces pumpkins (BHP_PumpkinDrop): a comma-separated list of creature PREFAB names (Skeleton, Draugr_Elite, Greydwarf_Shaman), not display names; case and spaces around names are ignored. * = every creature (HalloweenPieces' own behaviour, filter off). Empty = no creature drops pumpkins. Only matters on a server running HalloweenPieces: anywhere else there is no pumpkin drop to remove, so this does nothing."); IngestUrl = _url.Value; IngestToken = _token.Value; EilifStatsReporter.Enabled = _statsEnabled.Value; TombstoneKeeper.Configure(_keepItemTypes.Value); PumpkinDropFilter.Configure(_pumpkinCreatures.Value); try { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } catch { } Harmony val = new Harmony("net.eilif.companionclient"); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(EilifMapTrackerPlugin).Assembly); foreach (Type type in typesFromAssembly) { string text = "?"; try { if (!(type == null)) { text = type.Name; if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0) { val.CreateClassProcessor(type).Patch(); hashSet.Add(text); } } } catch (Exception ex) { Log.LogError((object)("[EilifDeath] could not apply " + text + ": " + ex.Message + " -> " + FeatureOf(text) + ".")); } } Log.LogInfo((object)$"[EilifDeath] patch classes applied: {CountApplied(hashSet)}/{ExpectedPatchClasses.Length}"); ReportMissing(hashSet); if (TombstoneKeeper.KeepTypes.Count > 0) { Log.LogInfo((object)$"[EilifDeath] tombstone keep-list armed ({TombstoneKeeper.KeepTypes.Count} item types; active only where deathkeepequip is set)."); } Log.LogInfo((object)string.Format("[EilifMap] {0} v{1} loaded. Posting explored-map % to {2} every {3}s while on a server.", "Eilif Companion Client", "0.4.6", _url.Value, _intervalSeconds.Value)); Log.LogInfo((object)("[EilifDeath] death-cause reporter armed (posts source:'eilif-death' to " + _url.Value + " when the local player dies on a server).")); Log.LogInfo((object)("[EilifHalloween] pumpkin drop filter: " + PumpkinDropFilter.Describe() + " (inert without HalloweenPieces).")); Log.LogInfo((object)string.Format("[EilifStats] raw profile-stats reporter {0} (posts source:'client' builds/crafts/distance/pickups/fish to {1} every {2}s and on logout while on a server).", _statsEnabled.Value ? "armed" : "DISABLED", _url.Value, _intervalSeconds.Value)); } private static string FeatureOf(string patchClass) { return patchClass switch { "Patch_GameLogout" => "the final explored-map reading on a clean logout (the interval post and the disconnect fallback still run)", "Patch_PlayerOnDeath" => "the authoritative death cause; the dashboard falls back to the third-party 'enemyhit' catch-all for every death", "Patch_MoveInventoryToGrave" => "the tombstone keep-list; deaths drop everything vanilla would drop", "Patch_PumpkinDropFilter" => "the [Halloween] PumpkinDropCreatures filter; on a HalloweenPieces server every creature can drop pumpkins", _ => "an unnamed feature", }; } private static int CountApplied(HashSet<string> applied) { int num = 0; for (int i = 0; i < ExpectedPatchClasses.Length; i++) { if (applied.Contains(ExpectedPatchClasses[i])) { num++; } } return num; } private static void ReportMissing(HashSet<string> applied) { for (int i = 0; i < ExpectedPatchClasses.Length; i++) { if (!applied.Contains(ExpectedPatchClasses[i])) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("[EilifDeath] MISSING patch class " + ExpectedPatchClasses[i] + " - " + FeatureOf(ExpectedPatchClasses[i]) + ".")); } } } } private void Update() { try { UpdateCore(); } catch (Exception ex) { try { DateTime utcNow = DateTime.UtcNow; if (_faultLastUtc != DateTime.MinValue && (utcNow - _faultLastUtc).TotalSeconds < 60.0) { if (_faultSuppressed < int.MaxValue) { _faultSuppressed++; } return; } int faultSuppressed = _faultSuppressed; _faultSuppressed = 0; _faultLastUtc = utcNow; ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[EilifMap] update pump failed: " + ex.Message + ((faultSuppressed > 0) ? $" (+{faultSuppressed} more in the last minute)" : "") + ". Cartography posting will retry on the next tick; nothing else is affected.")); } } catch { } } } private void UpdateCore() { bool flag = IsOnServer(); if (flag) { _postTimer += Time.unscaledDeltaTime; if (_postTimer >= (float)_intervalSeconds.Value) { _postTimer = 0f; ComputeAndPost("interval"); EilifStatsReporter.Post("interval"); } } else if (_wasConnected) { _postTimer = 0f; if (_lastPct >= 0f) { Post(_lastCachedPlayer, _lastCachedWorld, _lastPct, "disconnect(cached)"); } _lastPct = -1f; } _wasConnected = flag; } internal static bool IsOnServer() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return false; } if (instance.GetServerPeer() == null) { return false; } if ((Object)(object)Minimap.instance != (Object)null) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } internal void ComputeAndPost(string reason) { try { Minimap instance = Minimap.instance; ZNet instance2 = ZNet.instance; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !((Object)(object)localPlayer == (Object)null)) { float? num = ComputeExploredPct(instance); if (num.HasValue) { string playerName = localPlayer.GetPlayerName(); string text = instance2.GetWorldName() ?? ""; _lastCachedPlayer = playerName; _lastCachedWorld = text; _lastPct = num.Value; Post(playerName, text, num.Value, reason); } } } catch (Exception ex) { Log.LogWarning((object)("[EilifMap] compute/post failed: " + ex.Message)); } } private static float? ComputeExploredPct(Minimap mm) { if (ExploredField == null || TextureSizeField == null) { return null; } object value = ExploredField.GetValue(mm); bool[] arr = value as bool[]; Func<int, bool> func; int num; if (arr != null) { func = (int i) => arr[i]; num = arr.Length; } else { BitArray bits = value as BitArray; if (bits == null) { if (!_warnedExploredShape) { _warnedExploredShape = true; ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[EilifMap] Minimap.m_explored is " + ((value == null) ? "null" : value.GetType().FullName) + " (expected bool[] or BitArray); explored-map posts are disabled until the plugin is updated.")); } } return null; } func = (int i) => bits[i]; num = bits.Length; } if (num == 0) { return null; } int num2 = (int)TextureSizeField.GetValue(mm); if (num2 <= 0 || num < num2 * num2) { return null; } float num3 = (float)num2 / 2f; float num4 = num3 * num3; long num5 = 0L; long num6 = 0L; for (int num7 = 0; num7 < num2; num7++) { float num8 = (float)num7 - num3; float num9 = num4 - num8 * num8; if (num9 < 0f) { continue; } float num10 = (float)Math.Sqrt(num9); int num11 = Math.Max(0, (int)Math.Ceiling(num3 - num10)); int num12 = Math.Min(num2 - 1, (int)Math.Floor(num3 + num10)); int num13 = num7 * num2; for (int num14 = num11; num14 <= num12; num14++) { num6++; if (func(num13 + num14)) { num5++; } } } if (num6 == 0L) { return null; } float val = (float)((double)num5 / (double)num6 * 100.0); return Math.Min(100f, val); } private void Post(string playerName, string world, float pct, string reason) { if (!string.IsNullOrEmpty(playerName) && Interlocked.CompareExchange(ref _postInFlight, 1, 0) == 0) { float num = (float)Math.Round(pct, 2); string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"client-map\",\"playerName\":" + JsonStr(playerName) + ",\"reporter\":" + JsonStr(playerName) + ",\"world\":" + JsonStr(world) + ",\"exploredPct\":" + num.ToString("0.##", CultureInfo.InvariantCulture) + "}"; string url = _url.Value; string token = _token.Value; string pctText = num.ToString("0.##", CultureInfo.InvariantCulture); Task.Run(() => PostAsync(url, token, json, playerName, world, pctText, reason)); } } private async Task PostAsync(string url, string token, string json, string playerName, string world, string pctText, string reason) { try { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url); try { req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json"); if (!string.IsNullOrEmpty(token)) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token); } HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false); try { if (val.IsSuccessStatusCode) { Log.LogInfo((object)("[EilifMap] posted " + pctText + "% for " + playerName + " (" + world + ") [" + reason + "]")); } else { Log.LogWarning((object)$"[EilifMap] post HTTP {(int)val.StatusCode} {val.ReasonPhrase} for {playerName}"); } } finally { ((IDisposable)val)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } catch (Exception ex) { Log.LogWarning((object)("[EilifMap] post failed: " + ex.Message)); } finally { Interlocked.Exchange(ref _postInFlight, 0); } } internal static void PostJson(string json, string tag, string successLine) { string url = IngestUrl; if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(json)) { return; } string token = IngestToken; try { Task.Run(() => SendAsync(url, token, json, tag, successLine)); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)(tag + " could not queue post: " + ex.Message)); } } } private static async Task SendAsync(string url, string token, string json, string tag, string successLine) { try { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url); try { req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json"); if (!string.IsNullOrEmpty(token)) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token); } HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false); try { if (val.IsSuccessStatusCode) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)successLine); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)$"{tag} post HTTP {(int)val.StatusCode} {val.ReasonPhrase}"); } } } finally { ((IDisposable)val)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } catch (Exception ex) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)(tag + " post failed: " + ex.Message)); } } } internal static string JsonStr(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); 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", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Game), "Logout")] internal static class Patch_GameLogout { private static void Prefix() { try { EilifMapTrackerPlugin.Instance?.ComputeAndPost("logout"); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifMap] logout hook failed: " + ex.Message)); } } try { EilifStatsReporter.Post("logout"); } catch (Exception ex2) { ManualLogSource log2 = EilifMapTrackerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[EilifStats] logout hook failed: " + ex2.Message)); } } } } internal static class PumpkinDropFilter { internal const string PumpkinPrefab = "BHP_PumpkinDrop"; internal const string DefaultCreatures = "Draugr,Draugr_Ranged,Draugr_Elite"; internal static bool Active; internal static readonly HashSet<string> Allowed = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly char[] PrefabCut = new char[2] { '(', ' ' }; internal static void Configure(string csv) { Allowed.Clear(); Active = true; if (csv == null) { return; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { if (text == "*") { Active = false; Allowed.Clear(); break; } Allowed.Add(text); } } } internal static string Describe() { if (!Active) { return "off (* = every creature, HalloweenPieces' own behaviour)"; } if (Allowed.Count == 0) { return "no creature drops pumpkins"; } return Allowed.Count + " creature prefab(s) may drop pumpkins"; } internal static string PrefabName(string name) { if (string.IsNullOrEmpty(name)) { return ""; } int num = name.IndexOfAny(PrefabCut); if (num < 0) { return name; } return name.Substring(0, num); } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] internal static class Patch_PumpkinDropFilter { private static readonly FieldInfo CharacterField = AccessTools.Field(typeof(CharacterDrop), "m_character"); private static bool _warned; private static void Postfix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result) { try { if (!PumpkinDropFilter.Active || __result == null || __result.Count == 0 || (Object)(object)__instance == (Object)null) { return; } bool flag = false; for (int i = 0; i < __result.Count; i++) { if (IsPumpkin(__result[i].Key)) { flag = true; break; } } if (!flag) { return; } Character val = null; if (CharacterField != null) { object? value = CharacterField.GetValue(__instance); val = (Character)((value is Character) ? value : null); } if ((Object)(object)val == (Object)null) { val = ((Component)__instance).GetComponent<Character>(); } if ((Object)(object)val == (Object)null) { return; } string item = PumpkinDropFilter.PrefabName(((Object)((Component)val).gameObject).name).Trim(); if (!PumpkinDropFilter.Allowed.Contains(item)) { __result.RemoveAll((KeyValuePair<GameObject, int> kv) => IsPumpkin(kv.Key)); } } catch (Exception ex) { if (_warned) { return; } _warned = true; try { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifHalloween] pumpkin drop filter failed (logged once, drops left as rolled): " + ex.Message)); } } catch { } } } private static bool IsPumpkin(GameObject go) { if ((Object)(object)go != (Object)null) { return string.Equals(PumpkinDropFilter.PrefabName(((Object)go).name), "BHP_PumpkinDrop", StringComparison.OrdinalIgnoreCase); } return false; } } internal static class EilifStatsReporter { private struct StatKey { public readonly string VhKey; public readonly PlayerStatType Type; public StatKey(string vhKey, PlayerStatType type) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) VhKey = vhKey; Type = type; } } internal static bool Enabled = true; private static readonly StatKey[] StatMap = new StatKey[19] { new StatKey("vh_EnemyKills", (PlayerStatType)6), new StatKey("vh_Builds", (PlayerStatType)2), new StatKey("vh_Crafts", (PlayerStatType)13), new StatKey("vh_DistanceTraveled", (PlayerStatType)16), new StatKey("vh_DistanceWalk", (PlayerStatType)17), new StatKey("vh_DistanceRun", (PlayerStatType)18), new StatKey("vh_DistanceSail", (PlayerStatType)19), new StatKey("vh_DistanceAir", (PlayerStatType)20), new StatKey("vh_ItemsPickedUp", (PlayerStatType)12), new StatKey("vh_FishCaught", (PlayerStatType)160), new StatKey("vh_FishHooked", (PlayerStatType)158), new StatKey("vh_FishLost", (PlayerStatType)159), new StatKey("vh_FishCaughtTier0", (PlayerStatType)161), new StatKey("vh_FishCaughtTier1", (PlayerStatType)162), new StatKey("vh_FishCaughtTier2", (PlayerStatType)163), new StatKey("vh_FishCaughtTier3", (PlayerStatType)164), new StatKey("vh_FishCaughtTier4", (PlayerStatType)165), new StatKey("vh_FishCaughtTier5", (PlayerStatType)166), new StatKey("vh_FishCaughtTier6", (PlayerStatType)167) }; internal static void Post(string reason) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || !EilifMapTrackerPlugin.IsOnServer()) { return; } Game instance = Game.instance; ZNet instance2 = ZNet.instance; Player localPlayer = Player.m_localPlayer; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)localPlayer == (Object)null) { return; } PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile == null) { return; } string playerName = localPlayer.GetPlayerName(); if (string.IsNullOrEmpty(playerName)) { return; } string text = instance2.GetWorldName() ?? ""; int num = 0; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < StatMap.Length; i++) { if (TryReadStat(playerProfile, StatMap[i].Type, out var value)) { if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(EilifMapTrackerPlugin.JsonStr(StatMap[i].VhKey)).Append(':').Append(value.ToString(CultureInfo.InvariantCulture)); num++; } } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.Append("{\"name\":").Append(EilifMapTrackerPlugin.JsonStr(playerName)); stringBuilder2.Append(",\"stats\":{").Append((object?)stringBuilder).Append("}}"); if (num != 0) { EilifMapTrackerPlugin.PostJson("{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"client\",\"reporter\":" + EilifMapTrackerPlugin.JsonStr(playerName) + ",\"world\":" + EilifMapTrackerPlugin.JsonStr(text) + ",\"players\":[" + stringBuilder2?.ToString() + "]}", "[EilifStats]", $"[EilifStats] posted {num} counters for {playerName} ({text}) [{reason}]"); } } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifStats] read/post failed: " + ex.Message)); } } } private static bool TryReadStat(PlayerProfile profile, PlayerStatType type, out long value) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) value = 0L; try { PlayerStats[] playerStats = profile.m_playerStats; float num = ((playerStats == null || playerStats.Length == 0 || playerStats[0] == null) ? profile.GetStat(type) : playerStats[0][type]); if (float.IsNaN(num) || float.IsInfinity(num)) { return false; } value = (long)Math.Round(num, MidpointRounding.AwayFromZero); return true; } catch { return false; } } } internal static class TombstoneKeeper { internal static readonly HashSet<ItemType> KeepTypes = new HashSet<ItemType>(); internal const string DefaultKeepTypes = "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Ammo, AmmoNonEquipable"; internal static void Configure(string csv) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) KeepTypes.Clear(); if (string.IsNullOrEmpty(csv)) { return; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { try { KeepTypes.Add((ItemType)Enum.Parse(typeof(ItemType), text, ignoreCase: true)); } catch { EilifMapTrackerPlugin.Log.LogWarning((object)("[EilifDeath] unknown item type '" + text + "' in KeepItemTypes - ignored.")); } } } } } [HarmonyPatch(typeof(Inventory), "MoveInventoryToGrave")] internal static class Patch_MoveInventoryToGrave { private static readonly List<ItemData> Flagged = new List<ItemData>(); private static void Prefix(Inventory original) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) Flagged.Clear(); try { if (TombstoneKeeper.KeepTypes.Count == 0 || original == null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.GetGlobalKey((GlobalKeys)19)) { return; } List<ItemData> allItems = original.GetAllItems(); if (allItems == null) { return; } foreach (ItemData item in allItems) { if (item != null && !item.m_equipped && item.m_shared != null && !item.m_shared.m_questItem && TombstoneKeeper.KeepTypes.Contains(item.m_shared.m_itemType)) { item.m_equipped = true; Flagged.Add(item); } } if (Flagged.Count > 0) { EilifMapTrackerPlugin.Log.LogInfo((object)$"[EilifDeath] tombstone keep-list spared {Flagged.Count} item(s)."); } } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifDeath] tombstone keep-list prefix failed: " + ex.Message)); } } } private static void Finalizer() { try { for (int i = 0; i < Flagged.Count; i++) { try { ItemData val = Flagged[i]; if (val != null) { val.m_equipped = false; } } catch { } } } catch { } finally { try { Flagged.Clear(); } catch { } } } } }