Decompiled source of StatsLogger v1.2.4
gnp_StatsLogger/ValheimStatsLogger.dll
Decompiled 2 months 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.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using HarmonyLib; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimStatsLogger")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ValheimStatsLogger")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("5c29271f-5dca-4909-aabc-9558286a0409")] [assembly: AssemblyFileVersion("1.2.4.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.2.4.0")] namespace ValheimStatsLogger; [HarmonyPatch] public static class BlockAndStagger { [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static class Humanoid_BlockAttack_BlockAndStagger { private static MethodInfo miBlockDamage = AccessTools.Method(typeof(HitData), "BlockDamage", (Type[])null, (Type[])null); private static MethodInfo miStagger = AccessTools.Method(typeof(Character), "Stagger", (Type[])null, (Type[])null); private static MethodInfo miBlockTrigger = AccessTools.Method(typeof(Humanoid_BlockAttack_BlockAndStagger), "BlockTrigger", (Type[])null, (Type[])null); private static MethodInfo miStaggerTrigger = AccessTools.Method(typeof(Humanoid_BlockAttack_BlockAndStagger), "StaggerTrigger", (Type[])null, (Type[])null); private static readonly int Inserts = 2; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown int num = 0; List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Callvirt && (MethodInfo)list[i].operand == miBlockDamage) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)miBlockTrigger)); list.Insert(i + 1, list[i - 1]); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_1, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); DebugLogger.Log($"{typeof(Humanoid_BlockAttack_BlockAndStagger)} [Humanoid BlockAttack {miBlockTrigger.Name}] applied."); num++; } if (list[i].opcode == OpCodes.Callvirt && (MethodInfo)list[i].operand == miStagger && list[i - 4].opcode == OpCodes.Ldarg_2) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)miStaggerTrigger)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_1, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); DebugLogger.Log($"{typeof(Humanoid_BlockAttack_BlockAndStagger)} [Humanoid BlockAttack {miStaggerTrigger.Name}] applied."); num++; } } if (num == 0) { DebugLogger.LogError($"{typeof(Humanoid_BlockAttack_BlockAndStagger)} [Humanoid BlockAttack] failed."); } else if (num != Inserts) { DebugLogger.LogWarning($"{typeof(Humanoid_BlockAttack_BlockAndStagger)} [Humanoid BlockAttack] applied {num}/{Inserts}."); } return list.AsEnumerable(); } public static void BlockTrigger(Humanoid target, HitData hit, Character attacker, float dmg) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (StatsLogger.IsLocalPlayer((Character)(object)target)) { ItemData weapon = null; if (((object)attacker).GetType() == typeof(Humanoid)) { weapon = ((Humanoid)attacker).GetCurrentWeapon(); } StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Blocks, attacker, weapon, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.BlockedDamage, attacker, weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Blocks, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.BlockedDamage, dmg); } } public static void StaggerTrigger(Humanoid target, HitData hit, Character attacker) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (StatsLogger.IsLocalPlayer((Character)(object)target)) { ItemData weapon = null; if (((object)attacker).GetType() == typeof(Humanoid)) { weapon = ((Humanoid)attacker).GetCurrentWeapon(); } StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Staggers, attacker, weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Staggers, 1f); } } } } internal class ConsoleAndChat { [HarmonyPatch(typeof(Terminal), "InitTerminal")] private class AddCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Postfix>b__0_0(ConsoleEventArgs args) { //IL_0480: Unknown result type (might be due to invalid IL or missing references) if (args.Args.Length == 1) { args.Context.AddString("<color=purple>Stats Logger (by givenameplz)</color> [1.2.4]"); args.Context.AddString("Stats Logging for your Characters!"); args.Context.AddString(""); args.Context.AddString("statslogger chat <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones in the Chatbox, this is LOCAL to the player only.)</color>"); args.Context.AddString("statslogger console <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones in the Console.)</color>"); args.Context.AddString("statslogger effect <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones with a level up effect.)</color>"); args.Context.AddString("statslogger cooldown <color=#6666ff>[minutes:int]</color> <color=#666666>(Announce Milestones only every <n> minutes so you don't get spammed.)</color>"); args.Context.AddString("statslogger retail <color=#666666>(Outputs the Stats tracked by Valheim: Kills, Deaths, Crafts, Builds and MANY MORE!)</color>"); args.Context.AddString("statslogger reset <color=#666666>(Reset all stats on the current character.)</color>"); args.Context.AddString("statslogger hidedeaths <color=#66ff66>[value:bool]</color> <color=#666666>(Hides any Player Death related stats.)</color>"); args.Context.AddString(""); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger chat false"); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger cooldown 5"); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger inckills false"); args.Context.AddString(""); args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } else if (args.HasArgumentAnywhere("debug", 0, true)) { StatsLogger.Stats.DebugMessagesInUnityConsole = !StatsLogger.Stats.DebugMessagesInUnityConsole; args.Context.AddString($"Debug output in UnityConsole. ({StatsLogger.Stats.DebugMessagesInUnityConsole})"); } else if (args.HasArgumentAnywhere("announce", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result)) { StatsLogger.Stats.AnnounceChat = result; StatsLogger.Stats.AnnounceConsole = result; StatsLogger.Stats.AnnounceEffect = result; args.Context.AddString("Announce Stats Milestones: " + (result ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones'"); } } else { args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("reset", 0, true)) { if ((Object)(object)Player.m_localPlayer != (Object)null) { if (StatsLogger.Stats.ResetFlag) { if (args.HasArgumentAnywhere(Player.m_localPlayer.GetPlayerName().ToLower(), 0, true)) { StatsLogger.Stats.ResetData(Player.m_localPlayer.GetPlayerName()); args.Context.AddString("All StatsLogger Stats have been reset for '" + Player.m_localPlayer.GetPlayerName() + "'."); } StatsLogger.Stats.ResetFlag = false; } else { StatsLogger.Stats.ResetFlag = true; args.Context.AddString("<color=red>WARNING!</color> Do you want to reset ALL stats logged by the StatsLogger Mod for this Character? Type '/statslogger reset " + Player.m_localPlayer.GetPlayerName() + "' to confirm."); } } else { args.Context.AddString("<color=red>Not logged in with any character.</color>"); } } else { if (args.HasArgumentAnywhere("retail", 0, true)) { if (!((Object)(object)Game.instance != (Object)null)) { return; } PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (playerProfile == null || playerProfile.m_playerStats == null) { return; } { foreach (KeyValuePair<PlayerStatType, float> stat in playerProfile.m_playerStats.m_stats) { args.Context.AddString($"{stat.Key}: {stat.Value}"); } return; } } if (args.HasArgumentAnywhere("hidedeaths", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result2)) { StatsLogger.Stats.HideDeathStats = result2; args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Hide Player Death Stats'"); } } else { args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("chat", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result3)) { StatsLogger.Stats.AnnounceChat = result3; args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones in Chatbox (Local)'"); } } else { args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("console", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result4)) { StatsLogger.Stats.AnnounceConsole = result4; args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones in Console'"); } } else { args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("effect", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result5)) { StatsLogger.Stats.AnnounceEffect = result5; args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones with Effect'"); } } else { args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("cooldown", 0, true)) { if (args.Args.Length == 3) { if (int.TryParse(args.Args[2], out var result6) && result6 >= 0) { StatsLogger.Stats.AnnounceIntervalMinutes = result6; args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones Cooldown'"); } } else { args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); } } else { args.Context.AddString("<color=red>Error:</color> '" + args.FullLine + "' is not a recognized command for StatsLogger! Type 'statslogger' to see a list of valid commands."); } } } } private static void Postfix(Terminal __instance) { //IL_0032: 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 object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { //IL_0480: Unknown result type (might be due to invalid IL or missing references) if (args.Args.Length == 1) { args.Context.AddString("<color=purple>Stats Logger (by givenameplz)</color> [1.2.4]"); args.Context.AddString("Stats Logging for your Characters!"); args.Context.AddString(""); args.Context.AddString("statslogger chat <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones in the Chatbox, this is LOCAL to the player only.)</color>"); args.Context.AddString("statslogger console <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones in the Console.)</color>"); args.Context.AddString("statslogger effect <color=#66ff66>[value:bool]</color> <color=#666666>(Announce Milestones with a level up effect.)</color>"); args.Context.AddString("statslogger cooldown <color=#6666ff>[minutes:int]</color> <color=#666666>(Announce Milestones only every <n> minutes so you don't get spammed.)</color>"); args.Context.AddString("statslogger retail <color=#666666>(Outputs the Stats tracked by Valheim: Kills, Deaths, Crafts, Builds and MANY MORE!)</color>"); args.Context.AddString("statslogger reset <color=#666666>(Reset all stats on the current character.)</color>"); args.Context.AddString("statslogger hidedeaths <color=#66ff66>[value:bool]</color> <color=#666666>(Hides any Player Death related stats.)</color>"); args.Context.AddString(""); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger chat false"); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger cooldown 5"); args.Context.AddString("<color=orange>EXAMPLE:</color> statslogger inckills false"); args.Context.AddString(""); args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } else if (args.HasArgumentAnywhere("debug", 0, true)) { StatsLogger.Stats.DebugMessagesInUnityConsole = !StatsLogger.Stats.DebugMessagesInUnityConsole; args.Context.AddString($"Debug output in UnityConsole. ({StatsLogger.Stats.DebugMessagesInUnityConsole})"); } else if (args.HasArgumentAnywhere("announce", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result)) { StatsLogger.Stats.AnnounceChat = result; StatsLogger.Stats.AnnounceConsole = result; StatsLogger.Stats.AnnounceEffect = result; args.Context.AddString("Announce Stats Milestones: " + (result ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones'"); } } else { args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("reset", 0, true)) { if ((Object)(object)Player.m_localPlayer != (Object)null) { if (StatsLogger.Stats.ResetFlag) { if (args.HasArgumentAnywhere(Player.m_localPlayer.GetPlayerName().ToLower(), 0, true)) { StatsLogger.Stats.ResetData(Player.m_localPlayer.GetPlayerName()); args.Context.AddString("All StatsLogger Stats have been reset for '" + Player.m_localPlayer.GetPlayerName() + "'."); } StatsLogger.Stats.ResetFlag = false; } else { StatsLogger.Stats.ResetFlag = true; args.Context.AddString("<color=red>WARNING!</color> Do you want to reset ALL stats logged by the StatsLogger Mod for this Character? Type '/statslogger reset " + Player.m_localPlayer.GetPlayerName() + "' to confirm."); } } else { args.Context.AddString("<color=red>Not logged in with any character.</color>"); } } else if (args.HasArgumentAnywhere("retail", 0, true)) { if ((Object)(object)Game.instance != (Object)null) { PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (playerProfile != null && playerProfile.m_playerStats != null) { foreach (KeyValuePair<PlayerStatType, float> stat in playerProfile.m_playerStats.m_stats) { args.Context.AddString($"{stat.Key}: {stat.Value}"); } } } } else if (args.HasArgumentAnywhere("hidedeaths", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result2)) { StatsLogger.Stats.HideDeathStats = result2; args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Hide Player Death Stats'"); } } else { args.Context.AddString("Hide Player Death Stats: " + (StatsLogger.Stats.HideDeathStats ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("chat", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result3)) { StatsLogger.Stats.AnnounceChat = result3; args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones in Chatbox (Local)'"); } } else { args.Context.AddString("Announce Stats Milestones in Chatbox (Local): " + (StatsLogger.Stats.AnnounceChat ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("console", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result4)) { StatsLogger.Stats.AnnounceConsole = result4; args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones in Console'"); } } else { args.Context.AddString("Announce Stats Milestones in Console: " + (StatsLogger.Stats.AnnounceConsole ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("effect", 0, true)) { if (args.Args.Length == 3) { if (bool.TryParse(args.Args[2], out var result5)) { StatsLogger.Stats.AnnounceEffect = result5; args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones with Effect'"); } } else { args.Context.AddString("Announce Stats Milestones with Effect: " + (StatsLogger.Stats.AnnounceEffect ? "<color=green>ON</color>" : "<color=red>OFF</color>")); } } else if (args.HasArgumentAnywhere("cooldown", 0, true)) { if (args.Args.Length == 3) { if (int.TryParse(args.Args[2], out var result6) && result6 >= 0) { StatsLogger.Stats.AnnounceIntervalMinutes = result6; args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); StatsLogger.Stats.SaveSettings(); } else { args.Context.AddString("<color=red>Error:</color> Invalid value '" + args.Args[2] + "' for 'Announce Stats Milestones Cooldown'"); } } else { args.Context.AddString($"Announce Stats Milestones Cooldown: <color=green>{StatsLogger.Stats.AnnounceIntervalMinutes}</color>"); } } else { args.Context.AddString("<color=red>Error:</color> '" + args.FullLine + "' is not a recognized command for StatsLogger! Type 'statslogger' to see a list of valid commands."); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("statslogger", "<color=red>[MOD]</color> Stats Logging for your Characters!", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } } [HarmonyPatch] public static class DamageAndHits { public class Target { public Character Character; public HitData Hit; public ItemData Weapon; public DateTime Time = DateTime.Now; public DeathTypes DeathType; } public enum DeathTypes { Unknown, FallDamage, Drowning, EdgeOfTheWorld } public static DateTime LastCleanupAtkPlayer = DateTime.Now; public static readonly int IntervalSecondsAtkPlayer = 60; public static readonly int TTLSecondsAtkPlayer = 30; public static DateTime LastCleanupAtkByPlayer = DateTime.Now; public static readonly int IntervalSecondsAtkByPlayer = 60; public static readonly int TTLSecondsAtkByPlayer = 30; public static Dictionary<int, Target> RecentlyAttackedByPlayer = new Dictionary<int, Target>(); public static Target LastAttackedPlayer = null; public static void SpawnOnDamaged<TypeOfObject>(Character __instance, HitData hit) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_012c: 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) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) if (StatsLogger.IsLocalPlayer(__instance)) { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null) { ItemData weapon = null; if (typeof(TypeOfObject) == typeof(Humanoid)) { weapon = ((Humanoid)attacker).GetCurrentWeapon(); } LastAttackedPlayer = new Target { Character = attacker, Hit = hit, Weapon = weapon }; StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.TakingHits, attacker, weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.TakingHits, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.DamageTaken, attacker, weapon, hit.GetTotalDamage()); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageTaken, hit.GetTotalDamage()); StatsLogger.Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.LastDamageTaken.ToString(), 0f, useTimeBuffer: false, set: true); return; } Vector3 val = (Vector3)typeof(Character).GetField("m_lastGroundPoint", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(__instance); Vector3 val2 = (Vector3)typeof(Character).GetField("m_lastGroundNormal", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(__instance); if (hit.m_point == val && hit.m_dir == val2) { LastAttackedPlayer = new Target { DeathType = DeathTypes.FallDamage, Hit = hit }; StatsLogger.Stats.UpdateSingle(Settings.LogActions.DamageTaken, Settings.StringDetailsMisc.Gravity.ToString(), hit.GetTotalDamage()); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageTaken, hit.GetTotalDamage()); } else if (hit.m_point == __instance.GetCenterPoint() && hit.m_dir == Vector3.down) { LastAttackedPlayer = new Target { DeathType = DeathTypes.Drowning, Hit = hit }; StatsLogger.Stats.UpdateSingle(Settings.LogActions.DamageTaken, Settings.StringDetailsMisc.Drowning.ToString(), hit.GetTotalDamage()); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageTaken, hit.GetTotalDamage()); } else if (hit.m_point == Vector3.zero && hit.m_dir == Vector3.zero) { LastAttackedPlayer = new Target { DeathType = DeathTypes.EdgeOfTheWorld, Hit = hit }; } else { LastAttackedPlayer = new Target { DeathType = DeathTypes.Unknown, Hit = hit }; StatsLogger.Stats.UpdateSingle(Settings.LogActions.DamageTaken, Settings.StringDetailsMisc.Unknown.ToString(), hit.GetTotalDamage()); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageTaken, hit.GetTotalDamage()); } StatsLogger.Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.LastDamageTaken.ToString(), 0f, useTimeBuffer: false, set: true); } else { if (!hit.HaveAttacker()) { return; } Character attacker2 = hit.GetAttacker(); if ((Object)(object)attacker2 != (Object)null && StatsLogger.IsLocalPlayer(attacker2)) { ItemData currentWeapon = ((Humanoid)Player.m_localPlayer).GetCurrentWeapon(); if (!RecentlyAttackedByPlayer.ContainsKey(((Object)__instance).GetInstanceID())) { RecentlyAttackedByPlayer.Add(((Object)__instance).GetInstanceID(), new Target { Character = __instance, Hit = hit, Weapon = currentWeapon }); } else { RecentlyAttackedByPlayer[((Object)__instance).GetInstanceID()].Time = DateTime.Now; RecentlyAttackedByPlayer[((Object)__instance).GetInstanceID()].Hit = hit; RecentlyAttackedByPlayer[((Object)__instance).GetInstanceID()].Weapon = currentWeapon; } StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Attacks, __instance, currentWeapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Attacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.DamageDone, __instance, currentWeapon, hit.GetTotalDamage()); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageDone, hit.GetTotalDamage()); } } } [HarmonyPrefix] [HarmonyPatch(typeof(Humanoid), "OnDamaged")] private static void HumanoidOnDamaged(ref Humanoid __instance, HitData hit) { SpawnOnDamaged<Humanoid>((Character)(object)__instance, hit); } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "OnDamaged")] private static void CharacterOnDamaged(ref Character __instance, HitData hit) { SpawnOnDamaged<Character>(__instance, hit); } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "OnDeath")] private static void CharacterOnDeath(Character __instance) { if (RecentlyAttackedByPlayer.ContainsKey(((Object)__instance).GetInstanceID())) { ItemData weapon = RecentlyAttackedByPlayer[((Object)__instance).GetInstanceID()].Weapon; StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Kills, __instance, weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Kills, 1f); RecentlyAttackedByPlayer.Remove(((Object)__instance).GetInstanceID()); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnDeath")] private static void PlayerOnDeath(Player __instance) { if (!StatsLogger.IsLocalPlayer((Character)(object)__instance)) { return; } if (LastAttackedPlayer != null) { if ((Object)(object)LastAttackedPlayer.Character != (Object)null) { ItemData weapon = LastAttackedPlayer.Weapon; StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Deaths, LastAttackedPlayer.Character, weapon, 1f); } else if (LastAttackedPlayer.DeathType.HasFlag(DeathTypes.FallDamage)) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Deaths, Settings.StringDetailsMisc.Gravity.ToString(), 1f); } else if (LastAttackedPlayer.DeathType.HasFlag(DeathTypes.Drowning)) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Deaths, Settings.StringDetailsMisc.Drowning.ToString(), 1f); } else if (LastAttackedPlayer.DeathType.HasFlag(DeathTypes.EdgeOfTheWorld)) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Deaths, Settings.StringDetailsMisc.EdgeOfTheWorld.ToString(), 1f); } else { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Deaths, Settings.StringDetailsMisc.Unknown.ToString(), 1f); } } else { DebugLogger.LogWarning("No 'LastAttackedPlayer', this should not be possible."); StatsLogger.Stats.UpdateSingle(Settings.LogActions.Deaths, Settings.StringDetailsMisc.Unknown.ToString(), 1f); } StatsLogger.Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.LastDeath.ToString(), 0f, useTimeBuffer: false, set: true); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Deaths, 1f); LastAttackedPlayer = null; StatsLogger.Stats.SaveFile(Player.m_localPlayer.GetPlayerName()); } [HarmonyPostfix] [HarmonyPatch(typeof(Raven), "Damage")] private static void RavenDamage(ref Raven __instance, HitData hit) { if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null && StatsLogger.IsLocalPlayer(attacker)) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Dismissed, __instance.m_name, 1f); } } } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "Damage")] private static void Damage(ref Character __instance, HitData hit, ZNetView ___m_nview) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker) && !StatsLogger.IsLocalOwner(___m_nview)) { LocalHitCollector.Hits.Add(new LocalHitCollector.LocalHit { Hit = hit, Target = __instance, Weapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(), Type = ((object)__instance).GetType(), UID = ___m_nview.GetZDO().m_uid, TTL = TTLSecondsAtkByPlayer }); } } } } public static class DebugLogger { public enum LogTypes { Default, Warning, Error } public static bool Enabled = true; public static string Prefix = ""; public static LogTypes MinLogLevel = LogTypes.Warning; public static void Log(string str) { Log(str, LogTypes.Default); } public static void LogWarning(string str) { Log(str, LogTypes.Warning); } public static void LogError(string str) { Log(str, LogTypes.Error); } private static void Log(string str, LogTypes type = LogTypes.Default) { if (Enabled && type >= MinLogLevel) { str = Prefix + " " + str; switch (type) { case LogTypes.Default: Debug.Log((object)str); break; case LogTypes.Warning: Debug.LogWarning((object)str); break; case LogTypes.Error: Debug.LogError((object)str); break; } } } } [HarmonyPatch] public static class DestructibleObjects { [HarmonyPatch(typeof(Destructible), "RPC_Damage")] public static class Destructible_RPC_Damage_DamageAndDeath { private static MethodInfo miCreate = AccessTools.Method(typeof(EffectList), "Create", (Type[])null, (Type[])null); private static MethodInfo miDestroy = AccessTools.Method(typeof(Destructible), "Destroy", (Type[])null, (Type[])null); private static MethodInfo miDamageTrigger = AccessTools.Method(typeof(Destructible_RPC_Damage_DamageAndDeath), "DamageTrigger", (Type[])null, (Type[])null); private static MethodInfo miDeathTrigger = AccessTools.Method(typeof(Destructible_RPC_Damage_DamageAndDeath), "DeathTrigger", (Type[])null, (Type[])null); private static readonly int Inserts = 2; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown int num = 0; List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Callvirt && (MethodInfo)list[i].operand == miCreate) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)miDamageTrigger)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); DebugLogger.Log($"{typeof(Destructible_RPC_Damage_DamageAndDeath)} [Destructible RPC_Damage {miDamageTrigger.Name}] applied."); num++; } if (list[i].opcode == OpCodes.Call && (MethodInfo)list[i].operand == miDestroy) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)miDeathTrigger)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); DebugLogger.Log($"{typeof(Destructible_RPC_Damage_DamageAndDeath)} [Destructible RPC_Damage {miDeathTrigger.Name}] applied."); num++; } } if (num == 0) { DebugLogger.LogError($"{typeof(Destructible_RPC_Damage_DamageAndDeath)} [Destructible RPC_Damage] failed."); } else if (num != Inserts) { DebugLogger.LogWarning($"{typeof(Destructible_RPC_Damage_DamageAndDeath)} [Destructible RPC_Damage] applied {num}/{Inserts}."); } return list.AsEnumerable(); } public static void DamageTrigger(Destructible target, HitData hit) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) float totalDamage = hit.GetTotalDamage(); if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker)) { ItemData currentWeapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectAttacks, (object)target, currentWeapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectDamageDone, (object)target, currentWeapon, totalDamage, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectDamageDone, totalDamage); } } } public static void DeathTrigger(Destructible target, HitData hit) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker)) { ItemData currentWeapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectsDestroyed, (object)target, currentWeapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectsDestroyed, 1f); } } } } [HarmonyPrefix] [HarmonyPatch(typeof(Destructible), "Damage")] private static void Damage(ref Destructible __instance, HitData hit, ZNetView ___m_nview) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker) && !StatsLogger.IsLocalOwner(___m_nview)) { LocalHitCollector.Hits.Add(new LocalHitCollector.LocalHit { Hit = hit, Target = __instance, Weapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(), Type = ((object)__instance).GetType(), UID = ___m_nview.GetZDO().m_uid }); } } } } [HarmonyPatch] public static class GuardianPower { [HarmonyPatch(typeof(Player))] [HarmonyPatch("ActivateGuardianPower")] public static class Player_ActivateGuardianPower_ReturnFix { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); if (list.Count > 2) { if (list[list.Count - 1].opcode == OpCodes.Ret) { if (list[list.Count - 2].opcode == OpCodes.Ldc_I4_0) { list[list.Count - 2].opcode = OpCodes.Ldc_I4_1; DebugLogger.Log($"{typeof(Player_ActivateGuardianPower_ReturnFix)} applied."); } else { DebugLogger.LogWarning($"{typeof(Player_ActivateGuardianPower_ReturnFix)} failed: Return value is not as expected. ({list[list.Count - 2].opcode})"); } } else { DebugLogger.LogError($"{typeof(Player_ActivateGuardianPower_ReturnFix)} failed: No last return found. ({list[list.Count - 1].opcode})"); } } else { DebugLogger.LogError($"{typeof(Player_ActivateGuardianPower_ReturnFix)} failed: Not enough Code Instructions. ({list.Count})"); } return list.AsEnumerable(); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "ActivateGuardianPower")] private static void PlayerUsePower(ref Player __instance, StatusEffect ___m_guardianSE, bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && __result) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.UsedPower, ___m_guardianSE.m_name, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.UsedPower, 1f); } } } [HarmonyPatch] public static class Interactions { [HarmonyPatch(typeof(Player), "Interact")] public static class Player_Interact_GameObjects { private static MethodInfo miInteract = AccessTools.Method(typeof(Interactable), "Interact", (Type[])null, (Type[])null); private static MethodInfo miInteractTrigger = AccessTools.Method(typeof(Interactions), "InteractTrigger", (Type[])null, (Type[])null); private static readonly int Inserts = 1; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown int num = 0; List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Callvirt && (MethodInfo)list[i].operand == miInteract) { list.Insert(i - 3, new CodeInstruction(OpCodes.Ldarg_1, (object)null)); i++; list.Insert(i - 3, new CodeInstruction(OpCodes.Call, (object)miInteractTrigger)); i++; DebugLogger.Log($"{typeof(Player_Interact_GameObjects)} [Player Interact {miInteractTrigger.Name}] applied."); num++; } } if (num == 0) { DebugLogger.LogError($"{typeof(Player_Interact_GameObjects)} [Player Interact] failed."); } else if (num != Inserts) { DebugLogger.LogWarning($"{typeof(Player_Interact_GameObjects)} [Player Interact] applied {num}/{Inserts}."); } return list.AsEnumerable(); } } public static void InteractTrigger(GameObject go) { Hoverable componentInParent = go.GetComponentInParent<Hoverable>(); if (componentInParent != null) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Interacted, componentInParent.GetHoverName(), 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Interacted, 1f); } } } [HarmonyPatch] public static class Items { [HarmonyPostfix] [HarmonyPatch(typeof(Player), "ConsumeItem")] private static void PlayerConsume(ref Player __instance, Inventory inventory, ItemData item, bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && __result) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Consumed, item.m_shared.m_name, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Consumed, 1f); } } [HarmonyPostfix] [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(bool) })] private static void PlayerCraft(ref Inventory __instance, string name, int stack, int quality, int variant, long crafterID, string crafterName, bool pickedUp, ItemData __result) { if (StatsLogger.IsLocalPlayerInventory(__instance) && __result != null && ((crafterName != null) & (crafterName.Length > 0)) && crafterName == Player.m_localPlayer.GetPlayerName()) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Crafted, __result.m_shared.m_name, stack); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Crafted, stack); } } [HarmonyPostfix] [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] private static void PlayerPickup(ref Inventory __instance, ItemData item, ref bool __result) { if (StatsLogger.IsLocalPlayerInventory(__instance) && __result && item != null) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.PickedUp, item.m_shared.m_name, item.m_stack); StatsLogger.Stats.UpdateTotal(Settings.LogActions.PickedUp, item.m_stack); } } [HarmonyPostfix] [HarmonyPatch(typeof(Fish), "Pickup", new Type[] { typeof(Humanoid) })] private static void FishPickup(ref Fish __instance, Humanoid character, ref bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)character) && __result && (Object)(object)__instance != (Object)null) { DebugLogger.Log("Picked up Fish: " + __instance.m_name); } } [HarmonyPostfix] [HarmonyPatch(typeof(Humanoid), "DropItem")] private static void PlayerDropItem(ref Humanoid __instance, Inventory inventory, ItemData item, int amount, ref bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && __result && item != null) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Dropped, item.m_shared.m_name, amount); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Dropped, amount); } } } [HarmonyPatch] public static class LoadAndSave { [HarmonyPostfix] [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerData")] private static void PlayerLoaded(ref PlayerProfile __instance) { DebugLogger.Log("PlayerProfile: LoadPlayerData"); if ((Object)(object)Player.m_localPlayer != (Object)null) { StatsLogger.Stats.LoadFile(Player.m_localPlayer.GetPlayerName()); DebugLogger.Log("PLAYERSTATS ARE LOADED"); } } [HarmonyPrefix] [HarmonyPatch(typeof(Game), "SavePlayerProfile")] private static void PlayerSaved(ref Game __instance) { DebugLogger.Log("Game: SavePlayerProfile"); if ((Object)(object)Player.m_localPlayer != (Object)null) { StatsLogger.Stats.SaveFile(Player.m_localPlayer.GetPlayerName()); DebugLogger.Log("PLAYERSTATS ARE SAVED"); } } } [HarmonyPatch] public class LocalHitCollector { public class LocalHit { public ZDOID UID; public object Target; public ItemData Weapon; public HitData Hit; public Type Type; public float TTL = 5f; public DateTime Time = DateTime.Now; public bool HitRegistered; } public static List<LocalHit> Hits = new List<LocalHit>(); public static DateTime LastCleanup = DateTime.Now; public static readonly int IntervalSeconds = 10; [HarmonyPrefix] [HarmonyPatch(typeof(DamageText), "AddInworldText")] private static void Damaged(ref DamageText __instance, TextType type, Vector3 pos, float distance, float dmg, bool mySelf) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown if (Hits.Count <= 0) { return; } LocalHit localHit = null; foreach (LocalHit hit in Hits) { Vector3 point = hit.Hit.m_point; if (((Vector3)(ref point)).Equals(pos) && !hit.HitRegistered) { localHit = hit; DebugLogger.Log($"[LocalHitCollector] HIT FOUND: {localHit.Type}"); break; } } if (localHit == null) { return; } if (dmg <= 0f) { Hits.Remove(localHit); return; } int index = Hits.IndexOf(localHit); Hits[index].HitRegistered = true; Hits[index].Time = DateTime.Now; Type type2 = localHit.Type; DebugLogger.Log($"[LocalHitCollector] HIT REGISTERED: {type2} {dmg}"); if (type2 == typeof(Character) || type2 == typeof(Humanoid) || type2 == typeof(Player)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Attacks, (Character)localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Attacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.DamageDone, (Character)localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.DamageDone, dmg); } else if (type2 == typeof(TreeBase)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.TreeAttacks, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.TreeAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.TreeDamageDone, localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.TreeDamageDone, dmg); } else if (type2 == typeof(TreeLog)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectAttacks, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectDamageDone, localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectDamageDone, dmg); } else if (type2 == typeof(WearNTear)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructureAttacks, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructureAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructureDamageDone, localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructureDamageDone, dmg); } else if (type2 == typeof(Destructible)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectAttacks, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectDamageDone, localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectDamageDone, dmg); } else if (type2 == typeof(MineRock5)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RockAttacks, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RockDamageDone, localHit.Target, localHit.Weapon, dmg); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RockAttacks, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RockDamageDone, dmg); } } [HarmonyPrefix] [HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO")] private static void Destroyed(ZDOID uid) { //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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown if (Hits.Count <= 0) { return; } LocalHit localHit = Hits.Find((LocalHit item) => ((ZDOID)(ref item.UID)).ID == ((ZDOID)(ref uid)).ID); if (localHit != null && localHit.HitRegistered) { DebugLogger.Log($"[LocalHitCollector] ZDO DESTROYED: {localHit.Type}"); Hits.Remove(localHit); Type type = localHit.Type; if (type == typeof(Character) || type == typeof(Humanoid) || type == typeof(Player)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.Kills, (Character)localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Kills, 1f); } else if (type == typeof(TreeBase)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.TreesCut, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.TreesCut, 1f); } else if (type == typeof(TreeLog)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectsDestroyed, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectsDestroyed, 1f); } else if (type == typeof(WearNTear)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructuresDestroyed, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructuresDestroyed, 1f); } else if (type == typeof(Destructible)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ObjectsDestroyed, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ObjectsDestroyed, 1f); } else if (type == typeof(MineRock5)) { StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RocksMined, localHit.Target, localHit.Weapon, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RocksMined, 1f); } } } } [HarmonyPatch] public static class Mining { [HarmonyPatch(typeof(MineRock5), "DamageArea")] public static class MineRock5_DamageArea_DamageAndDestroy { private static MethodInfo miSaveHealth = AccessTools.Method(typeof(MineRock5), "SaveHealth", (Type[])null, (Type[])null); private static MethodInfo miDamageTrigger = AccessTools.Method(typeof(MineRock5_DamageArea_DamageAndDestroy), "DamageTrigger", (Type[])null, (Type[])null); private static MethodInfo miDestroyTrigger = AccessTools.Method(typeof(MineRock5_DamageArea_DamageAndDestroy), "DestroyTrigger", (Type[])null, (Type[])null); private static int Inserts = 2; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown int num = 0; List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Call && (MethodInfo)list[i].operand == miSaveHealth) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)miDamageTrigger)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); DebugLogger.Log($"{typeof(MineRock5_DamageArea_DamageAndDestroy)} [MineRock5 DamageArea {miDamageTrigger.Name}] applied."); num++; } if (list[i].opcode == OpCodes.Ret && list[i - 1].opcode == OpCodes.Ldc_I4_1) { list.Insert(i - 2, new CodeInstruction(OpCodes.Call, (object)miDestroyTrigger)); list.Insert(i - 2, new CodeInstruction(OpCodes.Ldarg_2, (object)null)); list.Insert(i - 2, new CodeInstruction(OpCodes.Ldarg_0, (object)null)); i += 3; DebugLogger.Log($"{typeof(MineRock5_DamageArea_DamageAndDestroy)} [MineRock5 DamageArea {miDestroyTrigger.Name}] applied."); num++; } } if (num == 0) { DebugLogger.LogError($"{typeof(MineRock5_DamageArea_DamageAndDestroy)} [MineRock5 DamageArea] failed."); } else if (num != Inserts) { DebugLogger.LogWarning($"{typeof(MineRock5_DamageArea_DamageAndDestroy)} [MineRock5 DamageArea] applied {num}/{Inserts}."); } return list.AsEnumerable(); } public static void DamageTrigger(MineRock5 target, HitData hit) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker)) { ItemData currentWeapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(); float totalDamage = hit.GetTotalDamage(); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RockAttacks, (object)target, currentWeapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RockDamageDone, (object)target, currentWeapon, totalDamage, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RockAttacks, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RockDamageDone, totalDamage); } } } public static void DestroyTrigger(MineRock5 target, HitData hit) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker)) { ItemData currentWeapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.RocksMined, (object)target, currentWeapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.RocksMined, 1f); } } } } [HarmonyPostfix] [HarmonyPatch(typeof(MineRock), "RPC_Hit")] private static void TestMine(ref MineRock __instance, HitData hit) { DebugLogger.Log($"MineRock {__instance.m_name} {hit.GetAttacker().m_name} {hit.GetTotalDamage()}"); } [HarmonyPrefix] [HarmonyPatch(typeof(MineRock5), "Damage")] private static void Damage(ref MineRock5 __instance, HitData hit, ZNetView ___m_nview) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker) && !StatsLogger.IsLocalOwner(___m_nview)) { LocalHitCollector.Hits.Add(new LocalHitCollector.LocalHit { Hit = hit, Target = __instance, Weapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(), Type = ((object)__instance).GetType(), UID = ___m_nview.GetZDO().m_uid }); } } } } [HarmonyPatch] public static class Misc { [HarmonyPostfix] [HarmonyPatch(typeof(Player), "PlacePiece")] private static void PlayerPlacePiece(ref Player __instance, Piece piece, bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && __result) { StatsLogger.Stats.UpdateSingle(Settings.LogActions.Built, piece.m_name, 1f); StatsLogger.Stats.UpdateTotal(Settings.LogActions.Built, 1f); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnJump")] private static void PlayerJump(ref Player __instance) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance)) { StatsLogger.Stats.UpdateTotal(Settings.LogActions.Jumps, 1f); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "TeleportTo")] private static void PlayerTeleport(ref Player __instance, bool __result) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && __result) { StatsLogger.Stats.UpdateTotal(Settings.LogActions.Teleported, 1f); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "SetSleeping")] private static void PlayerSleep(ref Player __instance, bool sleep) { if (StatsLogger.IsLocalPlayer((Character)(object)__instance) && sleep) { StatsLogger.Stats.UpdateTotal(Settings.LogActions.Sleeps, 1f); } } } [HarmonyPatch] public static class Projectiles { public class Target { public GameObject Object; public Collider Collider; public ItemData Item; public DateTime Time = DateTime.Now; } public static Dictionary<int, Target> FiredProjectiles = new Dictionary<int, Target>(); [HarmonyPrefix] [HarmonyPatch(typeof(Attack), "FireProjectileBurst")] private static void AttackFireProjectileBurst(ref Attack __instance, ItemData ___m_ammoItem, ItemData ___m_weapon, Humanoid ___m_character) { if (StatsLogger.IsLocalPlayer((Character)(object)___m_character)) { DebugLogger.Log($"[DEBUG]: {___m_weapon.m_shared.m_name} using {___m_ammoItem?.m_shared?.m_name} x{__instance?.m_projectiles}"); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.ProjectilesFired, (object)___m_ammoItem, ___m_weapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.ProjectilesFired, 1f); } } } [HarmonyPatch] public static class ShipTriggers { public static bool IsShipping; [HarmonyPostfix] [HarmonyPatch(typeof(Ship), "OnTriggerEnter")] private static void Enter(ref Ship __instance) { if ((Object)(object)Player.m_localPlayer != (Object)null) { IsShipping = __instance.IsPlayerInBoat(Player.m_localPlayer); } else { IsShipping = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(Ship), "OnTriggerExit")] private static void Exit(ref Ship __instance) { if ((Object)(object)Player.m_localPlayer != (Object)null) { IsShipping = __instance.IsPlayerInBoat(Player.m_localPlayer); } else { IsShipping = false; } } } [BepInPlugin("de.givenameplz.statslogger", "Stats Logger (by givenameplz)", "1.2.4")] public class StatsLogger : BaseUnityPlugin { public const string GUID = "de.givenameplz.statslogger"; public const string DisplayName = "Stats Logger (by givenameplz)"; public const string Version = "1.2.4"; public const string Description = "Stats Logging for your Characters!"; public const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static Settings Stats = new Settings(); public static bool Debugging = false; private static Random RNG = new Random(); public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") { return new string((from s in Enumerable.Repeat(chars, length) select s[RNG.Next(s.Length)]).ToArray()); } public static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp, bool outputAsLocalTime = false) { DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); if (outputAsLocalTime) { return dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); } return dateTime.AddSeconds(unixTimeStamp).ToUniversalTime(); } public static ulong DateTimeToUnixTimeStamp(DateTime dateTime) { DateTime dateTime2 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); return (ulong)(dateTime.ToUniversalTime() - dateTime2).TotalSeconds; } private void Awake() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) DebugLogger.Prefix = "de.givenameplz.statslogger"; Stats.LoadSettings(); new Harmony("de.givenameplz.statslogger").PatchAll(); UserInterface.Init(); } private void Update() { //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Unknown result type (might be due to invalid IL or missing references) //IL_05e2: Unknown result type (might be due to invalid IL or missing references) if (DamageAndHits.LastCleanupAtkByPlayer.CompareTo(DateTime.Now.AddSeconds(-DamageAndHits.IntervalSecondsAtkByPlayer)) < 0) { DamageAndHits.LastCleanupAtkByPlayer = DateTime.Now; if (DamageAndHits.RecentlyAttackedByPlayer.Count > 0) { foreach (int item in DamageAndHits.RecentlyAttackedByPlayer.Keys.Where((int key) => DamageAndHits.RecentlyAttackedByPlayer[key].Time < DateTime.Now.AddSeconds(-DamageAndHits.TTLSecondsAtkByPlayer)).ToList()) { DebugLogger.Log($"[DamageAndHits.RecentlyAttackedByPlayer] REM {item}"); DamageAndHits.RecentlyAttackedByPlayer.Remove(item); } } } if (DamageAndHits.LastCleanupAtkPlayer.CompareTo(DateTime.Now.AddSeconds(-DamageAndHits.IntervalSecondsAtkPlayer)) < 0) { DamageAndHits.LastCleanupAtkPlayer = DateTime.Now; if (DamageAndHits.LastAttackedPlayer != null && DamageAndHits.LastAttackedPlayer.Time < DateTime.Now.AddSeconds(-DamageAndHits.TTLSecondsAtkPlayer)) { DebugLogger.Log("[DamageAndHits.LastAttackedPlayer] REM " + (Object.op_Implicit((Object)(object)DamageAndHits.LastAttackedPlayer.Character) ? DamageAndHits.LastAttackedPlayer.Character.m_name : "unknown")); DamageAndHits.LastAttackedPlayer = null; } } if (Structures.LastCleanup.CompareTo(DateTime.Now.AddSeconds(-Structures.IntervalSeconds)) < 0) { Structures.LastCleanup = DateTime.Now; if (Structures.LastAttackedByPlayer.Count > 0) { foreach (int item2 in Structures.LastAttackedByPlayer.Keys.Where((int key) => Structures.LastAttackedByPlayer[key].Time < DateTime.Now.AddSeconds(-Structures.TTLSeconds)).ToList()) { DebugLogger.Log($"[Structures.LastAttackedByPlayer] REM {item2}"); Structures.LastAttackedByPlayer.Remove(item2); } } } if (LocalHitCollector.LastCleanup.CompareTo(DateTime.Now.AddSeconds(-LocalHitCollector.IntervalSeconds)) < 0 && LocalHitCollector.Hits.Count > 0) { foreach (LocalHitCollector.LocalHit item3 in LocalHitCollector.Hits.Where((LocalHitCollector.LocalHit item) => item.Time < DateTime.Now.AddSeconds(0f - item.TTL)).ToList()) { DebugLogger.Log($"[LocalHitCollector.Hits] REM {((ZDOID)(ref item3.UID)).ID}"); LocalHitCollector.Hits.Remove(item3); } } if (!((Object)(object)Player.m_localPlayer != (Object)null)) { return; } Stats.AutoSave(Player.m_localPlayer.GetPlayerName()); float deltaTime = Time.deltaTime; Stats.BurstUpdate(deltaTime); if (ShipTriggers.IsShipping) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Shipping.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsSwimming()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Swimming.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsSneaking()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Sneaking.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsCrouching()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Crouching.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsSitting()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Sitting.ToString(), deltaTime, useTimeBuffer: true); } if (Player.m_localPlayer.IsSafeInHome()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.SafeAtHome.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsPVPEnabled()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.PvPModeEnabled.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsRunning()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Running.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).InBed()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Sleeping.ToString(), deltaTime, useTimeBuffer: true); } if (VagonCheck.IsVagoning) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Carting.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).IsEncumbered()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Encumbered.ToString(), deltaTime, useTimeBuffer: true); } if (Player.m_localPlayer.InShelter()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Sheltered.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).InInterior()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Inside.ToString(), deltaTime, useTimeBuffer: true); } if (((Character)Player.m_localPlayer).InPlaceMode()) { Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.Building.ToString(), deltaTime, useTimeBuffer: true); } Settings stats = Stats; Biome currentBiome = Player.m_localPlayer.GetCurrentBiome(); stats.UpdateSingle(Settings.LogActions.Biome, ((object)(Biome)(ref currentBiome)).ToString(), deltaTime, useTimeBuffer: true); Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.LastDeath.ToString(), deltaTime, useTimeBuffer: true); Stats.UpdateSingle(Settings.LogActions.Time, Settings.StringDetailsTime.LastDamageTaken.ToString(), deltaTime, useTimeBuffer: true); foreach (StatusEffect statusEffect in ((SEMan)typeof(Player).GetField("m_seman", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(Player.m_localPlayer)).GetStatusEffects()) { Stats.UpdateSingle(Settings.LogActions.StatusEffect, statusEffect.m_name, deltaTime, useTimeBuffer: true); } Stats.UpdateTotal(Settings.LogActions.Time, deltaTime, useTimeBuffer: true); } public static bool IsLocalOwner(ZNetView nw) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } ZDOID zDOID = ((Character)Player.m_localPlayer).GetZDOID(); if ((Object)(object)nw == (Object)null) { return false; } ZDO zDO = nw.GetZDO(); if (zDO == null) { return false; } if (!zDO.HasOwner()) { return false; } if (zDO.IsOwner()) { return true; } return ((ZDOID)(ref zDOID)).UserID == zDO.GetOwner(); } public static bool IsLocalPlayer(Character character) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } if ((Object)(object)character == (Object)null) { return false; } return ((Object)Player.m_localPlayer).GetInstanceID() == ((Object)character).GetInstanceID(); } public static bool IsLocalPlayerInventory(Inventory inventory) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } if (inventory == null) { return false; } return ((object)((Humanoid)Player.m_localPlayer).GetInventory())?.Equals((object?)inventory) ?? false; } } public class Settings { public enum LogTypes { Single, Total } public enum LogDetails { Target, Level, Weapon, Player } public enum StringDetailsTime { Shipping, Swimming, Sneaking, Sitting, SafeAtHome, PvPModeEnabled, Running, Sleeping, Crouching, Carting, Encumbered, Sheltered, Inside, Building, LastDeath, LastDamageTaken } public enum StringDetailsMisc { Unknown, Tree, TreeLog, EdgeOfTheWorld, Drowning, Gravity } public enum LogActions { Dismissed, Kills, UsedPower, Consumed, Deaths, Crafted, Built, Jumps, Sleeps, Attacks, TakingHits, PickedUp, Dropped, Teleported, DamageDone, DamageTaken, Destroyed, ObjectDamageDone, ObjectAttacks, ObjectsDestroyed, StructureDamageDone, StructureAttacks, StructuresDestroyed, TreeDamageDone, TreeAttacks, TreesCut, RockDamageDone, RockAttacks, RocksMined, Time, StatusEffect, Biome, Blocks, BlockedDamage, Staggers, Interacted, ProjectilesFired } public class CharacterStats { public int Version = 2; public string CharacterName; public List<StatsEntry> Entries = new List<StatsEntry>(); public CharacterStats(string character) { CharacterName = character; } } public class StatsEntry { private string _name; private LogActions _action; private LogTypes _type; private Dictionary<LogDetails, string> _details; public string Name { get { return _name; } set { string[] array = value.Split(new char[1] { '|' }); _type = (LogTypes)Enum.Parse(typeof(LogTypes), array[0], ignoreCase: true); _action = (LogActions)Enum.Parse(typeof(LogActions), array[2], ignoreCase: true); _details = new Dictionary<LogDetails, string>(); string[] array2 = array[1].Split(new char[1] { ',' }); for (int i = 0; i < array2.Length; i++) { string[] array3 = array2[i].Split(new char[1] { ':' }); if (array3.Length == 2 && Enum.TryParse<LogDetails>(array3[0], ignoreCase: true, out var result)) { _details.Add(result, array3[1]); } } _name = value; } } public DateTime FirstLogged { get; set; } public DateTime LastLogged { get; set; } public float Amount { get; set; } public LogActions GetLogAction() { return _action; } public LogTypes GetLogType() { return _type; } public Dictionary<LogDetails, string> GetDetails() { return _details; } public bool IsDetail(LogDetails detail, string value) { if (_details.ContainsKey(detail)) { return _details[detail] == value; } return false; } public string GetDetail(LogDetails detail) { if (_details.ContainsKey(detail)) { return _details[detail]; } return null; } public bool HasOnlyDetail(LogDetails detail) { if (_details.Count == 1 && _details.ContainsKey(detail)) { return true; } return false; } public bool HasDetail(LogDetails detail) { if (_details.ContainsKey(detail)) { return true; } return false; } public string GetPrettyName() { string text = ""; if (_type == LogTypes.Total) { text += "Total "; } if (_details.ContainsKey(LogDetails.Weapon) && !_details.ContainsKey(LogDetails.Target)) { text = text + _details[LogDetails.Weapon] + " "; } if (_details.ContainsKey(LogDetails.Target)) { text = text + _details[LogDetails.Target] + " "; if (_details.ContainsKey(LogDetails.Level)) { text = text + "(Level " + _details[LogDetails.Level] + ") "; } if (_details.ContainsKey(LogDetails.Weapon)) { text = text + "with " + _details[LogDetails.Weapon] + " "; } } return text + _action; } public string GetFormattedAmount() { string text = ""; if (_action.HasFlag(LogActions.Time) || _action.HasFlag(LogActions.Biome) || _action.HasFlag(LogActions.StatusEffect)) { float num = Amount % 60f; float num2 = Amount / 60f % 60f; float num3 = Amount / 3600f; return $"{num3:00}:{num2:00}:{num:00}"; } return Math.Round(Amount, 1).ToString(); } } private static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "givenameplz", "ValheimStatsLogger"); private static readonly string FilenameSettings = Path.Combine(AppDataPath, "settings.cfg"); private static readonly string FileExt = "dat"; private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const int CurrentDataVersion = 2; private CharacterStats StatsInformation; private bool HasChanges; private int BackupsMax = 3; private int BackupsPointer = 1; private DateTime LastSave = DateTime.Now; private readonly int AutoSaveIntervalMinutes = 15; private Dictionary<string, float> TimeBuffer = new Dictionary<string, float>(); private float BurstBuffer; private readonly float BurstLimitSeconds = 5f; public int AnnounceIntervalMinutes = 5; public DateTime LastAnnounce = DateTime.Now; public bool AnnounceChat; public bool AnnounceConsole = true; public bool AnnounceEffect = true; public bool HideDeathStats; public bool DebugMessagesInUnityConsole; public bool ResetFlag; public bool HiddenLogLevelFound; public Settings() { Directory.CreateDirectory(AppDataPath); DebugLogger.Log("[STATS] Initialized."); } public void ResetData(string character) { StatsInformation = new CharacterStats(character); } public DateTime ParseALotOfDateTimeFormats(string rawdate, bool assumeLocal = true) { if (DateTime.TryParse(rawdate, out var result)) { return result; } string[] formats = new string[4] { "dd.MM.yyyy hh:mm:ss", "dd/MM/yyyy hh:mm:ss", "yyyy-MM-dd hh:mm:ss", "MM/dd/yyyy hh:mm:ss tt" }; if (DateTime.TryParseExact(rawdate, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | (assumeLocal ? DateTimeStyles.AssumeLocal : DateTimeStyles.AssumeUniversal), out result)) { return result; } return DateTime.Now; } public void LoadFile(string character) { string text = StatsLogger.RandomString(5); string text2 = Path.Combine(AppDataPath, character + "." + FileExt); string destFileName = Path.Combine(AppDataPath, character + "." + FileExt + ".fkt-" + text); StatsInformation = new CharacterStats(character); if (!File.Exists(text2)) { return; } FileStream fileStream = null; try { fileStream = new FileStream(text2, FileMode.Open); BinaryReader binaryReader = new BinaryReader(fileStream); int num = binaryReader.ReadInt32(); string destFileName2 = Path.Combine(AppDataPath, $"{character}.{FileExt}.v{num}-{text}"); switch (num) { case 1: { DebugLogger.LogWarning($"[STATS] Converting version '{num}' to '{2}'"); StatsInformation.Version = 2; StatsInformation.CharacterName = binaryReader.ReadString(); int num5 = binaryReader.ReadInt32(); for (int j = 0; j < num5; j++) { StatsInformation.Entries.Add(new StatsEntry { Name = binaryReader.ReadString(), FirstLogged = ParseALotOfDateTimeFormats(binaryReader.ReadString()), LastLogged = ParseALotOfDateTimeFormats(binaryReader.ReadString()), Amount = binaryReader.ReadSingle() }); ulong num6 = StatsLogger.DateTimeToUnixTimeStamp(StatsInformation.Entries[j].FirstLogged); ulong num7 = StatsLogger.DateTimeToUnixTimeStamp(StatsInformation.Entries[j].LastLogged); DebugLogger.Log($"{StatsInformation.Entries[j].Name} ({StatsInformation.Entries[j].Amount})"); DebugLogger.Log($"FIRST: {StatsInformation.Entries[j].FirstLogged} | {num6} | UTC: {StatsLogger.UnixTimeStampToDateTime(num6)} | Local: {StatsLogger.UnixTimeStampToDateTime(num6, outputAsLocalTime: true)}"); DebugLogger.Log($"LAST: {StatsInformation.Entries[j].LastLogged} | {num7} | UTC: {StatsLogger.UnixTimeStampToDateTime(num7)} | Local: {StatsLogger.UnixTimeStampToDateTime(num7, outputAsLocalTime: true)}"); } fileStream.Close(); File.Move(text2, destFileName2); HasChanges = true; SaveFile(character); break; } default: DebugLogger.LogError($"[STATS] Wrong version '{num}'"); fileStream.Close(); File.Move(text2, destFileName2); break; case 2: { StatsInformation.Version = num; StatsInformation.CharacterName = binaryReader.ReadString(); int num2 = binaryReader.ReadInt32(); for (int i = 0; i < num2; i++) { StatsInformation.Entries.Add(new StatsEntry { Name = binaryReader.ReadString(), FirstLogged = StatsLogger.UnixTimeStampToDateTime(binaryReader.ReadUInt64(), outputAsLocalTime: true), LastLogged = StatsLogger.UnixTimeStampToDateTime(binaryReader.ReadUInt64(), outputAsLocalTime: true), Amount = binaryReader.ReadSingle() }); ulong num3 = StatsLogger.DateTimeToUnixTimeStamp(StatsInformation.Entries[i].FirstLogged); ulong num4 = StatsLogger.DateTimeToUnixTimeStamp(StatsInformation.Entries[i].LastLogged); DebugLogger.Log($"{StatsInformation.Entries[i].Name} ({StatsInformation.Entries[i].Amount})"); DebugLogger.Log($"FIRST: {StatsInformation.Entries[i].FirstLogged} | {num3} | UTC: {StatsLogger.UnixTimeStampToDateTime(num3)} | Local: {StatsLogger.UnixTimeStampToDateTime(num3, outputAsLocalTime: true)}"); DebugLogger.Log($"LAST: {StatsInformation.Entries[i].LastLogged} | {num4} | UTC: {StatsLogger.UnixTimeStampToDateTime(num4)} | Local: {StatsLogger.UnixTimeStampToDateTime(num4, outputAsLocalTime: true)}"); } fileStream.Close(); break; } } } catch (Exception ex) { DebugLogger.LogError("[STATS] Error loading: " + ex.Message); StatsInformation = new CharacterStats(character); if (File.Exists(text2) && fileStream != null) { fileStream.Close(); File.Move(text2, destFileName); } } } public void SaveFile(string character) { LastSave = DateTime.Now; ApplyTimeBuffers(); if (!HasChanges) { return; } HasChanges = false; string text = Path.Combine(AppDataPath, character + "." + FileExt); string destFileName = Path.Combine(AppDataPath, $"{character}.{FileExt}.bkp{BackupsPointer}"); if (File.Exists(text)) { if (File.Exists(text)) { File.Copy(text, destFileName, overwrite: true); } if (++BackupsPointer > BackupsMax) { BackupsPointer = 1; } } try { FileStream fileStream = new FileStream(text, FileMode.Create); BinaryWriter binaryWriter = new BinaryWriter(fileStream); binaryWriter.Write(StatsInformation.Version); binaryWriter.Write(StatsInformation.CharacterName); binaryWriter.Write(StatsInformation.Entries.Count); foreach (StatsEntry entry in StatsInformation.Entries) { binaryWriter.Write(entry.Name); binaryWriter.Write(StatsLogger.DateTimeToUnixTimeStamp(entry.FirstLogged)); binaryWriter.Write(StatsLogger.DateTimeToUnixTimeStamp(entry.LastLogged)); binaryWriter.Write(entry.Amount); } binaryWriter.Flush(); binaryWriter.Close(); fileStream.Close(); } catch (Exception ex) { DebugLogger.LogError("[STATS] Error saving: " + ex.Message); } } public void AutoSave(string character) { if (HasChanges && LastSave.CompareTo(DateTime.Now.AddMinutes(-AutoSaveIntervalMinutes)) < 0) { SaveFile(character); } } public void LoadSettings() { DebugLogger.Log("[STATS] Loading Settings..."); AnnounceChat = false; AnnounceConsole = true; AnnounceEffect = true; if (!File.Exists(FilenameSettings)) { return; } string[] array = File.ReadAllLines(FilenameSettings); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '=' }, 2); if (array2.Length != 2) { continue; } DebugLogger.LogTypes result6; if (array2[0] == "announcechat") { if (bool.TryParse(array2[1], out var result)) { AnnounceChat = result; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } else if (array2[0] == "announceconsole") { if (bool.TryParse(array2[1], out var result2)) { AnnounceConsole = result2; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } else if (array2[0] == "announceeffect") { if (bool.TryParse(array2[1], out var result3)) { AnnounceEffect = result3; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } else if (array2[0] == "announceinterval") { if (int.TryParse(array2[1], out var result4)) { AnnounceIntervalMinutes = result4; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } else if (array2[0] == "hidedeathstats") { if (bool.TryParse(array2[1], out var result5)) { HideDeathStats = result5; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } else if (array2[0] == "loglevel" && Enum.TryParse<DebugLogger.LogTypes>(array2[1], out result6)) { HiddenLogLevelFound = true; DebugLogger.MinLogLevel = result6; DebugLogger.Log("[STATS] Setting: " + array2[0] + "=" + array2[1]); } } } public void SaveSettings() { DebugLogger.Log("[STATS] Saving Settings..."); List<string> list = new List<string> { "announcechat=" + (AnnounceChat ? "true" : "false"), "announceconsole=" + (AnnounceConsole ? "true" : "false"), "announceeffect=" + (AnnounceEffect ? "true" : "false"), "announceinterval=" + AnnounceIntervalMinutes, "hidedeathstats=" + (HideDeathStats ? "true" : "false") }; if (HiddenLogLevelFound) { list.Add($"loglevel={DebugLogger.MinLogLevel}"); } File.WriteAllLines(FilenameSettings, list); } public void BurstUpdate(float dt) { BurstBuffer += dt; if (BurstBuffer > BurstLimitSeconds) { ApplyTimeBuffers(); } } private void ApplyTimeBuffers() { BurstBuffer = 0f; string[] array = TimeBuffer.Keys.ToArray(); foreach (string text in array) { Update(text, TimeBuffer[text]); TimeBuffer.Remove(text); } } public void UpdateTotal(LogActions action, float value, bool useTimeBuffer = false) { InternalUpdate(LogTypes.Total, "", action, value, useTimeBuffer); } public void UpdateCombinedSingle(LogActions action, Character target, ItemData weapon, float value, bool useTimeBuffer = false) { string text = (((Object)(object)target != (Object)null) ? target.m_name : StringDetailsMisc.Unknown.ToString()); string text2 = ((weapon != null) ? weapon.m_shared.m_name : StringDetailsMisc.Unknown.ToString()); string target2 = $"target:{text},level:{target.GetLevel()},weapon:{text2}"; InternalUpdateSingle(action, target2, value, useTimeBuffer); target2 = $"target:{text},level:{target.GetLevel()}"; InternalUpdateSingle(action, target2, value, useTimeBuffer); target2 = "target:" + text + ",weapon:" + text2; InternalUpdateSingle(action, target2, value, useTimeBuffer); target2 = "target:" + text; InternalUpdateSingle(action, target2, value, useTimeBuffer); target2 = "weapon:" + text2; InternalUpdateSingle(action, target2, value, useTimeBuffer); } public void UpdateCombinedSingle(LogActions action, object target, ItemData weapon, float value, bool useTimeBuffer = false) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) string text; if (target == null) { text = StringDetailsMisc.Unknown.ToString(); } else if (target.GetType() == typeof(TreeBase)) { text = ((Object)(TreeBase)target).name.Replace("(Clone)", "").Trim(); } else if (target.GetType() == typeof(TreeLog)) { text = ((Object)(TreeLog)target).name.Replace("(Clone)", "").Trim(); } else if (!(target.GetType() == typeof(WearNTear))) { text = ((target.GetType() == typeof(Destructible)) ? ((Object)(Destructible)target).name.Replace("(Clone)", "").Trim() : ((target.GetType() == typeof(MineRock5)) ? ((MineRock5)target).m_name : ((target == null || !(target.GetType() == typeof(ItemData))) ? StringDetailsMisc.Unknown.ToString() : ((ItemData)target).m_shared.m_name))); } else { Piece val = (Piece)typeof(WearNTear).GetField("m_piece", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(target); text = ((!((Object)(object)val == (Object)null)) ? val.m_name : ((Object)(WearNTear)target).name.Replace("(Clone)", "").Trim()); } string text2 = ((weapon != null && weapon.m_shared != null) ? weapon.m_shared.m_name : StringDetailsMisc.Unknown.ToString()); string target2; if (target != null) { target2 = "target:" + text + ",weapon:" + text2; InternalUpdateSingle(action, target2, value, useTimeBuffer); target2 = "target:" + text; InternalUpdateSingle(action, target2, value, useTimeBuffer); } target2 = "weapon:" + text2; InternalUpdateSingle(action, target2, value, useTimeBuffer); } public void UpdateSingle(LogActions action, string target, float value, bool useTimeBuffer = false, bool set = false) { string target2 = "target:" + target; InternalUpdateSingle(action, target2, value, useTimeBuffer, set); } private void InternalUpdateSingle(LogActions action, string target, float value, bool useTimeBuffer = false, bool set = false) { InternalUpdate(LogTypes.Single, target, action, value, useTimeBuffer, set); } private void InternalUpdate(LogTypes type, string target, LogActions action, float value, bool useTimeBuffer = false, bool set = false) { string text = type.ToString().ToLower() + "|" + target + "|" + action.ToString().ToLower(); if (useTimeBuffer) { if (!TimeBuffer.ContainsKey(text)) { TimeBuffer.Add(text, 0f); } TimeBuffer[text] += value; } else { if (set && TimeBuffer.ContainsKey(text)) { TimeBuffer.Remove(text); } Update(text, value, set); } } private void Update(string name, float value, bool set = false) { if (StatsInformation == null) { DebugLogger.LogWarning("[STATS] NO CHARACTER LOADED"); return; } HasChanges = true; float oldAmount = 0f; StatsEntry statsEntry = StatsInformation.Entries.Find((StatsEntry x) => x.Name == name); if (statsEntry == null) { statsEntry = new StatsEntry { Name = name, Amount = value, FirstLogged = DateTime.Now, LastLogged = DateTime.Now }; StatsInformation.Entries.Add(statsEntry); } else { oldAmount = statsEntry.Amount; if (set) { statsEntry.Amount = value; } else { statsEntry.Amount += value; } statsEntry.LastLogged = DateTime.Now; } CheckAnnounce(statsEntry, oldAmount); if (DebugMessagesInUnityConsole) { DebugLogger.Log($"[STATS] {statsEntry.Name}: {statsEntry.Amount}"); } } public List<StatsEntry> GetStatsList() { if (StatsInformation == null) { DebugLogger.LogWarning("[STATS] StatsInformation not loaded!"); return new List<StatsEntry>(); } return StatsInformation.Entries; } private void CheckAnnounce(StatsEntry stat, float oldAmount) { //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) if (LastAnnounce.CompareTo(DateTime.Now.AddMinutes(-AnnounceIntervalMinutes)) > 0) { return; } int[] array = new int[7] { 1, 5, 10, 25, 50, 75, 100 }; bool flag = false; int num = 100; if (oldAmount > (float)num) { while (oldAmount > (float)(num / 10)) { num *= 10; } num /= 10; if (stat.Amount - oldAmount >= (float)num) { flag = true; } else if (oldAmount % (float)num > stat.Amount % (float)num) { flag = true; } } else { int[] array2 = array; foreach (int num2 in array2) { if (oldAmount < (float)num2 && stat.Amount >= (float)num2) { flag = true; break; } } } if (flag && (Object)(object)Chat.instance != (Object)null && (!HideDeathStats || (stat.GetLogAction() != LogActions.Deaths && (stat.GetDetail(LogDetails.Target) == null || !(stat.GetDetail(LogDetails.Target).ToLower() == StringDetailsTime.LastDeath.ToString().ToLower()))))) { LastAnnounce = DateTime.Now; string text = "<color=purple>[STATS]</color> " + stat.GetPrettyName() + ": " + stat.GetFormattedAmount(); if (AnnounceEffect && (Object)(object)Player.m_localPlayer != (Object)null) { Transform val = (Transform)typeof(Player).GetField("m_head", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(Player.m_localPlayer); Player.m_localPlayer.m_skillLevelupEffects.Create(val.position, val.rotation, val, 0.2f, -1); MessageHud.instance.QueueUnlockMsg(UserInterface.StatsLoggerIcon, "Character Stats", stat.GetPrettyName() + ": " + stat.GetFormattedAmount()); } if (AnnounceChat && (Object)(object)Chat.instance != (Object)null) { typeof(Chat).GetMethod("AddString", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null).Invoke(Chat.instance, new object[1] { text }); } if (AnnounceConsole && (Object)(object)Console.instance != (Object)null) { typeof(Console).GetMethod("AddString", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null).Invoke(Console.instance, new object[1] { text }); } } } } [HarmonyPatch] public static class Structures { public class Target { public HitData Hit; public ItemData Weapon; public DateTime Time = DateTime.Now; } [HarmonyPatch(typeof(WearNTear), "RPC_Damage")] public static class WearNTear_RPC_Damage_Damage { private static MethodInfo miApplyDamage = AccessTools.Method(typeof(WearNTear), "ApplyDamage", (Type[])null, (Type[])null); private static MethodInfo miDamageTrigger = AccessTools.Method(typeof(WearNTear_RPC_Damage_Damage), "DamageTrigger", (Type[])null, (Type[])null); private static readonly int Inserts = 1; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown int num = 0; List<CodeInstruction> list = new List<CodeInstruction>(instructions); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Call && (MethodInfo)list[i].operand == miApplyDamage) { int num2 = 2; list.Insert(i + num2, new CodeInstruction(OpCodes.Call, (object)miDamageTrigger)); list.Insert(i + num2, list[i - 2]); list.Insert(i + num2, list[i - 1]); list.Insert(i + num2, list[i - 3]); DebugLogger.Log($"{typeof(WearNTear_RPC_Damage_Damage)} [WearNTear RPC_Damage {miDamageTrigger.Name}] applied."); num++; } } if (num == 0) { DebugLogger.LogError($"{typeof(WearNTear_RPC_Damage_Damage)} [WearNTear RPC_Damage] failed."); } else if (num != Inserts) { DebugLogger.LogWarning($"{typeof(WearNTear_RPC_Damage_Damage)} [WearNTear RPC_Damage] applied {num}/{Inserts}."); } return list.AsEnumerable(); } public static void DamageTrigger(WearNTear target, HitData hit, float dmg) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!hit.HaveAttacker()) { return; } Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker)) { ItemData currentWeapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(); if (LastAttackedByPlayer.ContainsKey(((Object)target).GetInstanceID())) { LastAttackedByPlayer.Remove(((Object)target).GetInstanceID()); } LastAttackedByPlayer.Add(((Object)target).GetInstanceID(), new Target { Hit = hit, Time = DateTime.Now, Weapon = currentWeapon }); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructureAttacks, (object)target, currentWeapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructureAttacks, 1f); StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructureDamageDone, (object)target, currentWeapon, dmg, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructureDamageDone, dmg); } else if (LastAttackedByPlayer.ContainsKey(((Object)target).GetInstanceID())) { LastAttackedByPlayer.Remove(((Object)target).GetInstanceID()); } } } public static Dictionary<int, Target> LastAttackedByPlayer = new Dictionary<int, Target>(); public static DateTime LastCleanup = DateTime.Now; public static readonly int IntervalSeconds = 10; public static readonly int TTLSeconds = 5; [HarmonyPrefix] [HarmonyPatch(typeof(WearNTear), "Destroy")] private static void DestroyPieces(ref WearNTear __instance, Piece ___m_piece) { if (LastAttackedByPlayer.ContainsKey(((Object)__instance).GetInstanceID())) { ItemData weapon = LastAttackedByPlayer[((Object)__instance).GetInstanceID()].Weapon; StatsLogger.Stats.UpdateCombinedSingle(Settings.LogActions.StructuresDestroyed, (object)__instance, weapon, 1f, useTimeBuffer: false); StatsLogger.Stats.UpdateTotal(Settings.LogActions.StructuresDestroyed, 1f); LastAttackedByPlayer.Remove(((Object)__instance).GetInstanceID()); } } [HarmonyPrefix] [HarmonyPatch(typeof(WearNTear), "Damage")] private static void Damage(ref WearNTear __instance, ref HitData hit, ZNetView ___m_nview) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (hit.HaveAttacker()) { Character attacker = hit.GetAttacker(); if (StatsLogger.IsLocalPlayer(attacker) && !StatsLogger.IsLocalOwner(___m_nview)) { LocalHitCollector.Hits.Add(new LocalHitCollector.LocalHit { Hit = hit, Target = __instance, Weapon = ((Humanoid)(Player)attacker).GetCurrentWeapon(), Type = ((object)__instance).GetType(), UID = ___m_nview.GetZDO().m_uid }); } } } } [HarmonyPatch] public static class Trees { [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] public static class TreeBase_RPC_Damage_DamageAndDeath { private static MethodInfo miShake = AccessTools.Method(typeof(TreeBase), "Shake", (Type[])null, (Type[])null); private static MethodInfo miSpawnLog = AccessTools.Method(typeof(TreeBase), "SpawnLog", (Type[])null, (Type[])null); private static MethodInfo miDamageTrigger = AccessTools.Method(typeof(TreeBase_RPC_Damage_DamageAndDeath), "DamageTrigger", (Type[])null, (Type[])null); private static MethodInfo miDeathTrigger = AccessTools.Method(typeof(TreeBase_RPC_Damage_DamageAndDeath), "DeathTrigger", (Type[])null, (Type[])null); private static readonly int Inserts = 2; [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnu