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 DudeWhatAreMyStats v0.13.0
BepInEx/plugins/DudeWhatAreMyStats.dll
Decompiled 6 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("DudeWhatAreMyStats")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.13.0.0")] [assembly: AssemblyInformationalVersion("0.13.0+44684df6a8df6fe0e8a077306c87373da7fb5e18")] [assembly: AssemblyProduct("DudeWhatAreMyStats")] [assembly: AssemblyTitle("DudeWhatAreMyStats")] [assembly: AssemblyVersion("0.13.0.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 DudeWhatAreMyStats { internal static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; public static ConsoleEvent <>9__0_3; public static ConsoleEvent <>9__0_4; public static ConsoleEvent <>9__0_5; public static ConsoleEvent <>9__0_6; internal void <Register>b__0_0(ConsoleEventArgs args) { if ((Object)(object)Player.m_localPlayer == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("No character loaded."); } } else { StatsPanel.Toggle(); } } internal void <Register>b__0_1(ConsoleEventArgs args) { PlayerListHud.Toggle(); Terminal context = args.Context; if (context != null) { context.AddString("Player list " + (DwamsConfig.ShowPlayerList.Value ? "on." : "off.")); } } internal void <Register>b__0_2(ConsoleEventArgs args) { StatsNetwork.RequestNow(); Terminal context = args.Context; if (context != null) { context.AddString("Asked everyone online, and the server, for stats."); } } internal void <Register>b__0_3(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Deaths((args.Length > 1) ? args[1] : null)); } } internal void <Register>b__0_4(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Fights((args.Length > 1) ? args[1] : null)); } } internal void <Register>b__0_5(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Creatures((args.Length > 1) ? args[1] : null)); } } internal void <Register>b__0_6(ConsoleEventArgs args) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("panel: {0} | holding pause: {1} | answered live: {2}", StatsPanel.IsOpen ? "open" : "closed", StatsPause.Holding, StatsNetwork.KnownCount + 1)); if (StatsNetwork.OtherVersionCount > 0) { stringBuilder.AppendLine("version: " + StatsNetwork.OtherVersionText); } if (StatsNetwork.StoredUnreadable > 0) { stringBuilder.AppendLine($"version: the server holds {StatsNetwork.StoredUnreadable} record(s) this build cannot read, so nobody offline is shown"); } stringBuilder.AppendLine(StatsNetwork.ServerHasStore ? $"server store: answering, {StatsNetwork.StoredCount} character(s) remembered" : "server store: no answer yet (the server may not run this mod, which only costs you offline players)"); stringBuilder.AppendLine(KillLog.Loaded ? $"kill log: {KillLog.Species} species watched, {CreatureCatalog.Count} creature(s) catalogued" : "kill log: not open yet"); int num = Progression.Tier(); stringBuilder.AppendLine((num > 0) ? $"progress: tier {num}, {CreatureCatalog.TierName(num)} ({Progression.Describe(num)})" : "progress: no world loaded, so kills cannot be ranked"); if (DwamsConfig.RecordDeaths.Value) { stringBuilder.AppendLine(DeathLog.Loaded ? ($"death log: {DeathLog.Recorded} recorded, {DeathLog.ReportCount} kept in full" + (DeathWatch.InFight ? $", in a fight with {DeathWatch.LiveCount} creature(s) right now" : "")) : "death log: not open yet"); } else { stringBuilder.AppendLine("death log: off (Record Deaths)"); } if (DwamsConfig.RecordFights.Value) { stringBuilder.AppendLine(FightLog.Loaded ? ($"fight log: {FightLog.BossCount} boss fight(s), {FightLog.RecentCount} other(s)" + ((FightWatch.LiveCount > 0) ? $", following {FightWatch.LiveCount} creature(s) right now" : "")) : "fight log: not open yet"); } else { stringBuilder.AppendLine("fight log: off (Record Fights)"); } if (StatsStore.IsServer) { stringBuilder.AppendLine(StatsStore.Loaded ? $"this game is the server: {StatsStore.Count} character(s) in {StatsStore.Path}" : "this game is the server, but the store is off or has no world yet"); } foreach (Snapshot item in StatsNetwork.Roster()) { string lastSeenText = item.LastSeenText; string text = (item.IsLocal ? "you" : (item.Online ? "online" : ((lastSeenText.Length > 0) ? ("last seen " + lastSeenText) : "offline"))); stringBuilder.AppendLine(item.Name + " (" + text + "): " + StatGroups.Count(item.KillsShown) + " kills, " + StatGroups.Count(item.Deaths) + " deaths, " + StatGroups.Count(item.Bosses) + " bosses, " + StatGroups.Duration(item.Played) + " played, best " + item.BestSkillText); } Terminal context = args.Context; if (context != null) { context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } } } internal static void Register() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if ((Object)(object)Player.m_localPlayer == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("No character loaded."); } } else { StatsPanel.Toggle(); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("dwams", "Dude What Are My Stats: open or close the stats panel", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { PlayerListHud.Toggle(); Terminal context = args.Context; if (context != null) { context.AddString("Player list " + (DwamsConfig.ShowPlayerList.Value ? "on." : "off.")); } }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("dwams_hud", "Dude What Are My Stats: show or hide the always-on player list", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { StatsNetwork.RequestNow(); Terminal context = args.Context; if (context != null) { context.AddString("Asked everyone online, and the server, for stats."); } }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("dwams_refresh", "Dude What Are My Stats: ask the other players for their stats again", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj4 = <>c.<>9__0_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Deaths((args.Length > 1) ? args[1] : null)); } }; <>c.<>9__0_3 = val4; obj4 = (object)val4; } new ConsoleCommand("dwams_deaths", "Dude What Are My Stats: what has killed you, and the story of the last few", (ConsoleEvent)obj4, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj5 = <>c.<>9__0_4; if (obj5 == null) { ConsoleEvent val5 = delegate(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Fights((args.Length > 1) ? args[1] : null)); } }; <>c.<>9__0_4 = val5; obj5 = (object)val5; } new ConsoleCommand("dwams_fights", "Dude What Are My Stats: who did what in the fights worth remembering", (ConsoleEvent)obj5, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj6 = <>c.<>9__0_5; if (obj6 == null) { ConsoleEvent val6 = delegate(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString(Creatures((args.Length > 1) ? args[1] : null)); } }; <>c.<>9__0_5 = val6; obj6 = (object)val6; } new ConsoleCommand("dwams_creatures", "Dude What Are My Stats: how each creature is classified for the kill breakdown", (ConsoleEvent)obj6, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj7 = <>c.<>9__0_6; if (obj7 == null) { ConsoleEvent val7 = delegate(ConsoleEventArgs args) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("panel: {0} | holding pause: {1} | answered live: {2}", StatsPanel.IsOpen ? "open" : "closed", StatsPause.Holding, StatsNetwork.KnownCount + 1)); if (StatsNetwork.OtherVersionCount > 0) { stringBuilder.AppendLine("version: " + StatsNetwork.OtherVersionText); } if (StatsNetwork.StoredUnreadable > 0) { stringBuilder.AppendLine($"version: the server holds {StatsNetwork.StoredUnreadable} record(s) this build cannot read, so nobody offline is shown"); } stringBuilder.AppendLine(StatsNetwork.ServerHasStore ? $"server store: answering, {StatsNetwork.StoredCount} character(s) remembered" : "server store: no answer yet (the server may not run this mod, which only costs you offline players)"); stringBuilder.AppendLine(KillLog.Loaded ? $"kill log: {KillLog.Species} species watched, {CreatureCatalog.Count} creature(s) catalogued" : "kill log: not open yet"); int num = Progression.Tier(); stringBuilder.AppendLine((num > 0) ? $"progress: tier {num}, {CreatureCatalog.TierName(num)} ({Progression.Describe(num)})" : "progress: no world loaded, so kills cannot be ranked"); if (DwamsConfig.RecordDeaths.Value) { stringBuilder.AppendLine(DeathLog.Loaded ? ($"death log: {DeathLog.Recorded} recorded, {DeathLog.ReportCount} kept in full" + (DeathWatch.InFight ? $", in a fight with {DeathWatch.LiveCount} creature(s) right now" : "")) : "death log: not open yet"); } else { stringBuilder.AppendLine("death log: off (Record Deaths)"); } if (DwamsConfig.RecordFights.Value) { stringBuilder.AppendLine(FightLog.Loaded ? ($"fight log: {FightLog.BossCount} boss fight(s), {FightLog.RecentCount} other(s)" + ((FightWatch.LiveCount > 0) ? $", following {FightWatch.LiveCount} creature(s) right now" : "")) : "fight log: not open yet"); } else { stringBuilder.AppendLine("fight log: off (Record Fights)"); } if (StatsStore.IsServer) { stringBuilder.AppendLine(StatsStore.Loaded ? $"this game is the server: {StatsStore.Count} character(s) in {StatsStore.Path}" : "this game is the server, but the store is off or has no world yet"); } foreach (Snapshot item in StatsNetwork.Roster()) { string lastSeenText = item.LastSeenText; string text = (item.IsLocal ? "you" : (item.Online ? "online" : ((lastSeenText.Length > 0) ? ("last seen " + lastSeenText) : "offline"))); stringBuilder.AppendLine(item.Name + " (" + text + "): " + StatGroups.Count(item.KillsShown) + " kills, " + StatGroups.Count(item.Deaths) + " deaths, " + StatGroups.Count(item.Bosses) + " bosses, " + StatGroups.Duration(item.Played) + " played, best " + item.BestSkillText); } Terminal context = args.Context; if (context != null) { context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } }; <>c.<>9__0_6 = val7; obj7 = (object)val7; } new ConsoleCommand("dwams_status", "Dude What Are My Stats: what the scoreboard currently knows", (ConsoleEvent)obj7, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static string Creatures(string filter) { if (!CreatureCatalog.Built) { CreatureCatalog.Build(); } List<CreatureInfo> list = CreatureCatalog.All(); if (list.Count == 0) { return "No creatures catalogued. This needs a world loaded."; } bool flag = string.Equals(filter, "all", StringComparison.OrdinalIgnoreCase); bool flag2 = !flag && !string.IsNullOrEmpty(filter); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; foreach (CreatureInfo item in list) { if (item.Tameable) { num6++; } switch (item.Kind) { case CreatureKind.Hostile: num++; break; case CreatureKind.Critter: num2++; break; case CreatureKind.Neutral: num3++; break; case CreatureKind.Boss: num4++; break; default: num5++; break; } } StringBuilder stringBuilder = new StringBuilder(); string text = $"{list.Count} creature(s): {num} hostile, {num2} critters, {num3} leave you alone, " + $"{num4} bosses, {num5} unplaced; {num6} can be tamed."; stringBuilder.AppendLine(text); DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] " + text)); if (!flag && !flag2) { stringBuilder.AppendLine("Showing the ones worth checking. Use dwams_creatures all for every row, or a word to filter."); } List<string> list2 = new List<string>(); foreach (CreatureInfo item2 in list) { if (item2.OverruledFaction) { list2.Add(item2.Display); } } if (list2.Count > 0) { string text2 = $"Counted harmless despite a monster faction ({list2.Count}): " + string.Join(", ", list2.ToArray()) + ". Anything in here that can hurt you is a bug."; stringBuilder.AppendLine(text2); DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] " + text2)); } int num7 = 0; foreach (CreatureInfo item3 in list) { bool flag3 = item3.Tameable || item3.Kind == CreatureKind.Critter || item3.Kind == CreatureKind.Neutral || item3.Kind == CreatureKind.Unknown; if (flag2) { if (item3.Key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0 && item3.Display.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0 && item3.Prefab.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0) { continue; } } else if (!flag && !flag3) { continue; } string text3 = Row(item3); stringBuilder.AppendLine(text3); DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] " + text3)); num7++; } if (num7 == 0) { stringBuilder.AppendLine("Nothing matched."); } else { stringBuilder.AppendLine($"All {num7} row(s) also written to the BepInEx log, which does scroll."); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static string Row(CreatureInfo c) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(CreatureCatalog.TierName(c.Tier).PadRight(12)).Append(' '); stringBuilder.Append(c.Display).Append(" (").Append(c.Key) .Append(") "); stringBuilder.Append(c.Kind.ToString().ToLowerInvariant()); stringBuilder.Append(", ").Append(c.Faction); stringBuilder.Append(", ").Append(Mathf.RoundToInt(c.Health)).Append(" hp"); if (c.OnlyEverTame) { stringBuilder.Append(", only ever tame"); } else if (c.Tameable) { stringBuilder.Append(", tameable"); } stringBuilder.Append(c.Armed ? ", can attack" : ", NO ATTACK"); if (c.Flees) { stringBuilder.Append(", flees"); } if (c.Overridden) { stringBuilder.Append(" <set by config>"); } else if (c.Reason.Length > 0) { stringBuilder.Append(" <").Append(c.Reason).Append(">"); } if (c.Attacks.Length > 0) { stringBuilder.Append(" [").Append(c.Attacks).Append("]"); } float num = KillSplit.AssumedTameShare(c.Key); if (num > 0f) { stringBuilder.Append(", old kills counted ").Append(Mathf.RoundToInt(num * 100f)).Append("% tame"); } KillTally killTally = KillLog.For(c.Key); if (killTally != null) { stringBuilder.Append(", watched ").Append(Mathf.RoundToInt(killTally.Seen)).Append(" (") .Append(Mathf.RoundToInt(killTally.Tame)) .Append(" tame)"); } return stringBuilder.ToString(); } private static string Fights(string which) { if (DwamsConfig.RecordFights != null && !DwamsConfig.RecordFights.Value) { return "Record Fights is off in the config, so nothing is being recorded."; } if (!FightLog.Loaded) { return "No fight log loaded yet. It opens once a character is in a world."; } List<FightReport> list = new List<FightReport>(); list.AddRange(FightLog.Bosses()); list.AddRange(FightLog.Recent()); if (list.Count == 0) { return "No fights recorded yet. Boss fights always count; others have to clear Fight Seconds and Fight Health, and only the game that owns the creature can report one."; } StringBuilder stringBuilder = new StringBuilder(); string text = (string.IsNullOrEmpty(which) ? null : which.Trim()); stringBuilder.AppendLine($"{FightLog.BossCount} boss fight(s) and {FightLog.RecentCount} other(s) recorded." + ((text == null) ? " Add a name to narrow it." : (" Showing those matching \"" + text + "\"."))); int num = 0; foreach (FightReport item in list) { if (text != null && item.Display.IndexOf(text, StringComparison.OrdinalIgnoreCase) < 0 && item.CreatureKey.IndexOf(text, StringComparison.OrdinalIgnoreCase) < 0) { continue; } num++; foreach (string item2 in item.Lines()) { stringBuilder.AppendLine(item2); } } if (num == 0) { stringBuilder.AppendLine("Nothing matched."); } string text2 = stringBuilder.ToString().TrimEnd(Array.Empty<char>()); string[] array = text2.Split(new char[1] { '\n' }); foreach (string text3 in array) { DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] " + text3.TrimEnd(Array.Empty<char>()))); } return text2; } private static string Deaths(string which) { StringBuilder stringBuilder = new StringBuilder(); if (DwamsConfig.RecordDeaths != null && !DwamsConfig.RecordDeaths.Value) { return "Record Deaths is off in the config, so nothing is being recorded."; } if (!DeathLog.Loaded) { return "No death log loaded yet. It opens once a character is in a world."; } List<DeathReport> list = DeathLog.Reports(); if (!string.IsNullOrEmpty(which)) { if (!int.TryParse(which, out var result) || result < 1 || result > list.Count) { return $"Pick a death between 1 and {list.Count}, 1 being the most recent."; } foreach (string item in list[result - 1].Lines(full: true)) { stringBuilder.AppendLine(item); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } stringBuilder.AppendLine($"{DeathLog.Recorded} death(s) recorded, {list.Count} kept in full. Add a number for one in detail."); List<KeyValuePair<string, float>> list2 = DeathLog.KilledBy(); if (list2.Count > 0) { stringBuilder.Append("killed by:"); foreach (KeyValuePair<string, float> item2 in list2) { stringBuilder.Append(' ').Append(DeathReport.CauseName(item2.Key)).Append(" x") .Append(Mathf.FloorToInt(item2.Value)); } stringBuilder.AppendLine(); } List<KeyValuePair<string, float>> list3 = DeathLog.FoughtWith(); if (list3.Count > 0) { stringBuilder.Append("in the fight:"); foreach (KeyValuePair<string, float> item3 in list3) { stringBuilder.Append(' ').Append(DeathReport.CauseName(item3.Key)).Append(" x") .Append(Mathf.FloorToInt(item3.Value)); } stringBuilder.AppendLine(); } if (list.Count == 0) { stringBuilder.AppendLine("No deaths recorded yet. Give it time."); } for (int i = 0; i < list.Count; i++) { stringBuilder.AppendLine($"{i + 1}. {list[i].Lines(full: false)[0]}"); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } } internal enum CreatureKind { Unknown, Critter, Neutral, Hostile, Boss, Player } internal sealed class CreatureInfo { internal string Key = ""; internal string Prefab = ""; internal CreatureKind InferredKind; internal Faction Faction; internal float Health; internal bool Tameable; internal bool Armed; internal bool Flees; internal string Attacks = ""; internal string Reason = ""; internal bool WildSpawns; internal int Tier; internal Biome Biome; internal CreatureKind Kind { get { CreatureKind creatureKind = CreatureCatalog.Forced(Key); if (creatureKind == CreatureKind.Unknown) { return InferredKind; } return creatureKind; } } internal bool Overridden => CreatureCatalog.Forced(Key) != CreatureKind.Unknown; internal bool OnlyEverTame { get { if (Tameable && !WildSpawns) { return Kind != CreatureKind.Hostile; } return false; } } internal string Display => DeathReport.CauseName(Key); internal bool OverruledFaction { get { if (Kind == CreatureKind.Critter && Reason == "no attack of any kind") { return IsMonsterFaction; } return false; } } internal bool IsMonsterFaction { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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) //IL_003f: Expected I4, but got Unknown Faction faction = Faction; switch (faction - 2) { case 0: case 1: case 2: case 3: case 4: case 5: case 7: case 11: return true; default: return false; } } } } internal static class CreatureCatalog { private static readonly Dictionary<string, CreatureInfo> _byKey = new Dictionary<string, CreatureInfo>(StringComparer.Ordinal); private static bool _built; private static readonly Dictionary<string, CreatureKind> _forced = new Dictionary<string, CreatureKind>(StringComparer.OrdinalIgnoreCase); private static string _forcedFrom; private static readonly KeyValuePair<Biome, int>[] Tiers = new KeyValuePair<Biome, int>[9] { new KeyValuePair<Biome, int>((Biome)1, 1), new KeyValuePair<Biome, int>((Biome)8, 2), new KeyValuePair<Biome, int>((Biome)2, 3), new KeyValuePair<Biome, int>((Biome)256, 3), new KeyValuePair<Biome, int>((Biome)4, 4), new KeyValuePair<Biome, int>((Biome)16, 5), new KeyValuePair<Biome, int>((Biome)512, 6), new KeyValuePair<Biome, int>((Biome)32, 7), new KeyValuePair<Biome, int>((Biome)64, 8) }; internal static bool Built => _built; internal static int Count => _byKey.Count; internal static CreatureKind Forced(string key) { string text = ((DwamsConfig.TreatCreaturesAs != null) ? DwamsConfig.TreatCreaturesAs.Value : ""); if (text != _forcedFrom) { _forcedFrom = text; _forced.Clear(); string[] array = (text ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); int num = text2.IndexOf(':'); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); string text4 = text2.Substring(num + 1).Trim(); if (text3.Length == 0) { continue; } CreatureKind value; if (text4.Equals("hostile", StringComparison.OrdinalIgnoreCase)) { value = CreatureKind.Hostile; } else if (text4.Equals("critter", StringComparison.OrdinalIgnoreCase)) { value = CreatureKind.Critter; } else if (text4.Equals("neutral", StringComparison.OrdinalIgnoreCase)) { value = CreatureKind.Neutral; } else { if (!text4.Equals("boss", StringComparison.OrdinalIgnoreCase)) { DudeWhatAreMyStatsMod.Log.LogWarning((object)("[DudeWhatAreMyStats] Could not read \"" + text2 + "\" in Treat Creatures As; expected hostile, critter, neutral or boss.")); continue; } value = CreatureKind.Boss; } _forced[text3] = value; } } if (_forced.Count == 0 || string.IsNullOrEmpty(key)) { return CreatureKind.Unknown; } if (_forced.TryGetValue(key, out var value2)) { return value2; } int num2 = key.LastIndexOf('_'); if (num2 >= 0 && num2 + 1 < key.Length && _forced.TryGetValue(key.Substring(num2 + 1), out var value3)) { return value3; } return CreatureKind.Unknown; } internal static void Reset() { _byKey.Clear(); _built = false; } internal static List<CreatureInfo> All() { List<CreatureInfo> list = new List<CreatureInfo>(_byKey.Values); list.Sort(delegate(CreatureInfo a, CreatureInfo b) { int num = a.Tier.CompareTo(b.Tier); if (num != 0) { return num; } int num2 = a.Health.CompareTo(b.Health); return (num2 != 0) ? num2 : string.Compare(a.Key, b.Key, StringComparison.Ordinal); }); return list; } internal static CreatureInfo Find(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (!_built) { Build(); } if (!_byKey.TryGetValue(key, out var value)) { return null; } return value; } internal static CreatureKind KindOf(string key) { CreatureInfo creatureInfo = Find(key); if (creatureInfo != null) { return creatureInfo.Kind; } if (!string.IsNullOrEmpty(key) && key[0] != '$') { return CreatureKind.Player; } return CreatureKind.Unknown; } internal static void Build() { _byKey.Clear(); ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } _built = true; try { List<GameObject> prefabs = instance.m_prefabs; for (int i = 0; i < prefabs.Count; i++) { GameObject val = prefabs[i]; if (!((Object)(object)val == (Object)null)) { Character component = val.GetComponent<Character>(); if (!((Object)(object)component == (Object)null)) { Add(val, component); } } } ReadBiomes(); DudeWhatAreMyStatsMod.Log.LogInfo((object)$"[DudeWhatAreMyStats] Catalogued {_byKey.Count} creature(s) from this world's prefabs."); } catch (Exception arg) { DudeWhatAreMyStatsMod.Log.LogWarning((object)$"[DudeWhatAreMyStats] Could not read the creature list, so kills cannot be sorted by type: {arg}"); } } private static void Add(GameObject go, Character character) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) string text = (character.m_name ?? "").Trim(); if (text.Length == 0) { return; } string reason; CreatureKind creatureKind = Classify(go, character, out reason); bool flag = (Object)(object)go.GetComponent<Tameable>() != (Object)null; if (_byKey.TryGetValue(text, out var value)) { if (character.m_health > value.Health) { value.Health = character.m_health; } if (flag) { value.Tameable = true; } if (creatureKind > value.InferredKind && value.InferredKind != CreatureKind.Boss) { value.InferredKind = creatureKind; value.Reason = reason; value.Armed = HasAttack(go); value.Flees = Flees(go); value.Attacks = AttackNames(go); } } else { _byKey[text] = new CreatureInfo { Key = text, Prefab = (((Object)go).name ?? ""), InferredKind = creatureKind, Faction = character.m_faction, Health = character.m_health, Tameable = flag, Armed = HasAttack(go), Flees = Flees(go), Attacks = AttackNames(go), Reason = reason, Tier = TierFromFaction(character.m_faction) }; } } private static CreatureKind Classify(GameObject go, Character character, out string reason) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected I4, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Invalid comparison between Unknown and I4 if (character.m_boss || (int)character.m_faction == 8) { reason = "boss"; return CreatureKind.Boss; } if ((Object)(object)go.GetComponent<Player>() != (Object)null) { reason = "a player"; return CreatureKind.Player; } if (!HasAttack(go)) { reason = "no attack of any kind"; return CreatureKind.Critter; } Faction faction = character.m_faction; switch (faction - 2) { case 0: case 1: case 2: case 3: case 4: case 5: case 7: case 11: reason = "monster faction"; return CreatureKind.Hostile; default: { BaseAI component = go.GetComponent<BaseAI>(); if ((int)character.m_faction == 10 || ((Object)(object)component != (Object)null && component.m_aggravatable)) { reason = "aggravatable"; return CreatureKind.Neutral; } reason = "armed"; return CreatureKind.Hostile; } } } internal static bool Flees(GameObject go) { MonsterAI component = go.GetComponent<MonsterAI>(); if ((Object)(object)component != (Object)null) { return component.m_fleeIfNotAlerted; } return false; } internal static bool HasAttack(GameObject go) { if (HasDamagingAoe(go)) { return true; } Humanoid component = go.GetComponent<Humanoid>(); if ((Object)(object)component == (Object)null) { return false; } if (Damaging(component.m_unarmedWeapon)) { return true; } if (Any(component.m_defaultItems)) { return true; } if (Any(component.m_randomWeapon)) { return true; } if (component.m_randomSets != null) { ItemSet[] randomSets = component.m_randomSets; foreach (ItemSet val in randomSets) { if (val != null && Any(val.m_items)) { return true; } } } if (component.m_randomItems != null) { RandomItem[] randomItems = component.m_randomItems; foreach (RandomItem val2 in randomItems) { if (val2 != null && Damaging(val2.m_prefab)) { return true; } } } return false; } internal static string AttackNames(GameObject go) { Humanoid component = go.GetComponent<Humanoid>(); if ((Object)(object)component == (Object)null) { return ""; } List<string> list = new List<string>(); if (Damaging(component.m_unarmedWeapon)) { list.Add("fists: " + ((Object)component.m_unarmedWeapon).name + " " + Mathf.RoundToInt(((DamageTypes)(ref component.m_unarmedWeapon.m_itemData.m_shared.m_damages)).GetTotalDamage())); } Collect(component.m_defaultItems, list); Collect(component.m_randomWeapon, list); if (component.m_randomSets != null) { ItemSet[] randomSets = component.m_randomSets; foreach (ItemSet val in randomSets) { if (val != null) { Collect(val.m_items, list); } } } if (component.m_randomItems != null) { RandomItem[] randomItems = component.m_randomItems; foreach (RandomItem val2 in randomItems) { if (val2 != null) { Collect((GameObject[])(object)new GameObject[1] { val2.m_prefab }, list); } } } if (list.Count != 0) { return string.Join(", ", list.ToArray()); } return ""; } private static void Collect(GameObject[] items, List<string> into) { if (items == null) { return; } foreach (GameObject val in items) { if (!((Object)(object)val == (Object)null) && into.Count < 6) { ItemDrop component = val.GetComponent<ItemDrop>(); SharedData val2 = (((Object)(object)component != (Object)null && component.m_itemData != null) ? component.m_itemData.m_shared : null); if (val2 != null && !(((DamageTypes)(ref val2.m_damages)).GetTotalDamage() <= 0f)) { into.Add(((Object)val).name + " " + Mathf.RoundToInt(((DamageTypes)(ref val2.m_damages)).GetTotalDamage())); } } } } private static bool Any(GameObject[] items) { if (items == null) { return false; } for (int i = 0; i < items.Length; i++) { if (Damaging(items[i])) { return true; } } return false; } private static bool HasDamagingAoe(GameObject go) { Aoe[] componentsInChildren = go.GetComponentsInChildren<Aoe>(true); if (componentsInChildren == null) { return false; } Aoe[] array = componentsInChildren; foreach (Aoe val in array) { if (!((Object)(object)val == (Object)null) && ((DamageTypes)(ref val.m_damage)).GetTotalDamage() > 0f) { return true; } } return false; } private static bool Damaging(GameObject item) { if ((Object)(object)item != (Object)null) { return Damaging(item.GetComponent<ItemDrop>()); } return false; } private static bool Damaging(ItemDrop drop) { SharedData val = (((Object)(object)drop != (Object)null && drop.m_itemData != null) ? drop.m_itemData.m_shared : null); if (val != null) { return ((DamageTypes)(ref val.m_damages)).GetTotalDamage() > 0f; } return false; } internal static string TierName(int tier) { return tier switch { 1 => "Meadows", 2 => "Black Forest", 3 => "Swamp", 4 => "Mountain", 5 => "Plains", 6 => "Mistlands", 7 => "Ashlands", 8 => "Deep North", _ => "anywhere", }; } private static int TierFromFaction(Faction faction) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected I4, but got Unknown return (faction - 1) switch { 0 => 1, 1 => 2, 2 => 3, 5 => 3, 4 => 4, 6 => 5, 8 => 6, 9 => 6, 3 => 7, 12 => 8, _ => 0, }; } private static void ReadBiomes() { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) SpawnSystem val = Object.FindAnyObjectByType<SpawnSystem>(); if ((Object)(object)val == (Object)null || val.m_spawnLists == null) { return; } foreach (SpawnSystemList spawnList in val.m_spawnLists) { if ((Object)(object)spawnList == (Object)null || spawnList.m_spawners == null) { continue; } foreach (SpawnData spawner in spawnList.m_spawners) { if (spawner == null || (Object)(object)spawner.m_prefab == (Object)null) { continue; } Character component = spawner.m_prefab.GetComponent<Character>(); if ((Object)(object)component == (Object)null) { continue; } string text = (component.m_name ?? "").Trim(); if (text.Length != 0 && _byKey.TryGetValue(text, out var value)) { value.WildSpawns = true; int num = LowestTier(spawner.m_biome); if (num > 0 && ((int)value.Biome == 0 || num < value.Tier)) { value.Tier = num; value.Biome = spawner.m_biome; } } } } } private static int LowestTier(Biome mask) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) int num = 0; KeyValuePair<Biome, int>[] tiers = Tiers; for (int i = 0; i < tiers.Length; i++) { KeyValuePair<Biome, int> keyValuePair = tiers[i]; if ((mask & keyValuePair.Key) != 0 && (num == 0 || keyValuePair.Value < num)) { num = keyValuePair.Value; } } return num; } } internal static class DeathLog { private const int FileSchema = 1; private const float SaveDebounce = 3f; private const float SaveRetry = 30f; private static readonly Dictionary<string, float> _killedBy = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly Dictionary<string, float> _foughtWith = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly List<DeathReport> _reports = new List<DeathReport>(); private static int _recorded; private static string _path; private static bool _loaded; private static bool _dirty; private static float _saveAt; private static long _profileId; private static float _nextLoadTry; internal static bool Loaded => _loaded; internal static string Path => _path; internal static int Recorded => _recorded; internal static int ReportCount => _reports.Count; internal static void Load() { if (DwamsConfig.RecordDeaths == null || !DwamsConfig.RecordDeaths.Value) { return; } Game instance = Game.instance; PlayerProfile val = (((Object)(object)instance != (Object)null) ? instance.GetPlayerProfile() : null); if (val == null || ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated())) { return; } long playerID = val.GetPlayerID(); string name = val.GetName(); if (string.IsNullOrEmpty(name) || (_loaded && playerID == _profileId)) { return; } Unload(); _profileId = playerID; _path = System.IO.Path.Combine(Paths.ConfigPath, "DudeWhatAreMyStats", Sanitize(name) + "_" + playerID + ".deaths.bin"); _loaded = true; try { if (!File.Exists(_path)) { DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] No death log for " + name + " yet; recording from now on (" + _path + ").")); } else { FromBytes(File.ReadAllBytes(_path)); DudeWhatAreMyStatsMod.Log.LogInfo((object)$"[DudeWhatAreMyStats] Loaded {_recorded} recorded death(s) and {_reports.Count} report(s) for {name}."); } } catch (Exception arg) { DudeWhatAreMyStatsMod.Log.LogError((object)$"[DudeWhatAreMyStats] Could not read {_path}: {arg}"); Preserve("corrupt"); Clear(); } } internal static void Unload() { SaveIfDirty(); Clear(); _path = null; _loaded = false; _profileId = 0L; } private static void Clear() { _killedBy.Clear(); _foughtWith.Clear(); _reports.Clear(); _recorded = 0; _dirty = false; } internal static void Update() { if (!_loaded) { float unscaledTime = Time.unscaledTime; if (!(unscaledTime < _nextLoadTry)) { _nextLoadTry = unscaledTime + 1f; Load(); } } else if (_dirty && Time.unscaledTime >= _saveAt) { Save(); } } internal static void SaveIfDirty() { if (_loaded && _dirty) { Save(); } } internal static void Record(DeathReport report) { if (report == null) { return; } if (!_loaded) { Load(); } if (!_loaded) { return; } _recorded++; Bump(_killedBy, report.Cause); foreach (Participant participant in report.Participants) { if (participant.Key != report.Cause) { Bump(_foughtWith, participant.Key); } } _reports.Insert(0, report); int num = ((DwamsConfig.KeepReports != null) ? DwamsConfig.KeepReports.Value : 20); if (num >= 0 && _reports.Count > num) { _reports.RemoveRange(num, _reports.Count - num); } if (DwamsConfig.LogDeaths != null && DwamsConfig.LogDeaths.Value) { foreach (string item in report.Lines(full: true)) { DudeWhatAreMyStatsMod.Log.LogInfo((object)("[DudeWhatAreMyStats] " + item)); } } MarkDirty(); } private static void Bump(Dictionary<string, float> counts, string key) { if (!string.IsNullOrEmpty(key)) { counts[key] = (counts.TryGetValue(key, out var value) ? value : 0f) + 1f; } } internal static List<KeyValuePair<string, float>> KilledBy(int top = 0) { return Sorted(_killedBy, top); } internal static List<KeyValuePair<string, float>> FoughtWith(int top = 0) { return Sorted(_foughtWith, top); } private static List<KeyValuePair<string, float>> Sorted(Dictionary<string, float> counts, int top) { List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>(counts); list.Sort(delegate(KeyValuePair<string, float> a, KeyValuePair<string, float> b) { int num = b.Value.CompareTo(a.Value); return (num == 0) ? string.Compare(a.Key, b.Key, StringComparison.Ordinal) : num; }); if (top > 0 && list.Count > top) { list.RemoveRange(top, list.Count - top); } return list; } internal static List<DeathReport> Reports() { return _reports; } private static void MarkDirty() { _dirty = true; _saveAt = Time.unscaledTime + 3f; } internal static void Save() { if (!_loaded || _path == null) { return; } try { Directory.CreateDirectory(System.IO.Path.GetDirectoryName(_path)); string text = _path + ".tmp"; File.WriteAllBytes(text, ToBytes()); if (File.Exists(_path)) { File.Replace(text, _path, null); } else { File.Move(text, _path); } _dirty = false; } catch (Exception ex) { _saveAt = Time.unscaledTime + 30f; DudeWhatAreMyStatsMod.Log.LogError((object)$"[DudeWhatAreMyStats] Could not save {_path}; trying again in {30f:0}s: {ex.Message}"); } } private static byte[] ToBytes() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(_recorded); val.Write(_killedBy.Count); foreach (KeyValuePair<string, float> item in _killedBy) { val.Write(item.Key); val.Write(item.Value); } val.Write(_foughtWith.Count); foreach (KeyValuePair<string, float> item2 in _foughtWith) { val.Write(item2.Key); val.Write(item2.Value); } val.Write(_reports.Count); foreach (DeathReport report in _reports) { ZPackage val2 = new ZPackage(); report.Pack(val2); val.Write(val2.GetArray()); } return val.GetArray(); } private static void FromBytes(byte[] bytes) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown Clear(); if (bytes == null || bytes.Length == 0) { return; } ZPackage val = new ZPackage(bytes); int num = val.ReadInt(); if (num != 1) { Preserve("fileschema"); DudeWhatAreMyStatsMod.Log.LogWarning((object)($"[DudeWhatAreMyStats] The death log is file version {num} and this build reads {1}. " + "The old file has been kept alongside; starting empty.")); return; } _recorded = val.ReadInt(); int num2 = val.ReadInt(); for (int i = 0; i < num2; i++) { string key = val.ReadString(); _killedBy[key] = val.ReadSingle(); } int num3 = val.ReadInt(); for (int j = 0; j < num3; j++) { string key2 = val.ReadString(); _foughtWith[key2] = val.ReadSingle(); } int num4 = val.ReadInt(); for (int k = 0; k < num4; k++) { byte[] array = val.ReadByteArray(); try { _reports.Add(DeathReport.Unpack(new ZPackage(array))); } catch (Exception ex) { DudeWhatAreMyStatsMod.Log.LogWarning((object)("[DudeWhatAreMyStats] Skipped an unreadable death report: " + ex.Message)); } } } private static void Preserve(string why) { try { File.Copy(_path, _path + "." + why + "-" + DateTime.Now.ToString("yyyyMMdd-HHmmss"), overwrite: true); } catch { } } private static string Sanitize(string s) { char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars(); StringBuilder stringBuilder = new StringBuilder(); string text = s ?? ""; foreach (char c in text) { stringBuilder.Append((Array.IndexOf(invalidFileNameChars, c) >= 0) ? '_' : c); } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "viking"; } } [HarmonyPatch(typeof(Character), "Damage")] internal static class Character_Damage_DeathWatch_Patch { [HarmonyPrefix] private static void Prefix(Character __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && DeathWatch.Enabled && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { Character val = null; try { val = (hit.HaveAttacker() ? hit.GetAttacker() : null); } catch { } if ((Object)(object)val == (Object)(object)__instance) { val = null; } bool dodged = false; try { dodged = hit.m_dodgeable && __instance.IsDodgeInvincible(); } catch { } DeathWatch.RecordAttempt(val, hit, dodged); } } } [HarmonyPatch(typeof(Character), "ApplyDamage")] internal static class Character_ApplyDamage_DeathWatch_Patch { [HarmonyPrefix] private static void Prefix(Character __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && DeathWatch.Enabled && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !__instance.IsDead() && !__instance.IsTeleporting() && !__instance.InCutscene()) { DeathWatch.RecordDamage(hit); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class Humanoid_BlockAttack_DeathWatch_Patch { [HarmonyPrefix] private static void Prefix(Humanoid __instance, ref float __state) { __state = -1f; if (DeathWatch.Enabled) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { __state = val.GetStamina(); } } } [HarmonyPostfix] private static void Postfix(Humanoid __instance, Character attacker, bool __result, float __state) { if (__state < 0f) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null)) { float num = __state - val.GetStamina(); if (__result || !(num <= 0f)) { DeathWatch.RecordBlock(attacker, (num > 0f) ? num : 0f); } } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class Player_OnDeath_DeathWatch_Patch { private static readonly FieldRef<Character, HitData> LastHit = AccessTools.FieldRefAccess<Character, HitData>("m_lastHit"); [HarmonyPostfix] private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && DeathWatch.Enabled) { HitData hit = null; try { hit = LastHit.Invoke((Character)(object)__instance); } catch { } DeathWatch.Seal(__instance, hit); } } } internal enum Involvement { Present, Pressed, Hurt, Killer } internal sealed class Participant { internal string Key = ""; internal string Display = ""; internal int Level = 1; internal bool IsPlayer; internal Involvement Involvement; internal int Attempts; internal int Landed; internal int Blocked; internal int Dodged; internal float Damage; internal float StaminaDrained; internal float SecondsTargeting; internal float BiggestHit; internal string Weapon = ""; internal bool Inferred; internal void Raise(Involvement to) { if (to > Involvement) { Involvement = to; } } internal void Pack(ZPackage pkg) { pkg.Write(Key ?? ""); pkg.Write(Display ?? ""); pkg.Write(Level); pkg.Write(IsPlayer); pkg.Write((int)Involvement); pkg.Write(Attempts); pkg.Write(Landed); pkg.Write(Blocked); pkg.Write(Dodged); pkg.Write(Damage); pkg.Write(StaminaDrained); pkg.Write(SecondsTargeting); pkg.Write(BiggestHit); pkg.Write(Weapon ?? ""); pkg.Write(Inferred); } internal static Participant Unpack(ZPackage pkg) { return new Participant { Key = pkg.ReadString(), Display = pkg.ReadString(), Level = pkg.ReadInt(), IsPlayer = pkg.ReadBool(), Involvement = (Involvement)pkg.ReadInt(), Attempts = pkg.ReadInt(), Landed = pkg.ReadInt(), Blocked = pkg.ReadInt(), Dodged = pkg.ReadInt(), Damage = pkg.ReadSingle(), StaminaDrained = pkg.ReadSingle(), SecondsTargeting = pkg.ReadSingle(), BiggestHit = pkg.ReadSingle(), Weapon = pkg.ReadString(), Inferred = pkg.ReadBool() }; } } internal sealed class DeathReport { internal const string EnvironmentPrefix = "@"; internal long WhenUtc; internal int Day; internal string Biome = ""; internal bool AtNight; internal string Cause = ""; internal string CauseDisplay = ""; internal string KillerWeapon = ""; internal int KillerLevel = 1; internal bool KillerIsPlayer; internal string HitType = ""; internal readonly List<KeyValuePair<string, float>> Blow = new List<KeyValuePair<string, float>>(); internal float BlowTotal; internal bool BlowRanged; internal bool BlowBackstab; internal bool BlowBlockable; internal float MaxHealth; internal float Armor; internal string MyWeapon = ""; internal readonly List<string> Effects = new List<string>(); internal float FightSeconds; internal int HitsTaken; internal float BiggestHit; internal float StaminaSpentBlocking; internal readonly List<Participant> Participants = new List<Participant>(); internal bool IsEnvironment { get { if (Cause != null) { return Cause.StartsWith("@", StringComparison.Ordinal); } return false; } } internal string WhenText { get { long num = Snapshot.NowUtc() - WhenUtc; if (num < 90) { return "just now"; } if (num < 3600) { return num / 60 + "m ago"; } if (num < 86400) { return num / 3600 + "h ago"; } return num / 86400 + "d ago"; } } internal List<string> Lines(bool full) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(WhenText).Append(" - ").Append(Headline()); if (Day > 0) { stringBuilder.Append(" (day ").Append(Day); if (AtNight) { stringBuilder.Append(", night"); } if (Biome.Length > 0) { stringBuilder.Append(", ").Append(Biome); } stringBuilder.Append(")"); } list.Add(stringBuilder.ToString()); if (!full) { return list; } StringBuilder stringBuilder2 = new StringBuilder(" blow: "); stringBuilder2.Append(StatGroups.Count(BlowTotal)).Append(" damage"); if (Blow.Count > 0) { stringBuilder2.Append(" ("); for (int i = 0; i < Blow.Count; i++) { if (i > 0) { stringBuilder2.Append(", "); } stringBuilder2.Append(Blow[i].Key).Append(' ').Append(StatGroups.Count(Blow[i].Value)); } stringBuilder2.Append(")"); } if (HitType.Length > 0) { stringBuilder2.Append(", ").Append(HitType); } if (BlowRanged) { stringBuilder2.Append(", ranged"); } if (BlowBackstab) { stringBuilder2.Append(", backstab"); } if (!BlowBlockable) { stringBuilder2.Append(", unblockable"); } list.Add(stringBuilder2.ToString()); StringBuilder stringBuilder3 = new StringBuilder(" you: "); stringBuilder3.Append(StatGroups.Count(MaxHealth)).Append(" max health, ").Append(StatGroups.Count(Armor)) .Append(" armor"); if (MyWeapon.Length > 0) { stringBuilder3.Append(", holding ").Append(MyWeapon); } if (Effects.Count > 0) { stringBuilder3.Append(", ").Append(string.Join(" + ", Effects.ToArray())); } list.Add(stringBuilder3.ToString()); StringBuilder stringBuilder4 = new StringBuilder(" fight: "); stringBuilder4.Append(StatGroups.Duration(FightSeconds)).Append(", ").Append(HitsTaken) .Append(" hit(s) taken"); if (BiggestHit > 0f) { stringBuilder4.Append(", worst ").Append(StatGroups.Count(BiggestHit)); } if (StaminaSpentBlocking > 0f) { stringBuilder4.Append(", ").Append(StatGroups.Count(StaminaSpentBlocking)).Append(" stamina blocking"); } list.Add(stringBuilder4.ToString()); foreach (Participant item in Sorted()) { list.Add(" " + Row(item)); } return list; } internal string Headline() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append((CauseDisplay.Length > 0) ? CauseDisplay : "something unknown"); if (KillerWeapon.Length > 0) { stringBuilder.Append(" (").Append(KillerWeapon).Append(")"); } int num = 0; foreach (Participant participant in Participants) { if (participant.Involvement != Involvement.Killer) { num++; } } if (num > 0) { stringBuilder.Append(", with ").Append(num).Append((num == 1) ? " other in the fight" : " others in the fight"); } return stringBuilder.ToString(); } internal List<Participant> Sorted() { List<Participant> list = new List<Participant>(Participants); list.Sort(delegate(Participant a, Participant b) { int num = b.Involvement.CompareTo(a.Involvement); if (num != 0) { return num; } int num2 = b.Damage.CompareTo(a.Damage); return (num2 != 0) ? num2 : b.SecondsTargeting.CompareTo(a.SecondsTargeting); }); return list; } internal static string Row(Participant p) { StringBuilder stringBuilder = new StringBuilder(); if (p.Involvement == Involvement.Killer) { stringBuilder.Append("killed you: "); } stringBuilder.Append(p.Display); List<string> list = new List<string>(); if (p.Damage > 0f) { list.Add(StatGroups.Count(p.Damage) + " damage" + (p.Inferred ? " (inferred)" : "")); } if (p.Landed > 0) { list.Add(p.Landed + " hit(s)"); } if (p.Blocked > 0) { list.Add(p.Blocked + " blocked"); } if (p.Dodged > 0) { list.Add(p.Dodged + " dodged"); } if (p.StaminaDrained > 0f) { list.Add(StatGroups.Count(p.StaminaDrained) + " stamina"); } if (list.Count == 0 && p.SecondsTargeting > 0f) { list.Add("on you " + StatGroups.Duration(p.SecondsTargeting)); } if (list.Count == 0) { list.Add("in the fight"); } stringBuilder.Append(": ").Append(string.Join(", ", list.ToArray())); return stringBuilder.ToString(); } internal static string CauseName(string key) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(key)) { return "(unknown)"; } if (key[0] == "@"[0]) { string text = key.Substring(1); try { return DeathWatch.EnvironmentName((HitType)Enum.Parse(typeof(HitType), text)); } catch { return text; } } if (key[0] == '#') { return key.Substring(1); } if (key[0] == '$' && Localization.instance != null) { string text2 = Localization.instance.Localize(key); if (!string.IsNullOrEmpty(text2) && text2 != key && !text2.StartsWith("[", StringComparison.Ordinal)) { return text2; } } return key.TrimStart(new char[1] { '$' }); } internal void Pack(ZPackage pkg) { pkg.Write(WhenUtc); pkg.Write(Day); pkg.Write(Biome ?? ""); pkg.Write(AtNight); pkg.Write(Cause ?? ""); pkg.Write(CauseDisplay ?? ""); pkg.Write(KillerWeapon ?? ""); pkg.Write(KillerLevel); pkg.Write(KillerIsPlayer); pkg.Write(HitType ?? ""); pkg.Write(Blow.Count); foreach (KeyValuePair<string, float> item in Blow) { pkg.Write(item.Key ?? ""); pkg.Write(item.Value); } pkg.Write(BlowTotal); pkg.Write(BlowRanged); pkg.Write(BlowBackstab); pkg.Write(BlowBlockable); pkg.Write(MaxHealth); pkg.Write(Armor); pkg.Write(MyWeapon ?? ""); pkg.Write(Effects.Count); foreach (string effect in Effects) { pkg.Write(effect ?? ""); } pkg.Write(FightSeconds); pkg.Write(HitsTaken); pkg.Write(BiggestHit); pkg.Write(StaminaSpentBlocking); pkg.Write(Participants.Count); foreach (Participant participant in Participants) { participant.Pack(pkg); } } internal static DeathReport Unpack(ZPackage pkg) { DeathReport deathReport = new DeathReport { WhenUtc = pkg.ReadLong(), Day = pkg.ReadInt(), Biome = pkg.ReadString(), AtNight = pkg.ReadBool(), Cause = pkg.ReadString(), CauseDisplay = pkg.ReadString(), KillerWeapon = pkg.ReadString(), KillerLevel = pkg.ReadInt(), KillerIsPlayer = pkg.ReadBool(), HitType = pkg.ReadString() }; int num = pkg.ReadInt(); for (int i = 0; i < num; i++) { string key = pkg.ReadString(); deathReport.Blow.Add(new KeyValuePair<string, float>(key, pkg.ReadSingle())); } deathReport.BlowTotal = pkg.ReadSingle(); deathReport.BlowRanged = pkg.ReadBool(); deathReport.BlowBackstab = pkg.ReadBool(); deathReport.BlowBlockable = pkg.ReadBool(); deathReport.MaxHealth = pkg.ReadSingle(); deathReport.Armor = pkg.ReadSingle(); deathReport.MyWeapon = pkg.ReadString(); int num2 = pkg.ReadInt(); for (int j = 0; j < num2; j++) { deathReport.Effects.Add(pkg.ReadString()); } deathReport.FightSeconds = pkg.ReadSingle(); deathReport.HitsTaken = pkg.ReadInt(); deathReport.BiggestHit = pkg.ReadSingle(); deathReport.StaminaSpentBlocking = pkg.ReadSingle(); int num3 = pkg.ReadInt(); for (int k = 0; k < num3; k++) { deathReport.Participants.Add(Participant.Unpack(pkg)); } return deathReport; } } internal static class DeathWatch { private const float SampleInterval = 0.5f; private const float PresenceRange = 40f; private const int MaxParticipants = 24; private static readonly Dictionary<ZDOID, Participant> _live = new Dictionary<ZDOID, Participant>(); private static readonly Dictionary<string, ZDOID> _lastDot = new Dictionary<string, ZDOID>(); private static float _fightStart; private static float _lastContact; private static float _nextSample; private static float _lastSample; private static int _hitsTaken; private static float _biggestHit; private static float _staminaBlocking; private static bool _capped; private static float _lastSeal; private static bool _failed; internal static bool Enabled { get { if (!_failed && DwamsConfig.RecordDeaths != null) { return DwamsConfig.RecordDeaths.Value; } return false; } } internal static bool InFight => _fightStart > 0f; internal static int LiveCount => _live.Count; internal static void Reset() { _live.Clear(); _lastDot.Clear(); _fightStart = 0f; _lastContact = 0f; _hitsTaken = 0; _biggestHit = 0f; _staminaBlocking = 0f; _capped = false; } internal static void Update() { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float time = Time.time; if (_fightStart > 0f && time - _lastContact > DwamsConfig.FightGapSeconds.Value) { Reset(); } if (time < _nextSample) { return; } float num = ((_lastSample > 0f) ? Mathf.Min(time - _lastSample, 2f) : 0.5f); _lastSample = time; _nextSample = time + 0.5f; if (!DwamsConfig.RecordBystanders.Value) { return; } try { Vector3 position = ((Component)localPlayer).transform.position; float num2 = 1600f; List<Character> allCharacters = Character.GetAllCharacters(); for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)localPlayer || val.IsDead() || val.IsPlayer()) { continue; } Vector3 val2 = ((Component)val).transform.position - position; if (((Vector3)(ref val2)).sqrMagnitude > num2) { continue; } BaseAI baseAI = val.GetBaseAI(); if ((Object)(object)baseAI == (Object)null || (Object)(object)baseAI.GetTargetCreature() != (Object)(object)localPlayer) { continue; } Participant participant = Touch(val); if (participant != null) { participant.SecondsTargeting += num; _lastContact = time; if (_fightStart <= 0f) { _fightStart = time; } } } } catch (Exception e) { Warn("Could not sweep for nearby attackers", e); } } private static Participant Touch(Character c, bool swinging = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) ZDOID zDOID = c.GetZDOID(); if (zDOID == ZDOID.None) { return null; } if (_live.TryGetValue(zDOID, out var value)) { if (swinging) { string text = WeaponName(c); if (text.Length > 0) { value.Weapon = text; } } return value; } if (_live.Count >= 24) { _capped = true; return null; } Participant participant = new Participant { Key = NameKey(c), Display = Describe(c), Level = c.GetLevel(), IsPlayer = (c is Player), Weapon = WeaponName(c), Involvement = Involvement.Present }; _live[zDOID] = participant; return participant; } private static void Contact() { float time = Time.time; if (_fightStart <= 0f) { _fightStart = time; } _lastContact = time; } internal static void RecordAttempt(Character attacker, HitData hit, bool dodged) { //IL_001d: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return; } try { Contact(); if ((Object)(object)attacker == (Object)null) { return; } ZDOID zDOID = attacker.GetZDOID(); if (zDOID != ZDOID.None) { if (hit.m_damage.m_poison > 0f) { _lastDot["poison"] = zDOID; } if (hit.m_damage.m_fire > 0f) { _lastDot["fire"] = zDOID; } if (hit.m_damage.m_spirit > 0f) { _lastDot["spirit"] = zDOID; } } Participant participant = Touch(attacker, swinging: true); if (participant != null) { participant.Attempts++; if (dodged) { participant.Dodged++; } participant.Raise(Involvement.Pressed); } } catch (Exception e) { Warn("Could not record an attack", e); } } internal static void RecordDamage(HitData hit) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return; } try { Contact(); float num = hit.GetTotalDamage() * Game.m_localDamgeTakenRate; bool flag = num > 0.1f; bool flag2 = DotKind(hit.m_hitType) != null && !hit.HaveAttacker(); if (flag && !flag2) { _hitsTaken++; if (num > _biggestHit) { _biggestHit = num; } } bool inferred; Participant participant = Resolve(hit, out inferred); if (participant == null) { return; } if (!flag) { participant.Raise(Involvement.Pressed); return; } participant.Damage += num; if (!flag2) { participant.Landed++; if (num > participant.BiggestHit) { participant.BiggestHit = num; } } if (inferred) { participant.Inferred = true; } participant.Raise(Involvement.Hurt); } catch (Exception e) { Warn("Could not record damage taken", e); } } internal static void RecordBlock(Character attacker, float stamina) { if (!Enabled) { return; } try { Contact(); if (stamina > 0f) { _staminaBlocking += stamina; } if ((Object)(object)attacker == (Object)null) { return; } Participant participant = Touch(attacker); if (participant != null) { participant.Blocked++; if (stamina > 0f) { participant.StaminaDrained += stamina; } participant.Raise(Involvement.Pressed); } } catch (Exception e) { Warn("Could not record a block", e); } } private static Participant Resolve(HitData hit, out bool inferred) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) inferred = false; Character val = (hit.HaveAttacker() ? hit.GetAttacker() : null); if ((Object)(object)val != (Object)null) { return Touch(val); } if (hit.m_attacker != ZDOID.None && _live.TryGetValue(hit.m_attacker, out var value)) { return value; } string text = DotKind(hit.m_hitType); if (text == null) { return null; } if (_lastDot.TryGetValue(text, out var value2) && _live.TryGetValue(value2, out var value3)) { inferred = true; return value3; } if (text == "fire" && _lastDot.TryGetValue("spirit", out var value4) && _live.TryGetValue(value4, out var value5)) { inferred = true; return value5; } return null; } private static string DotKind(HitType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 if ((int)type == 7) { return "poison"; } if ((int)type == 5) { return "fire"; } return null; } internal unsafe static void Seal(Player player, HitData hit) { //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return; } float time = Time.time; if (_lastSeal > 0f && time - _lastSeal < 2f) { return; } _lastSeal = time; try { DeathReport deathReport = new DeathReport { WhenUtc = Snapshot.NowUtc() }; if ((Object)(object)EnvMan.instance != (Object)null) { deathReport.Day = EnvMan.instance.GetDay(); deathReport.AtNight = EnvMan.IsNight(); } deathReport.Biome = BiomeName(player); deathReport.MaxHealth = ((Character)player).GetMaxHealth(); deathReport.Armor = ((Character)player).GetBodyArmor(); deathReport.MyWeapon = WeaponName((Character)(object)player); ReadEffects(player, deathReport.Effects); Participant value = null; if (hit != null) { deathReport.HitType = ((object)Unsafe.As<HitType, HitType>(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString(); deathReport.BlowRanged = hit.m_ranged; deathReport.BlowBackstab = hit.m_backstabBonus > 1f; deathReport.BlowBlockable = hit.m_blockable; deathReport.BlowTotal = hit.GetTotalDamage(); Breakdown(hit, deathReport.Blow); Character val = (hit.HaveAttacker() ? hit.GetAttacker() : null); if ((Object)(object)val != (Object)null) { value = Touch(val); } else if (hit.m_attacker != ZDOID.None) { _live.TryGetValue(hit.m_attacker, out value); } if (value == null) { string text = DotKind(hit.m_hitType); if (text != null && _lastDot.TryGetValue(text, out var value2)) { _live.TryGetValue(value2, out value); } } } if (value != null) { value.Raise(Involvement.Killer); deathReport.Cause = value.Key; deathReport.CauseDisplay = value.Display; deathReport.KillerWeapon = value.Weapon; deathReport.KillerLevel = value.Level; deathReport.KillerIsPlayer = value.IsPlayer; } else { HitType type = (HitType)((hit != null) ? ((int)hit.m_hitType) : 0); deathReport.Cause = "@" + ((object)(*(HitType*)(&type))/*cast due to .constrained prefix*/).ToString(); deathReport.CauseDisplay = EnvironmentName(type); } float time2 = Time.time; deathReport.FightSeconds = ((_fightStart > 0f) ? Mathf.Max(0f, time2 - _fightStart) : 0f); deathReport.HitsTaken = _hitsTaken; deathReport.BiggestHit = _biggestHit; deathReport.StaminaSpentBlocking = _staminaBlocking; float value3 = DwamsConfig.BystanderSeconds.Value; foreach (Participant value4 in _live.Values) { if (value4.Involvement != Involvement.Present || !(value4.SecondsTargeting < value3)) { deathReport.Participants.Add(value4); } } if (_capped) { DudeWhatAreMyStatsMod.Log.LogInfo((object)$"[DudeWhatAreMyStats] That fight had more than {24} creatures in it; the rest are not in the report."); } DeathLog.Record(deathReport); } catch (Exception e) { Warn("Could not record that death", e); } finally { Reset(); } } internal static string NameKey(Character c) { if ((Object)(object)c == (Object)null) { return ""; } Player val = (Player)(object)((c is Player) ? c : null); if (val != null) { string playerName = val.GetPlayerName(); return "#" + (string.IsNullOrEmpty(playerName) ? "someone" : playerName); } string text = (c.m_name ?? "").Trim(); if (text.StartsWith("$", StringComparison.Ordinal)) { return text; } return PrefabName(c); } internal static string PrefabName(Character c) { if ((Object)(object)c == (Object)null) { return ""; } string text = ((Object)((Component)c).gameObject).name ?? ""; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num < 0) { return text; } return text.Substring(0, num); } internal static string Describe(Character c) { if ((Object)(object)c == (Object)null) { return "something"; } Player val = (Player)(object)((c is Player) ? c : null); if (val != null) { string playerName = val.GetPlayerName(); if (!string.IsNullOrEmpty(playerName)) { return playerName; } return "someone"; } string text = ((Localization.instance != null) ? Localization.instance.Localize(c.m_name ?? "").Trim() : ""); if (text.Length == 0) { text = PrefabName(c); } if (c.IsTamed()) { string text2 = ""; ZNetView component = ((Component)c).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { text2 = (component.GetZDO().GetString(ZDOVars.s_tamedName, "") ?? "").Trim(); } if (text2.Length <= 0) { return "tame " + text; } return text2 + " the " + text; } if (c.IsBoss()) { return text; } int level = c.GetLevel(); if (level <= 1) { return text; } return level - 1 + "-star " + text; } internal static string WeaponName(Character c) { try { Character obj = ((c is Humanoid) ? c : null); ItemData val = ((obj != null) ? ((Humanoid)obj).GetCurrentWeapon() : null); if (val == null) { return ""; } if (val.m_shared != null && !string.IsNullOrEmpty(val.m_shared.m_name) && Localization.instance != null) { string text = Localization.instance.Localize(val.m_shared.m_name).Trim(); if (text.Length > 0 && !text.StartsWith("[", StringComparison.Ordinal)) { return text; } } return ((Object)(object)val.m_dropPrefab != (Object)null) ? (((Object)val.m_dropPrefab).name ?? "") : ""; } catch { return ""; } } internal static string EnvironmentName(HitType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected I4, but got Unknown return (type - 1) switch { 2 => "a fall", 3 => "drowning", 7 => "drowning", 4 => "burning", 5 => "the cold", 6 => "poison", 8 => "smoke", 9 => "the edge of the world", 12 => "a tree", 11 => "a cart", 16 => "a boat", 14 => "a collapsing building", 23 => "a drawbridge", 15 => "a ballista", 18 => "a catapult", 17 => "a stalactite", 22 => "an incinerator", 19 => "cinders", 21 => "lava", 20 => "boiling water", 10 => "an impact", 13 => "your own hand", 0 => "something unknown", 1 => "another player", _ => "something unknown", }; } private unsafe static string BiomeName(Player player) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { Biome currentBiome = player.GetCurrentBiome(); if ((int)currentBiome == 0) { return ""; } string text = ((Localization.instance != null) ? Localization.instance.Localize("$biome_" + ((object)(*(Biome*)(¤tBiome))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()).Trim() : ""); if (text.Length > 0 && !text.StartsWith("[", StringComparison.Ordinal)) { return text; } return ((object)(*(Biome*)(¤tBiome))/*cast due to .constrained prefix*/).ToString(); } catch { return ""; } } private static void ReadEffects(Player player, List<string> into) { try { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan == null) { return; } foreach (StatusEffect statusEffect in sEMan.GetStatusEffects()) { if (!((Object)(object)statusEffect == (Object)null) && into.Count < 8) { string text = ((Localization.instance != null) ? Localization.instance.Localize(statusEffect.m_name ?? "").Trim() : ""); if (text.Length == 0 || text.StartsWith("[", StringComparison.Ordinal)) { text = ((Object)statusEffect).name; } if (!string.IsNullOrEmpty(text)) { into.Add(text); } continue; } break; } } catch { } } private static void Breakdown(HitData hit, List<KeyValuePair<string, float>> into) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) DamageTypes damage = hit.m_damage; Add(into, "blunt", damage.m_blunt); Add(into, "slash", damage.m_slash); Add(into, "pierce", damage.m_pierce); Add(into, "chop", damage.m_chop); Add(into, "pickaxe", damage.m_pickaxe); Add(into, "fire", damage.m_fire); Add(into, "frost", damage.m_frost); Add(into, "lightning", damage.m_lightning); Add(into, "poison", damage.m_poison); Add(into, "spirit", damage.m_spirit); into.Sort((KeyValuePair<string, float> a, KeyValuePair<string, float> b) => b.Value.CompareTo(a.Value)); } private static void Add(List<KeyValuePair<string, float>> into, string name, float value) { if (value > 0f) { into.Add(new KeyValuePair<string, float>(name, value)); } } private static void Warn(string what, Exception e) { if (!_failed) { _failed = true; DudeWhatAreMyStatsMod.Log.LogWarning((object)$"[DudeWhatAreMyStats] {what}; death details are off for the rest of this session. {e}"); } } } [BepInPlugin("DeathMonger.DudeWhatAreMyStats", "Dude What Are My Stats", "0.13.0")] public class DudeWhatAreMyStatsMod : BaseUnityPlugin { public const string ModGuid = "DeathMonger.DudeWhatAreMyStats"; public const string ModName = "Dude What Are My Stats"; public const string ModVersion = "0.13.0"; internal static ConfigEntry<bool> ModEnabled; private readonly Harmony _harmony = new Harmony("DeathMonger.DudeWhatAreMyStats"); internal static DudeWhatAreMyStatsMod Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Mod Enabled", true, "Master toggle for the entire mod. Set to false to disable every patch, the stats panel and the console command without removing the DLL. Requires a game restart to take effect."); if (!ModEnabled.Value) { Log.LogInfo((object)"[DudeWhatAreMyStats] Mod Enabled = false in config; skipping patches and commands."); return; } DwamsConfig.Bind(this); Commands.Register(); _harmony.PatchAll(); Log.LogInfo((object)"[DudeWhatAreMyStats] 0.13.0 loaded."); } private void Update() { if (ModEnabled.Value) { UiKit.ObserveFocus(); StatsNetwork.Update(); StatsStore.Update(); DeathLog.Update(); KillLog.Update(); FightLog.Update(); FightWatch.Update(); if ((Object)(object)Player.m_localPlayer == (Object)null) { StatsPanel.Close(); PlayerListHud.Hide(); FightWatch.Clear(); } else { StatsPanel.Update(); PlayerListHud.Update(); DeathWatch.Update(); } } } private void OnDestroy() { StatsPause.Release(); _harmony.UnpatchSelf(); } internal ConfigEntry<T> BindLocal<T>(string section, string key, T defaultValue, string description) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } internal ConfigEntry<float> BindLocalRange(string section, string key, float defaultValue, float min, float max, string description) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())); } internal ConfigEntry<int> BindLocalRangeInt(string section, string key, int defaultValue, int min, int max, string description) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>())); } internal static void Message(string text) { if (!((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } internal static class DwamsConfig { internal static ConfigEntry<KeyboardShortcut> OpenKey; internal static ConfigEntry<bool> PauseWhileOpen; internal static ConfigEntry<bool> AskOtherPlayers; internal static ConfigEntry<float> RefreshSeconds; internal static ConfigEntry<bool> RememberOfflinePlayers; internal static ConfigEntry<float> PushMinutes; internal static ConfigEntry<bool> ServerStoreEnabled; internal static ConfigEntry<float> ServerKeepDays; internal static ConfigEntry<int> ServerMaxCharacters; internal static ConfigEntry<bool> ShowZeroStats; internal static ConfigEntry<string> SortColumn; internal static ConfigEntry<bool> SortDescending; internal static ConfigEntry<string> CollapsedGroups; internal static ConfigEntry<string> PanelSize; internal static ConfigEntry<string> PanelPosition; internal static ConfigEntry<int> ListScrollRows; internal static ConfigEntry<int> TopCreatureCount; internal static ConfigEntry<bool> ShowPlayerList; internal static ConfigEntry<KeyboardShortcut> PlayerListKey; internal static ConfigEntry<bool> PlayerListIncludeOffline; internal static ConfigEntry<float> PlayerListRefreshSeconds; internal static ConfigEntry<int> PlayerListMaxRows; internal static ConfigEntry<HudCorner> PlayerListCorner; internal static ConfigEntry<string> PlayerListOffset; internal static ConfigEntry<float> PlayerListFontSize; internal static ConfigEntry<bool> RecordDeaths; internal static ConfigEntry<bool> RecordBystanders; internal static ConfigEntry<float> BystanderSeconds; internal static ConfigEntry<float> FightGapSeconds; internal static ConfigEntry<int> KeepReports; internal static ConfigEntry<int> TopDeathCauses; internal static ConfigEntry<bool> LogDeaths; internal static ConfigEntry<bool> RecordKills; internal static ConfigEntry<string> AssumeTameShare; internal static ConfigEntry<bool> KillsColumnHostileOnly; internal static ConfigEntry<string> TreatCreaturesAs; internal static ConfigEntry<bool> ShowKillKindColumns; internal static ConfigEntry<bool> RecordFights; internal static ConfigEntry<float> FightSeconds; internal static ConfigEntry<float> FightHealth; internal static ConfigEntry<int> KeepFights; internal static ConfigEntry<bool> AnnounceFights; internal static ConfigEntry<bool> LogFights; internal static ConfigEntry<bool> FixTreasureDiscoveryCount; internal static void Bind(DudeWhatAreMyStatsMod mod) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) OpenKey = mod.BindLocal<KeyboardShortcut>("Keys", "Open Stats", new KeyboardShortcut((KeyCode)105, Array.Empty<KeyCode>()), "Opens and closes the stats panel. Escape closes it too."); PauseWhileOpen = mod.BindLocal("Behaviour", "Pause While Open", defaultValue: true, "Pause the game while the stats panel is open, the way the ESC menu does. This goes through the game's own pause, so it only takes effect when the game would let the ESC menu pause it: alone in a solo or hosted game. On a server with other players online nothing freezes, unless Pause My Server is installed and grants the pause."); AskOtherPlayers = mod.BindLocal("Behaviour", "Ask Other Players", defaultValue: true, "Ask everyone else online for their stats so the scoreboard can compare you. Each player's own game answers for them, so only players who also run this mod appear. Turning this off works both ways: you stop asking, you stop answering, and the server is told to forget the record it already holds, so you leave everyone else's scoreboard as well as emptying your own."); RefreshSeconds = mod.BindLocalRange("Behaviour", "Refresh Seconds", 10f, 0f, 120f, "How often to ask the other players again while the panel is open. 0 asks only when the panel opens and when you press Refresh."); RememberOfflinePlayers = mod.BindLocal("Behaviour", "Show Offline Players", defaultValue: true, "List players who are not online, as well as those who are. That covers anyone who logs out while you are still playing, and, if the server also runs this mod, everyone it remembers from before you logged in, each marked with how long ago they were last seen. Without the mod on the server there is nowhere to remember anyone, so only the first case applies. Turn off to list only players online now."); PushMinutes = mod.BindLocalRange("Behaviour", "Push Minutes", 5f, 0f, 60f, "How often to hand your own stats to the server, so it can show them to others once you have logged off. It also happens when you open the panel and when you leave the world. 0 stops sending them at all, which keeps you off the board for anyone who was not online at the same time as you. A server without this mod ignores them either way."); ServerStoreEnabled = mod.BindLocal("Server", "Server Store Enabled", defaultValue: true, "Server-side setting, ignored on a client. Keep a record of each character's last known stats so the scoreboard can show players who are not online. The file lives in BepInEx/config/DudeWhatAreMyStats/, one per world. Turn off to keep nothing, which leaves every client showing only the players online."); ServerKeepDays = mod.BindLocalRange("Server", "Server Keep Days", 30f, 0f, 365f, "Server-side setting, ignored on a client. Forget a character the server has not heard from in this many days, so a world does not accumulate one-time visitors forever. 0 keeps every character for good."); ServerMaxCharacters = mod.BindLocalRangeInt("Server", "Server Max Characters", 100, 10, 200, "Server-side setting, ignored on a client. The most characters to remember; past this the least recently seen are dropped. The whole set travels to a client in one message, and an oversized one would jam that player's connection rather than merely fail, so this is a real ceiling and not just housekeeping. 100 is far more than a group of friends will ever need."); ShowZeroStats = mod.BindLocal("Display", "Show Zero Stats", defaultValue: false, "Show stats that are still zero in the Details tab. Off hides them, which is most of the 200-odd stats the game tracks."); SortColumn = mod.BindLocal("Display", "Sort Column", "Kills", "Which scoreboard column is sorted, remembered between sessions. Click a column header to change it rather than editing this."); SortDescending = mod.BindLocal("Display", "Sort Descending", defaultValue: true, "Whether the sorted scoreboard column runs highest first. Click a column header twice to flip it."); CollapsedGroups = mod.BindLocal("Display", "Collapsed Groups", "", "Which sections are folded away in the Details tab, kept between sessions. Click a section header, or Expand all / Collapse all, in the panel itself rather than editing this."); PanelSize = mod.BindLocal("Display", "Panel Size", "980,620", "Width and height of the stats panel, remembered when you drag its bottom-right corner."); PanelPosition = mod.BindLocal("Display", "Panel Position", "0,0", "Where the stats panel sits, as an offset from the screen center, remembered when you drag it by its title. Set to 0,0 to put it back in the middle."); ListScrollRows = mod.BindLocalRangeInt("Display", "List Scroll Rows", 4, 1, 20, "How many rows a list moves per notch of the mouse wheel."); TopCreatureCount = mod.BindLocalRangeInt("Display", "Top Creature Count", 15, 0, 100, "How many creatures to list in the Details tab's Creatures killed section, most killed first. 0 hides it."); ShowPlayerList = mod.BindLocal("Player List", "Show Player List", defaultValue: false, "Keep a small list of players and how many times each has died on screen all the time, most deaths first. It stays up over the map and the inventory, and steps aside only when the game hides its own HUD (Ctrl+F3), during cutscenes, behind the pause menu and the death or teleport fade, and while the stats panel is open. Off by default. The console command dwams_hud turns it on and off too."); PlayerListKey = mod.BindLocal<KeyboardShortcut>("Player List", "Toggle Key", new KeyboardShortcut((KeyCode)0, Array.Empty<KeyCode>()), "A key that turns the player list on and off. Unbound by default, since almost every letter is taken by the game or another mod; pick one that is free in your own setup. It stands down while you are typing in any text box."); PlayerListIncludeOffline = mod.BindLocal("Player List", "Include Offline Players", defaultValue: false, "Also list players who are not online, from the server's record of them. Off keeps the list to the people actually playing, which is what an always-visible list is usually for. Needs the mod on the server to have anyone to show."); PlayerListRefreshSeconds = mod.BindLocalRange("Player List", "Refresh Seconds", 30f, 5f, 300f, "How often the list asks the other players for their numbers while the stats panel is closed. Deaths change rarely, so this can be slow. Your own count is always current."); PlayerListMaxRows = mod.BindLocalRangeInt("Player List", "Max Rows", 10, 1, 30, "The most players to list. Any beyond that are summed up as \"+N more\"."); PlayerListCorner = mod.BindLocal("Player List", "Corner", HudCorner.TopLeft, "Which corner of the screen the list sits in."); PlayerListOffset = mod.BindLocal("Player List", "Offset", "16,300", "How far in from that corner, as x,y in pixels at 1080p. Both numbers are measured inward from the corner, whichever corner it is. The default sits on the left, below the hotbar; move it if it overlaps something else on your screen."); PlayerListFontSize = mod.BindLocalRange("Player List", "Font Size", 16f, 10f, 30f, "Text size of the player list."); RecordKills = mod.BindLocal("Kills", "Record Kills", defaultValue: true, "Record what you kill, which Valheim does not do in full: it files a kill under the species and nothing else, so a tame wolf and a wild one are the same row and a 2-star troll is just a troll. This notes the pet and the stars as kills happen. The client that owns the creature is the one holding those details, so it tells everyone who damaged it, exactly as the game already does when it credits the kill. Off records nothing new; what is already recorded is kept."); AssumeTameShare = mod.BindLocal("Kills", "Assume Tame Share", "Boar:100, Wolf:100", "What to assume about kills made before this mod was recording them, as a list of species and the percentage of those to count as pets rather than wild animals. The game kept no record either way, so a long-running world has to be told. This applies ONLY to kills nothing watched: anything recorded since is exact, so the guesswork shrinks every time you play and never touches a kill made from now on. Species not listed are counted as wild. Write the short name, as in Boar:100, or a percentage for a world that killed some of each, as in Wolf:70. A creature the world never spawns by itself and a player can tame needs no entry: every one that ever died was somebody's, which the hen and the chicken are, so they are counted as pets without being listed. Name one here anyway to overrule that. The dwams_creatures console command lists every name you can use and says which can be tamed, and which only ever exist because a player made them."); TreatCreaturesAs = mod.BindLocal("Kills", "Treat Creatures As", "", "Overrule how a creature is classified, as a list like Seal:hostile, Lox Calf:critter. The four values are hostile, critter, neutral and boss. The mod works out what a creature is by reading your world's own prefabs, which is right for everything anyone has checked, but nothing can recognise every way a creature might hurt you, and one that damages you by some unusual means reads as harmless. The dwams_creatures console command names the rows where this mod overruled the game's own grouping, which is where a mistake would be; anything there that can actually hurt you belongs in this list. Empty means trust what the prefabs say."); ShowKillKindColumns = mod.BindLocal("Kills", "Show Tame And Critter Columns", defaultValue: false, "Add Tame and Critters columns to the scoreboard beside Kills. Off by default: ten columns is a lot to read, and the same numbers are in the Details tab under Kills by type. The columns that stay spread out to fill the width either way. Takes effect the next time the panel opens."); RecordFights = mod.BindLocal("Fights", "Record Fights", defaultValue: true, "Record how much damage each player did to a creature over a fight, and report it when the creature dies. Boss fights are always reported and kept; smaller ones have to clear the two thresholds below, or every greydwarf would file a report. The game that owns the creature is the only one that sees every blow, so it does the adding up and tells everyone who took part. On a dedicated server that is the server, which has to be running this mod for the whole fight to be counted."); FightSeconds = mod.BindLocal("Fights", "Fight Seconds", 15f, "How long a fight has to last to be worth a report. Bosses ignore this."); FightHealth = mod.BindLocal("Fights", "Fight Health", 200f, "How much health the creature needs for a fight to be worth a report. Bosses ignore this. The default catches trolls and lox and leaves the greydwarves alone."); KeepFights = mod.BindLocal("Fights", "Keep Fights", 25, "How many non-boss fight reports to keep. Boss fights are kept separately and do not roll over."); AnnounceFights = mod.BindLocal("Fights", "Announce Fights", defaultValue: true, "Show a line when a reported fight ends, saying what your share of it was."); LogFights = mod.BindLocal("Fights", "Log Fights", defaultValue: false, "Also write every fight report to the BepInEx log, in full."); KillsColumnHostileOnly = mod.BindLocal("Kills", "Kills Column Hostile Only", defaultValue: true, "Show only hostile kills in the scoreboard's Kills column, which is what people mean by a kill count: pets, deer, fish and chickens come out of it and get their own columns. Off puts the column back to the game's own total, which counts everything that ever died by your hand and is the number every other stats mod shows."); RecordDeaths = mod.BindLocal("Deaths", "Record Deaths", defaultValue: true, "Keep track of what kills you: which creature, what it was holding, its star level, and everyone else who was in the fight. Valheim counts your deaths and sorts them by category but never records which creature, so this is the mod watching your fights as they happen. It runs entirely on your own game, costs nothing until you are actually in a fight, and needs nothing on the server. Off keeps no record and leaves the tables empty; what is already recorded is kept."); RecordBystanders = mod.BindLocal("Deaths", "Record Bystanders", defaultValue: true, "Also record creatures that were in the fight without landing a blow. Ten greydwarves that surround you and drain your stamina are part of why the troll got you, so they belong in the record. This is the one part that looks around rather than waiting to be hit: twice a second it checks which of the loaded creatures have you as their target. Off records only what actually reached you."); BystanderSeconds = mod.BindLocalRange("Deaths", "Bystander Seconds", 3f, 0f, 30f, "How long something must have you in its sights before it counts as having been in the fight. This keeps a boar that glanced at you from across a field out of the record. Anything that swung at you, hurt you or killed you is recorded however brief it was."); FightGapSeconds = mod.BindLocalRange("Deaths", "Fight Gap Seconds", 10f, 2f, 60f, "How long