Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TweakStats v1.0.0
BepInEx/plugins/TweakStats/TweakStats.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Kriona.Shared; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Kriona")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Kriona Hagen - MIT License")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Tweak Stats")] [assembly: AssemblyTitle("Tweak Stats")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TweakStats { [HarmonyPatch(typeof(ObjectDB), "Awake")] public static class ObjectDBAwakePatch { [HarmonyPriority(0)] private static void Postfix() { Tweaks.Apply(); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] public static class ObjectDBCopyOtherDBPatch { [HarmonyPriority(0)] private static void Postfix() { Tweaks.Apply(); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] public static class ZNetSceneAwakePatch { [HarmonyPriority(0)] private static void Postfix() { Tweaks.Apply(); } } [HarmonyPatch(typeof(ZNet), "Awake")] public static class ZNetAwakePatch { private static void Postfix(ZNet __instance) { if (__instance.IsServer()) { Tweaks.CurrentWorld = ZNet.World; Tweaks.Apply(); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] public static class ZNetPeerInfoPatch { private static void Postfix(ZNet __instance) { if (!__instance.IsServer() && ZNet.World != Tweaks.CurrentWorld) { Tweaks.CurrentWorld = ZNet.World; Tweaks.Apply(); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] public static class ZNetOnDestroyPatch { private static void Postfix() { Tweaks.CurrentWorld = null; Tweaks.ClearServerConfig(); ServerSync.Clear(); Tweaks.Apply(); } } public static class Commands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__Run; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleOptionsFetcher <>9__2_0; public static Converter<string, string> <>9__7_0; public static Converter<string, string> <>9__13_1; public static Comparison<ItemDrop> <>9__14_0; public static Comparison<Recipe> <>9__14_1; public static Converter<GameObject, (string name, string m_name)> <>9__14_2; public static Converter<GameObject, (string name, string m_name)> <>9__14_3; public static Converter<StatusEffect, (string name, string m_name)> <>9__14_4; public static Comparison<(string prefabName, string gameName)> <>9__15_0; internal List<string> <Register>b__2_0() { return new List<string> { "find", "inventory", "world", "dump", "list", "reload" }; } internal string <World>b__7_0(string entry) { return "[Worlds: " + entry + "]"; } internal string <Dump>b__13_1(string selector) { return selector.Trim(); } internal int <List>b__14_0(ItemDrop a, ItemDrop b) { return string.Compare(((Object)((Component)a).gameObject).name, ((Object)((Component)b).gameObject).name, StringComparison.OrdinalIgnoreCase); } internal int <List>b__14_1(Recipe a, Recipe b) { return string.Compare(((Object)((Component)a.m_item).gameObject).name, ((Object)((Component)b.m_item).gameObject).name, StringComparison.OrdinalIgnoreCase); } internal (string name, string m_name) <List>b__14_2(GameObject creature) { return (name: ((Object)creature).name, m_name: creature.GetComponent<Character>().m_name); } internal (string name, string m_name) <List>b__14_3(GameObject piece) { return (name: ((Object)piece).name, m_name: piece.GetComponent<Piece>().m_name); } internal (string name, string m_name) <List>b__14_4(StatusEffect effect) { return (name: ((Object)effect).name, m_name: effect.m_name); } internal int <AppendNames>b__15_0((string prefabName, string gameName) a, (string prefabName, string gameName) b) { return string.Compare(a.prefabName, b.prefabName, StringComparison.OrdinalIgnoreCase); } } private const int MaxFound = 30; private const int MaxDumpNameLength = 100; public static void Register() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown object obj = <>O.<0>__Run; if (obj == null) { ConsoleEvent val = Run; <>O.<0>__Run = val; obj = (object)val; } object obj2 = <>c.<>9__2_0; if (obj2 == null) { ConsoleOptionsFetcher val2 = () => new List<string> { "find", "inventory", "world", "dump", "list", "reload" }; <>c.<>9__2_0 = val2; obj2 = (object)val2; } new ConsoleCommand("tweakstats", "find <text>, inventory, world, dump <section>, list or reload - Tweak Stats config tools", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } private static void Run(ConsoleEventArgs args) { string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : ""); string text2 = ((args.Length > 2) ? args.ArgsAll.Substring(args.ArgsAll.IndexOf(' ') + 1).Trim() : ""); if (text == "find" && text2 != "") { Find(args.Context, text2); return; } switch (text) { case "inventory": case "inv": Inventory(args.Context); return; case "world": World(args.Context); return; case "dump": if (text2 != "") { Dump(args.Context, text2); return; } break; } if (text == "list") { List(args.Context); return; } if (text == "reload") { Tweaks.Reload(); args.Context.AddString("Reloaded " + Path.GetFileName(Plugin.ConfigPath)); return; } args.Context.AddString("tweakstats find <text> - finds the sections for items, creatures, build pieces and status effects whose name contains the text, e.g. tweakstats find troll"); args.Context.AddString("tweakstats inventory - lists the items you're carrying with their section names and main stats"); args.Context.AddString("tweakstats world - shows the world's name and ID and the server you're on, for [Worlds: ...] groups"); args.Context.AddString("tweakstats dump <section> - writes every stat of what a section names to " + DumpFileName("SwordIron") + ", e.g. tweakstats dump SwordIron"); args.Context.AddString("tweakstats list - writes every item, recipe, creature, build piece and status effect name to " + ListFileName()); args.Context.AddString("tweakstats reload - reads " + Path.GetFileName(Plugin.ConfigPath) + " again"); } private static void Find(Terminal terminal, string text) { List<string> list = new List<string>(); foreach (ItemDrop item in Prefabs.Items()) { AddIfFound(list, text, "", ((Object)((Component)item).gameObject).name, item.m_itemData.m_shared.m_name); } foreach (GameObject item2 in Prefabs.Creatures()) { AddIfFound(list, text, "Creature:", ((Object)item2).name, item2.GetComponent<Character>().m_name); } foreach (GameObject item3 in Prefabs.Pieces()) { AddIfFound(list, text, "Piece:", ((Object)item3).name, item3.GetComponent<Piece>().m_name); } foreach (StatusEffect item4 in Prefabs.Effects()) { AddIfFound(list, text, "Effect:", ((Object)item4).name, item4.m_name); } list.Sort(StringComparer.OrdinalIgnoreCase); if (list.Count == 0) { terminal.AddString("Nothing matches \"" + text + "\"" + (((Object)(object)ZNetScene.instance == (Object)null) ? " - join a world to search creatures and build pieces too" : "")); return; } for (int i = 0; i < list.Count && i < 30; i++) { terminal.AddString(list[i]); } if (list.Count > 30) { terminal.AddString("...and " + (list.Count - 30) + " more - search for more of the name to narrow it down"); } } private static void AddIfFound(List<string> found, string text, string kind, string prefabName, string gameName) { string text2 = Prefabs.Localize(gameName); if (prefabName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { found.Add("[" + kind + prefabName + "]" + ((text2 != "") ? (" - " + text2) : "")); } } private static void Inventory(Terminal terminal) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { terminal.AddString("Join a world to list the items you're carrying"); return; } List<ItemData> allItemsInGridOrder = ((Humanoid)localPlayer).GetInventory().GetAllItemsInGridOrder(); if (allItemsInGridOrder.Count == 0) { terminal.AddString("You aren't carrying anything"); return; } terminal.AddString("You're carrying " + allItemsInGridOrder.Count + ((allItemsInGridOrder.Count == 1) ? " item" : " items") + " - section, name and stats:"); foreach (ItemData item in allItemsInGridOrder) { terminal.AddString(DescribeItem(item)); } } private static void World(Terminal terminal) { World currentWorld = Tweaks.CurrentWorld; if ((Object)(object)ZNet.instance == (Object)null || currentWorld == null) { terminal.AddString("Join a world to see its name and ID"); return; } terminal.AddString("World: " + currentWorld.m_name + " (ID " + currentWorld.m_uid + ")"); terminal.AddString("Playing: " + DescribeConnection()); List<string> list = new List<string> { currentWorld.m_name, currentWorld.m_uid.ToString() }; string text = ServerEntry(); if (text != null) { list.Add(text); } if (ZNet.IsSinglePlayer) { list.Add("SinglePlayer"); } terminal.AddString("Sections in " + string.Join(" or ", list.ConvertAll((string entry) => "[Worlds: " + entry + "]")) + " apply here - use the ID or server when other worlds share the name"); } private static string ServerEntry() { if (ZNet.instance.IsServer()) { return null; } if (!string.IsNullOrEmpty(ZNet.m_serverHost)) { return ZNet.m_serverHost; } if (ZNet.m_serverSteamID != 0L) { return ZNet.m_serverSteamID.ToString(); } if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId)) { return ZNet.m_serverPlayFabPlayerId; } return null; } private static string DescribeConnection() { if (ZNet.instance.IsServer()) { if (ZNet.IsSinglePlayer) { return "single player"; } return "hosting" + ((ZNet.m_ServerName != "") ? (" the server \"" + ZNet.m_ServerName + "\"") : " for other players"); } if (!string.IsNullOrEmpty(ZNet.m_serverHost)) { return "on a server at " + ZNet.m_serverHost + ":" + ZNet.m_serverHostPort; } if (ZNet.m_serverSteamID != 0L) { return "on a server with Steam ID " + ZNet.m_serverSteamID; } if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId)) { return "on a crossplay server with PlayFab ID " + ZNet.m_serverPlayFabPlayerId; } return "on a server"; } private static string DescribeItem(ItemData item) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Invalid comparison between Unknown and I4 SharedData shared = item.m_shared; string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : shared.m_name); string text2 = "[" + text + "] " + Prefabs.Localize(shared.m_name); if (item.m_stack > 1) { text2 = text2 + " x" + item.m_stack; } List<string> list = new List<string>(); if (shared.m_maxQuality > 1) { list.Add("level " + item.m_quality); } if (item.m_equipped) { list.Add("equipped"); } string text3 = DescribeDamages(item.GetDamage()); if (text3 != "") { list.Add("damages " + text3); } float armor = item.GetArmor(); if (armor > 0f) { list.Add("armor " + TweakValue.Format(armor)); } if ((int)shared.m_itemType == 5) { list.Add("block " + TweakValue.Format(item.GetBaseBlockPower())); } List<string> list2 = new List<string>(); AddFood(list2, shared.m_food, "health"); AddFood(list2, shared.m_foodStamina, "stamina"); AddFood(list2, shared.m_foodEitr, "eitr"); if (list2.Count > 0) { list.Add("food " + string.Join(" ", list2) + " for " + TweakValue.Format(shared.m_foodBurnTime) + "s"); } if (shared.m_useDurability) { list.Add("durability " + TweakValue.Format(Math.Round(item.m_durability)) + "/" + TweakValue.Format(item.GetMaxDurability())); } list.Add("weight " + TweakValue.Format(item.GetWeight(-1))); return text2 + " - " + string.Join(", ", list); } private static void AddFood(List<string> food, float amount, string name) { if (amount > 0f) { food.Add(TweakValue.Format(amount) + " " + name); } } private static string DescribeDamages(DamageTypes damages) { //IL_0036: 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) List<string> list = new List<string>(); FieldInfo[] fields = typeof(DamageTypes).GetFields(); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(float) && (float)fieldInfo.GetValue(damages) > 0f) { list.Add(fieldInfo.Name.Substring(2) + " " + TweakValue.Format(fieldInfo.GetValue(damages))); } } return string.Join(" ", list); } private static void Dump(Terminal terminal, string header) { header = header.Trim().TrimStart('[').TrimEnd(']'); List<string> problems = new List<string>(); StringBuilder text = new StringBuilder(); int count = 0; string fileHeader = header; Tweaks.WithoutTweaks(delegate { foreach (Target item in Targets.Resolve(new List<string>(header.Split(',')).ConvertAll((string selector) => selector.Trim()), problems, null)) { List<string> list = new List<string>(); item.List(list); text.Append("\n[" + item.Header + "]\n"); foreach (string item2 in list) { text.Append("# " + item2 + "\n"); } count++; fileHeader = ((count == 1) ? item.Header : header); } }); foreach (string item3 in problems) { terminal.AddString(item3); } if (count != 0) { string text2 = "# Tweak Stats dump of [" + header + "] from Valheim " + Version.GetVersionString(false) + " on " + DateTime.Now.ToString("yyyy-MM-dd HH:mm") + "\n# These are the game's values before any tweaks. Copy the lines you want into " + Path.GetFileName(Plugin.ConfigPath) + " and remove the # to use them\n"; string text3 = DumpFileName(fileHeader); if (WriteFile(terminal, text3, text2 + text)) { terminal.AddString("Wrote " + count + ((count == 1) ? " section" : " sections") + " to " + Path.Combine(Paths.ConfigPath, text3)); } } } private static void List(Terminal terminal) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("# Tweak Stats - Names\n\n"); stringBuilder.Append("Generated by Tweak Stats 1.0.0 from Valheim " + Version.GetVersionString(false) + " on " + DateTime.Now.ToString("yyyy-MM-dd") + ".\n\n"); stringBuilder.Append("## Items\n\n"); stringBuilder.Append("| Section | Name | Type | Skill |\n"); stringBuilder.Append("| --- | --- | --- | --- |\n"); List<ItemDrop> list = Prefabs.Items(); list.Sort((ItemDrop a, ItemDrop b) => string.Compare(((Object)((Component)a).gameObject).name, ((Object)((Component)b).gameObject).name, StringComparison.OrdinalIgnoreCase)); foreach (ItemDrop item in list) { SharedData shared = item.m_itemData.m_shared; stringBuilder.Append("| `[" + ((Object)((Component)item).gameObject).name + "]` | " + Prefabs.DisplayName(item) + " | " + ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + " | " + ((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString() + " |\n"); } stringBuilder.Append("\n## Recipes\n\n"); stringBuilder.Append("| Section | Makes | Station |\n"); stringBuilder.Append("| --- | --- | --- |\n"); List<Recipe> list2 = Prefabs.Recipes(); list2.Sort((Recipe a, Recipe b) => string.Compare(((Object)((Component)a.m_item).gameObject).name, ((Object)((Component)b.m_item).gameObject).name, StringComparison.OrdinalIgnoreCase)); foreach (Recipe item2 in list2) { string text = (((Object)(object)item2.m_craftingStation != (Object)null) ? ((Object)((Component)item2.m_craftingStation).gameObject).name : "none"); stringBuilder.Append("| `[Recipe:" + ((Object)((Component)item2.m_item).gameObject).name + "]` | " + Prefabs.DisplayName(item2.m_item) + " x" + item2.m_amount + " | " + text + " |\n"); } List<GameObject> list3 = Prefabs.Creatures(); AppendNames(stringBuilder, "Creatures", "Creature:", list3.ConvertAll((GameObject creature) => (name: ((Object)creature).name, m_name: creature.GetComponent<Character>().m_name))); List<GameObject> list4 = Prefabs.Pieces(); AppendNames(stringBuilder, "Build Pieces", "Piece:", list4.ConvertAll((GameObject piece) => (name: ((Object)piece).name, m_name: piece.GetComponent<Piece>().m_name))); List<StatusEffect> list5 = Prefabs.Effects(); AppendNames(stringBuilder, "Status Effects", "Effect:", list5.ConvertAll((StatusEffect effect) => (name: ((Object)effect).name, m_name: effect.m_name))); if (WriteFile(terminal, ListFileName(), stringBuilder.ToString())) { terminal.AddString("Wrote " + list.Count + " items, " + list2.Count + " recipes, " + list3.Count + " creatures, " + list4.Count + " build pieces and " + list5.Count + " status effects to " + Path.Combine(Paths.ConfigPath, ListFileName())); if ((Object)(object)ZNetScene.instance == (Object)null) { terminal.AddString("Join a world to list creatures and build pieces too"); } } } private static void AppendNames(StringBuilder text, string heading, string kind, List<(string prefabName, string gameName)> entries) { entries.Sort(((string prefabName, string gameName) a, (string prefabName, string gameName) b) => string.Compare(a.prefabName, b.prefabName, StringComparison.OrdinalIgnoreCase)); text.Append("\n## " + heading + "\n\n"); text.Append("| Section | Name |\n"); text.Append("| --- | --- |\n"); foreach (var (text2, text3) in entries) { text.Append("| `[" + kind + text2 + "]` | " + Prefabs.Localize(text3) + " |\n"); } } private static bool WriteFile(Terminal terminal, string fileName, string text) { string text2 = Path.Combine(Paths.ConfigPath, fileName); try { File.WriteAllText(text2, text); return true; } catch (Exception ex) { terminal.AddString("Couldn't write " + text2 + ": " + ex.Message); return false; } } private static string DumpFileName(string header) { StringBuilder stringBuilder = new StringBuilder(); char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char c in header) { if (Array.IndexOf(invalidFileNameChars, c) < 0 && c != ',' && !char.IsWhiteSpace(c)) { stringBuilder.Append(c); } else if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '_') { stringBuilder.Append('_'); } } string text = stringBuilder.ToString().TrimEnd('_'); if (text == "") { text = "all"; } if (text.Length > 100) { text = text.Substring(0, 100); } return "kriona.TweakStats.dump." + text + ".txt"; } private static string ListFileName() { return "kriona.TweakStats.names.md"; } } [HarmonyPatch(typeof(Container), "Load")] public static class ContainerLoadPatch { private const int LoadingWidth = 1000; private static void Prefix(Container __instance, out int __state) { __state = -1; if (__instance.m_inventory != null && !Instances.IsGrave(__instance)) { __state = __instance.m_inventory.m_width; __instance.m_inventory.m_width = 1000; } } private static void Postfix(Container __instance, bool __result, int __state) { if (__state < 0) { return; } __instance.m_inventory.m_width = __state; if (__result) { Instances.MarkResized(__instance); if (Instances.KeepsSize(__instance)) { __instance.m_inventory.m_height = __instance.m_height; } Instances.DropItemsOutside(__instance); } } } public static class Instances { private static readonly FieldInfo characterHealth = typeof(Character).GetField("m_health"); private static readonly FieldInfo pieceHealth = typeof(WearNTear).GetField("m_health"); private static readonly FieldInfo containerWidth = typeof(Container).GetField("m_width"); private static readonly FieldInfo containerHeight = typeof(Container).GetField("m_height"); private static readonly int resizedKey = StringExtensionMethods.GetStableHashCode("kriona.TweakStats.resized"); private static Dictionary<object, HashSet<FieldInfo>> touched = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance); private static Dictionary<object, HashSet<FieldInfo>> previous = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance); public static void Touch(Component component, FieldInfo field) { if (!touched.TryGetValue(component, out var value)) { value = new HashSet<FieldInfo>(); touched.Add(component, value); } value.Add(field); } public static void StartApply() { previous = touched; touched = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance); } public static void Sync() { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown Dictionary<object, HashSet<FieldInfo>> dictionary = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance); Merge(dictionary, previous); Merge(dictionary, touched); if (dictionary.Count == 0 || (Object)(object)ZNetScene.instance == (Object)null) { return; } Dictionary<Type, Dictionary<string, Component>> dictionary2 = new Dictionary<Type, Dictionary<string, Component>>(); foreach (object key in dictionary.Keys) { Component val = (Component)((key is Component) ? key : null); if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); if (!dictionary2.TryGetValue(type, out var value)) { value = new Dictionary<string, Component>(); dictionary2.Add(type, value); } value[((Object)val.gameObject).name] = val; } } foreach (KeyValuePair<Type, Dictionary<string, Component>> item in dictionary2) { Object[] array = Object.FindObjectsByType(item.Key, (FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { Component val2 = (Component)array[i]; if (item.Value.TryGetValue(Utils.GetPrefabName(((Object)val2.gameObject).name), out var value2) && (Object)(object)value2 != (Object)(object)val2) { Copy(value2, val2, dictionary[value2]); } } } } public static void RefreshEquipmentEffects() { foreach (Character s_character in Character.s_characters) { Humanoid val = (Humanoid)(object)((s_character is Humanoid) ? s_character : null); if ((Object)(object)val == (Object)null || (Object)(object)((Character)val).m_nview == (Object)null || !((Character)val).m_nview.IsValid() || !((Character)val).m_nview.IsOwner()) { continue; } foreach (StatusEffect equipmentStatusEffect in val.m_equipmentStatusEffects) { ((Character)val).m_seman.RemoveStatusEffect(equipmentStatusEffect, true); } val.m_equipmentStatusEffects.Clear(); val.UpdateEquipmentStatusEffects(); } } private static void Merge(Dictionary<object, HashSet<FieldInfo>> into, Dictionary<object, HashSet<FieldInfo>> from) { foreach (KeyValuePair<object, HashSet<FieldInfo>> item in from) { if (!into.TryGetValue(item.Key, out var value)) { value = new HashSet<FieldInfo>(); into.Add(item.Key, value); } value.UnionWith(item.Value); } } private static void Copy(Component prefabComponent, Component instance, HashSet<FieldInfo> fields) { foreach (FieldInfo field in fields) { field.SetValue(instance, field.GetValue(prefabComponent)); } if (ContainsField(fields, characterHealth)) { Character val = (Character)(object)((instance is Character) ? instance : null); if (val != null) { ResetMaxHealth(val); } } if (ContainsField(fields, pieceHealth)) { WearNTear val2 = (WearNTear)(object)((instance is WearNTear) ? instance : null); if (val2 != null) { ScalePieceHealth(val2); } } if (ContainsField(fields, containerWidth) || ContainsField(fields, containerHeight)) { Container val3 = (Container)(object)((instance is Container) ? instance : null); if (val3 != null) { ResizeInventory(val3); } } } private static bool ContainsField(HashSet<FieldInfo> fields, FieldInfo field) { foreach (FieldInfo field2 in fields) { if (field2.DeclaringType == field.DeclaringType && field2.Name == field.Name) { return true; } } return false; } private static void ResetMaxHealth(Character character) { if (!((Object)(object)character.m_nview == (Object)null) && character.m_nview.IsValid() && character.m_nview.IsOwner()) { bool num = character.GetHealth() >= character.GetMaxHealth(); character.SetupMaxHealth(); if (num) { character.SetHealth(character.GetMaxHealth()); } } } private static void ScalePieceHealth(WearNTear piece) { if (Game.m_worldLevel > 0 && (Object)(object)Game.instance != (Object)null) { piece.m_health += (float)Game.m_worldLevel * Game.instance.m_worldLevelPieceHPMultiplier * piece.m_health; } if ((Object)(object)piece.m_nview != (Object)null && piece.m_nview.IsValid()) { piece.m_healthPercentage = Mathf.Clamp01(piece.m_nview.GetZDO().GetFloat(ZDOVars.s_health, piece.m_health) / piece.m_health); } } private static void ResizeInventory(Container container) { if (container.m_inventory != null && !IsGrave(container)) { container.m_inventory.m_width = container.m_width; container.m_inventory.m_height = container.m_height; MarkResized(container); DropItemsOutside(container); } } public static bool KeepsSize(Container container) { if (IsGrave(container)) { return false; } if (!IsResized(container)) { if ((Object)(object)container.m_nview != (Object)null && container.m_nview.IsValid()) { return container.m_nview.GetZDO().GetBool(resizedKey, false); } return false; } return true; } public static void MarkResized(Container container) { if (!IsGrave(container) && !((Object)(object)container.m_nview == (Object)null) && container.m_nview.IsValid() && container.m_nview.IsOwner() && IsResized(container)) { ZDO zDO = container.m_nview.GetZDO(); if (!zDO.GetBool(resizedKey, false)) { zDO.Set(resizedKey, true); container.m_lastRevision = zDO.DataRevision; } } } private static bool IsResized(Container container) { if ((Object)(object)ZNetScene.instance == (Object)null) { return false; } GameObject prefab = ZNetScene.instance.GetPrefab(Utils.GetPrefabName(((Component)container).gameObject)); Container val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Container>() : null); if ((Object)(object)val == (Object)null || !touched.TryGetValue(val, out var value)) { return false; } if (!ContainsField(value, containerWidth)) { return ContainsField(value, containerHeight); } return true; } public static bool IsGrave(Container container) { if (!((Object)(object)((Component)container).GetComponentInParent<TombStone>() != (Object)null)) { if ((Object)(object)container.m_nview != (Object)null) { return (Object)(object)((Component)container.m_nview).GetComponent<TombStone>() != (Object)null; } return false; } return true; } public static void DropItemsOutside(Container container) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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) if (IsGrave(container) || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid() || !container.m_nview.IsOwner()) { return; } Inventory inventory = container.m_inventory; foreach (ItemData item in inventory.GetAllItems().FindAll((ItemData item) => item.m_gridPos.x >= inventory.GetWidth() || item.m_gridPos.y >= inventory.GetHeight())) { Vector3 val = ((Component)container).transform.position + Vector3.up * 0.5f + Random.insideUnitSphere * 0.3f; Quaternion val2 = Quaternion.Euler(0f, (float)Random.Range(0, 360), 0f); ItemDrop.DropItem(item, 0, val, val2); inventory.RemoveItem(item); } } } [HarmonyPatch(typeof(ItemDrop), "Awake")] public static class ItemDropAwakePatch { private static void Postfix(ItemDrop __instance) { if (!((Object)(object)__instance.m_itemData.m_dropPrefab == (Object)null)) { ItemDrop component = __instance.m_itemData.m_dropPrefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && (Object)(object)component != (Object)(object)__instance) { __instance.m_itemData.m_shared = component.m_itemData.m_shared; } } } } [HarmonyPatch(typeof(ItemData), "Save", new Type[] { typeof(ZPackage) })] public static class ItemDataSavePatch { private const float MaxSavedDurability = 21000000f; private static void Prefix(ItemData __instance) { if (__instance.m_durability > 21000000f) { __instance.m_durability = 21000000f; } } } public static class Limits { private class Limit { public FieldInfo Field; public double Min; public double Max; public string Reason; public Limit(Type type, string name, double min, double max, string reason) { Field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Min = min; Max = max; Reason = reason; } } private const double MaxMagnitude = 1000000000.0; private static readonly List<Limit> limits = new List<Limit> { new Limit(typeof(SharedData), "m_maxStackSize", 1.0, 65535.0, "the game can't add items to a stack of 0, and saves stacks as numbers up to 65535"), new Limit(typeof(Recipe), "m_amount", 0.0, 1000.0, "the game makes each crafted stack separately, all at once"), new Limit(typeof(Attack), "m_projectiles", 0.0, 100.0, "the game makes every projectile of a shot at once"), new Limit(typeof(Attack), "m_attackAngle", -360.0, 360.0, "the game checks for hits every 4 degrees of a swing"), new Limit(typeof(Container), "m_width", 1.0, 100.0, "the game makes a slot in the window for each slot in the chest"), new Limit(typeof(Container), "m_height", 1.0, 100.0, "the game makes a slot in the window for each slot in the chest"), new Limit(typeof(Beehive), "m_maxHoney", 0.0, 1000.0, "the game drops each honey separately, all at once"), new Limit(typeof(SapCollector), "m_maxLevel", 0.0, 1000.0, "the game drops each sap separately, all at once") }; private static readonly List<string> notes = new List<string>(); public static object Clamp(FieldInfo field, object result) { if (result == null || !TweakValue.IsNumeric(field.FieldType)) { return result; } double num = Convert.ToDouble(result, CultureInfo.InvariantCulture); double num2 = num; string text = null; foreach (Limit limit in limits) { if (!(limit.Field == null) && !(limit.Field.DeclaringType != field.DeclaringType) && !(limit.Field.Name != field.Name)) { if (num2 > limit.Max) { num2 = limit.Max; text = limit.Reason; } else if (num2 < limit.Min) { num2 = limit.Min; text = limit.Reason; } } } if (text == null) { return result; } AddNote(num, num2, text); return Convert.ChangeType(num2, field.FieldType, CultureInfo.InvariantCulture); } public static double ClampMagnitude(double value, double original) { if (Math.Abs(value) <= 1000000000.0 || Math.Abs(value) <= Math.Abs(original)) { return value; } double num = (double)Math.Sign(value) * Math.Max(1000000000.0, Math.Abs(original)); AddNote(value, num, "larger numbers can overflow the game's math"); return num; } private static void AddNote(double value, double clamped, string reason) { notes.Add(Describe(value) + " is outside what the game can handle, so it's " + Describe(clamped) + " - " + reason); } private static string Describe(double number) { string text = ((Math.Abs(number) < 1000000000000000.0) ? "0.####" : "0.###e+0"); return number.ToString(text, CultureInfo.InvariantCulture); } public static List<string> TakeNotes() { List<string> list = new List<string>(); foreach (string note in notes) { if (!list.Contains(note)) { list.Add(note); } } notes.Clear(); return list; } } public static class Originals { private static readonly Dictionary<object, Dictionary<FieldInfo, object>> values = new Dictionary<object, Dictionary<FieldInfo, object>>(ReferenceComparer.Instance); public static void Set(object owner, FieldInfo field, object value) { if (!values.TryGetValue(owner, out var value2)) { value2 = new Dictionary<FieldInfo, object>(); values.Add(owner, value2); } if (!value2.ContainsKey(field)) { value2.Add(field, field.GetValue(owner)); } field.SetValue(owner, value); } public static void Restore() { foreach (KeyValuePair<object, Dictionary<FieldInfo, object>> value in values) { foreach (KeyValuePair<FieldInfo, object> item in value.Value) { item.Key.SetValue(value.Key, item.Value); } } values.Clear(); } } [BepInPlugin("kriona.TweakStats", "Tweak Stats", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "kriona.TweakStats"; public const string PluginName = "Tweak Stats"; public const string PluginVersion = "1.0.0"; public static ManualLogSource Log; public static string ConfigPath; private Harmony harmony; private ConfigWatcher watcher; private void Awake() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ConfigPath = Path.Combine(Paths.ConfigPath, "kriona.TweakStats.cfg"); if (!File.Exists(ConfigPath)) { WriteDefaultConfig(); } Tweaks.Load(); watcher = new ConfigWatcher(ConfigPath, Tweaks.Reload, ((BaseUnityPlugin)this).Logger, null); harmony = new Harmony("kriona.TweakStats"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Tweak Stats 1.0.0 loaded"); } private void Update() { Tweaks.ShowPendingMessage(); watcher.Update(); } private void WriteDefaultConfig() { try { using Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("DefaultConfig.cfg"); using StreamReader streamReader = new StreamReader(stream); File.WriteAllText(ConfigPath, streamReader.ReadToEnd()); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Couldn't write " + ConfigPath + ": " + ex.Message)); } } private void OnDestroy() { watcher?.Dispose(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } public static class Prefabs { public static List<ItemDrop> Items() { List<ItemDrop> list = new List<ItemDrop>(); if ((Object)(object)ObjectDB.instance == (Object)null) { return list; } foreach (GameObject item in ObjectDB.instance.m_items) { if (!((Object)(object)item == (Object)null)) { ItemDrop component = item.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData.m_shared.m_icons != null && component.m_itemData.m_shared.m_icons.Length != 0) { list.Add(component); } } } return list; } public static List<Recipe> Recipes() { List<Recipe> list = new List<Recipe>(); if ((Object)(object)ObjectDB.instance == (Object)null) { return list; } foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if ((Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null) { list.Add(recipe); } } return list; } public static List<GameObject> Creatures() { List<GameObject> list = new List<GameObject>(); if ((Object)(object)ZNetScene.instance == (Object)null) { return list; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Character>() != (Object)null && (Object)(object)prefab.GetComponent<Player>() == (Object)null) { list.Add(prefab); } } return list; } public static List<GameObject> Pieces() { List<GameObject> list = new List<GameObject>(); if ((Object)(object)ZNetScene.instance == (Object)null) { return list; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Piece>() != (Object)null && (Object)(object)prefab.GetComponentInChildren<TombStone>(true) == (Object)null) { list.Add(prefab); } } return list; } public static List<StatusEffect> Effects() { List<StatusEffect> list = new List<StatusEffect>(); if ((Object)(object)ObjectDB.instance == (Object)null) { return list; } foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { if ((Object)(object)statusEffect != (Object)null) { list.Add(statusEffect); } } return list; } public static HashSet<StatusEffect> HitEffects() { HashSet<StatusEffect> hashSet = new HashSet<StatusEffect>(); if ((Object)(object)ObjectDB.instance == (Object)null) { return hashSet; } foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null && (Object)(object)val.m_itemData.m_shared.m_attackStatusEffect != (Object)null) { hashSet.Add(val.m_itemData.m_shared.m_attackStatusEffect); } } foreach (StatusEffect item2 in Effects()) { if (item2 is SE_Burning || item2 is SE_Poison || item2 is SE_Frost || item2 is SE_Harpooned) { hashSet.Add(item2); } } return hashSet; } public static string NameOf(Object unityObject) { Component val = (Component)(object)((unityObject is Component) ? unityObject : null); if (val != null) { return ((Object)val.gameObject).name; } return unityObject.name; } public static string DisplayName(ItemDrop item) { return Localize(item.m_itemData.m_shared.m_name); } public static string Localize(string text) { if (Localization.instance == null) { return text; } return Localization.instance.Localize(text); } public static bool CanFind(Type type) { if (!(type == typeof(ItemDrop)) && !(type == typeof(GameObject)) && !typeof(StatusEffect).IsAssignableFrom(type)) { return type == typeof(CraftingStation); } return true; } public static string Describe(Type type) { if (type == typeof(ItemDrop)) { return "item"; } if (typeof(StatusEffect).IsAssignableFrom(type)) { return "status effect"; } if (type == typeof(CraftingStation)) { return "crafting station"; } return "prefab"; } public static Object Find(string name, Type type) { if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } if (type == typeof(ItemDrop)) { return (Object)(object)FindItem(name); } if (type == typeof(GameObject)) { return (Object)(object)FindPrefab(name); } if (typeof(StatusEffect).IsAssignableFrom(type)) { foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { if ((Object)(object)statusEffect != (Object)null && type.IsInstanceOfType(statusEffect) && ((Object)statusEffect).name.Equals(name, StringComparison.OrdinalIgnoreCase)) { return (Object)(object)statusEffect; } } return null; } if (type == typeof(CraftingStation)) { return (Object)(object)FindStation(name); } return null; } public static ItemDrop FindItem(string name) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name); if ((Object)(object)itemPrefab != (Object)null) { return itemPrefab.GetComponent<ItemDrop>(); } foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item != (Object)null && ((Object)item).name.Equals(name, StringComparison.OrdinalIgnoreCase)) { return item.GetComponent<ItemDrop>(); } } return null; } private static GameObject FindPrefab(string name) { if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(name); if ((Object)(object)prefab != (Object)null) { return prefab; } foreach (GameObject prefab2 in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab2 != (Object)null && ((Object)prefab2).name.Equals(name, StringComparison.OrdinalIgnoreCase)) { return prefab2; } } } ItemDrop val = FindItem(name); if (!((Object)(object)val != (Object)null)) { return null; } return ((Component)val).gameObject; } private static CraftingStation FindStation(string name) { string value = name.TrimStart('$'); foreach (CraftingStation item in Stations()) { if (((Object)((Component)item).gameObject).name.Equals(name, StringComparison.OrdinalIgnoreCase) || item.m_name.TrimStart('$').Equals(value, StringComparison.OrdinalIgnoreCase)) { return item; } } return null; } private static List<CraftingStation> Stations() { List<CraftingStation> list = new List<CraftingStation>(); foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if ((Object)(object)recipe != (Object)null) { AddStation(list, recipe.m_craftingStation); AddStation(list, recipe.m_repairStation); } } foreach (GameObject item in ObjectDB.instance.m_items) { PieceTable val = ((!((Object)(object)item != (Object)null)) ? null : item.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_buildPieces); if ((Object)(object)val == (Object)null) { continue; } foreach (GameObject piece in val.m_pieces) { if ((Object)(object)piece != (Object)null) { AddStation(list, piece.GetComponent<CraftingStation>()); } } } return list; } private static void AddStation(List<CraftingStation> stations, CraftingStation station) { if ((Object)(object)station != (Object)null && !stations.Contains(station)) { stations.Add(station); } } } public class PrefabTarget : Target { private static readonly Type[] mainTypes = new Type[6] { typeof(Character), typeof(Piece), typeof(WearNTear), typeof(BaseAI), typeof(CharacterDrop), typeof(Tameable) }; private static readonly ItemType[] attackTypes; private readonly List<Component> components; private readonly List<ItemDrop> attacks; public PrefabTarget(GameObject prefab, string header) { Root = prefab; Header = header; NeedsServer = true; components = Components(prefab); attacks = Attacks(prefab); } public override bool TryApply(string path, TweakValue value, out string error) { string text = FirstPart(path); string text2 = RestOfPath(path); if (attacks != null && text.Equals("attacks", StringComparison.OrdinalIgnoreCase)) { return TryApplyToAttacks(text2, value, out error); } Component val = FindComponent(text); if ((Object)(object)val != (Object)null) { if (text2 == "") { error = text + " is a part of the prefab, so it needs the name of one of its stats, e.g. " + text + ".<stat>"; return false; } return TryApplyToComponent(val, text2, value, out error); } foreach (Component component in components) { if (StatPath.FindField(((object)component).GetType(), text) != null) { return TryApplyToComponent(component, path, value, out error); } } error = "there's no stat \"" + text + "\"" + StatPath.Suggest(components.ConvertAll((Component component) => ((object)component).GetType()), text); return false; } public override void List(List<string> lines) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (Component component in components) { List<string> list = new List<string>(); StatPath.List(component, list); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string item2 in list) { string item = FirstPart(item2.Substring(0, item2.IndexOf(" = ", StringComparison.Ordinal))); hashSet2.Add(item); lines.Add((hashSet.Contains(item) ? (((object)component).GetType().Name + ".") : "") + item2); } hashSet.UnionWith(hashSet2); } if (attacks == null) { return; } foreach (ItemDrop attack in attacks) { List<string> list2 = new List<string>(); StatPath.List(attack.m_itemData.m_shared, list2); foreach (string item3 in list2) { lines.Add("attacks." + ((Object)((Component)attack).gameObject).name + "." + item3); } } } private static bool TryApplyToComponent(Component component, string path, TweakValue value, out string error) { if (!StatPath.TryApply(component, path, value, out error)) { return false; } FieldInfo fieldInfo = StatPath.FindField(((object)component).GetType(), FirstPart(path)); if (fieldInfo != null) { Instances.Touch(component, fieldInfo); } return true; } private bool TryApplyToAttacks(string path, TweakValue value, out string error) { error = null; List<ItemDrop> list = attacks; string value2 = FirstPart(path); foreach (ItemDrop attack in attacks) { if (((Object)((Component)attack).gameObject).name.Equals(value2, StringComparison.OrdinalIgnoreCase)) { list = new List<ItemDrop> { attack }; path = RestOfPath(path); break; } } if (path == "") { error = "attacks needs the name of a stat, e.g. attacks.damages, or an attack and a stat, e.g. attacks." + ((attacks.Count > 0) ? ((Object)((Component)attacks[0]).gameObject).name : "<attack>") + ".damages"; return false; } foreach (ItemDrop item in list) { if (!StatPath.TryApply(item.m_itemData.m_shared, path, value, out error)) { return false; } } return true; } private Component FindComponent(string name) { foreach (Component component in components) { Type type = ((object)component).GetType(); while (type != null && type != typeof(MonoBehaviour)) { if (type.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { return component; } type = type.BaseType; } } return null; } private static List<Component> Components(GameObject prefab) { List<Component> list = new List<Component>(); MonoBehaviour[] array = prefab.GetComponents<MonoBehaviour>(); foreach (MonoBehaviour val in array) { if (!((Object)(object)val == (Object)null) && !(val is ZNetView) && !((object)val).GetType().Name.StartsWith("ZSync", StringComparison.Ordinal)) { list.Add((Component)(object)val); } } List<Component> list2 = new List<Component>(); Type[] array2 = mainTypes; foreach (Type type in array2) { foreach (Component item in list) { if (type.IsInstanceOfType(item) && !list2.Contains(item)) { list2.Add(item); } } } foreach (Component item2 in list) { if (!list2.Contains(item2)) { list2.Add(item2); } } return list2; } private static List<ItemDrop> Attacks(GameObject prefab) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) Humanoid component = prefab.GetComponent<Humanoid>(); if ((Object)(object)component == (Object)null) { return null; } List<GameObject> list = new List<GameObject>(); AddRange(list, component.m_defaultItems); AddRange(list, component.m_randomWeapon); if (component.m_randomSets != null) { ItemSet[] randomSets = component.m_randomSets; foreach (ItemSet val in randomSets) { AddRange(list, val.m_items); } } if (component.m_randomItems != null) { RandomItem[] randomItems = component.m_randomItems; foreach (RandomItem val2 in randomItems) { list.Add(val2.m_prefab); } } List<ItemDrop> list2 = new List<ItemDrop>(); foreach (GameObject item in list) { ItemDrop val3 = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if ((Object)(object)val3 != (Object)null && !list2.Contains(val3) && Array.IndexOf(attackTypes, val3.m_itemData.m_shared.m_itemType) >= 0) { list2.Add(val3); } } return list2; } private static void AddRange(List<GameObject> list, GameObject[] prefabs) { if (prefabs != null) { list.AddRange(prefabs); } } private static string FirstPart(string path) { int num = path.IndexOf('.'); return ((num >= 0) ? path.Substring(0, num) : path).Trim(); } private static string RestOfPath(string path) { int num = path.IndexOf('.'); if (num < 0) { return ""; } return path.Substring(num + 1).Trim(); } static PrefabTarget() { ItemType[] array = new ItemType[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); attackTypes = (ItemType[])(object)array; } } public class ReferenceComparer : IEqualityComparer<object> { public static readonly ReferenceComparer Instance = new ReferenceComparer(); public new bool Equals(object a, object b) { return a == b; } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } public static class ServerSync { private const string VersionRpc = "kriona.TweakStats.Version"; private const string ConfigRpc = "kriona.TweakStats.Config"; private const string RefusedRpc = "kriona.TweakStats.Refused"; private static readonly Dictionary<ZRpc, string> clientVersions = new Dictionary<ZRpc, string>(); public static void OnNewConnection(ZNetPeer peer) { if (ZNet.instance.IsServer()) { peer.m_rpc.Register<string>("kriona.TweakStats.Version", (Action<ZRpc, string>)RPC_Version); return; } peer.m_rpc.Register<ZPackage>("kriona.TweakStats.Config", (Action<ZRpc, ZPackage>)RPC_Config); peer.m_rpc.Register<string>("kriona.TweakStats.Refused", (Action<ZRpc, string>)RPC_Refused); peer.m_rpc.Invoke("kriona.TweakStats.Version", new object[1] { "1.0.0" }); } public static bool Allow(ZRpc rpc) { if (!Tweaks.RequireMod) { return true; } if (!clientVersions.TryGetValue(rpc, out var value)) { Plugin.Log.LogInfo((object)"Refused a player without Tweak Stats, which this server requires"); rpc.Invoke("Error", new object[1] { 3 }); return false; } if (!SameMinorVersion(value, "1.0.0")) { Plugin.Log.LogInfo((object)("Refused a player with Tweak Stats " + value + ", since this server has 1.0.0")); rpc.Invoke("kriona.TweakStats.Refused", new object[1] { "This server requires Tweak Stats " + MinorVersion("1.0.0") + ", and you have " + value }); rpc.Invoke("Error", new object[1] { 3 }); return false; } return true; } public static void SendConfig(ZRpc rpc) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (clientVersions.ContainsKey(rpc)) { ZPackage val = new ZPackage(); val.Write(Tweaks.LocalText); rpc.Invoke("kriona.TweakStats.Config", new object[1] { val }); } } public static void SendConfigToAll() { if ((Object)(object)ZNet.instance == (Object)null) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.m_rpc != null && peer.m_uid != 0L) { SendConfig(peer.m_rpc); } } } public static void Clear() { clientVersions.Clear(); } private static void RPC_Version(ZRpc rpc, string version) { ForgetDisconnected(); clientVersions[rpc] = version; } private static void ForgetDisconnected() { HashSet<ZRpc> hashSet = new HashSet<ZRpc>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { hashSet.Add(peer.m_rpc); } List<ZRpc> list = new List<ZRpc>(); foreach (ZRpc key in clientVersions.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (ZRpc item in list) { clientVersions.Remove(item); } } private static void RPC_Config(ZRpc rpc, ZPackage package) { Tweaks.UseServerConfig(package.ReadString()); } private static void RPC_Refused(ZRpc rpc, string reason) { Plugin.Log.LogWarning((object)reason); } private static bool SameMinorVersion(string a, string b) { return MinorVersion(a) == MinorVersion(b); } private static string MinorVersion(string version) { string[] array = version.Split('.'); if (array.Length < 2) { return version; } return array[0] + "." + array[1]; } } public static class StatPath { private class ListEntry { public Type ElementType; public string KeyField; public string DefaultField; public bool RemoveWhenZero; public Func<object, string> Key; public Func<string, (object entry, string error)> Create; } private const int MaxDepth = 5; private unsafe static readonly List<ListEntry> listEntries = new List<ListEntry> { new ListEntry { ElementType = typeof(Requirement), KeyField = "m_resItem", DefaultField = "m_amount", RemoveWhenZero = true, Key = delegate(object entry) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ItemDrop resItem = ((Requirement)entry).m_resItem; return (resItem == null) ? null : ((Object)((Component)resItem).gameObject).name; }, Create = CreateRequirement }, new ListEntry { ElementType = typeof(DamageModPair), KeyField = "m_type", DefaultField = "m_modifier", RemoveWhenZero = false, Key = delegate(object entry) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) DamageModPair val = (DamageModPair)entry; return ((object)(*(DamageType*)(&val.m_type))/*cast due to .constrained prefix*/).ToString(); }, Create = CreateDamageModPair }, new ListEntry { ElementType = typeof(Drop), KeyField = "m_prefab", DefaultField = "m_chance", RemoveWhenZero = true, Key = delegate(object entry) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) GameObject prefab = ((Drop)entry).m_prefab; return (prefab == null) ? null : ((Object)prefab).name; }, Create = CreateDrop } }; private static readonly Dictionary<Type, List<FieldInfo>> fieldCache = new Dictionary<Type, List<FieldInfo>>(); public static bool TryApply(object root, string path, TweakValue value, out string error) { string[] array = path.Split('.'); for (int i = 0; i < array.Length; i++) { array[i] = array[i].Trim(); if (array[i] == "") { error = "\"" + path + "\" has an empty part between its dots"; return false; } } return TryApplyToField(root, array, 0, value, out error); } public static void List(object root, List<string> lines) { ListFields(root, "", lines, 0, null); } private static List<FieldInfo> GetFields(Type type) { if (fieldCache.TryGetValue(type, out var value)) { return value; } value = new List<FieldInfo>(); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!fieldInfo.IsNotSerialized && !fieldInfo.Name.Contains("<") && (fieldInfo.IsPublic || fieldInfo.IsDefined(typeof(SerializeField), inherit: true))) { value.Add(fieldInfo); } } fieldCache.Add(type, value); return value; } private static string PathName(FieldInfo field) { if (!field.Name.StartsWith("m_")) { return field.Name; } return field.Name.Substring(2); } public static FieldInfo FindField(Type type, string name) { foreach (FieldInfo field in GetFields(type)) { if (PathName(field).Equals(name, StringComparison.OrdinalIgnoreCase) || field.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { return field; } } return null; } private static bool TryApplyToField(object owner, string[] parts, int index, TweakValue value, out string error) { error = null; FieldInfo fieldInfo = FindField(owner.GetType(), parts[index]); if (fieldInfo == null) { error = "there's no stat \"" + JoinPath(parts, index + 1) + "\"" + Suggest(owner.GetType(), parts[index]); return false; } Type fieldType = fieldInfo.FieldType; object value2 = fieldInfo.GetValue(owner); bool flag = index == parts.Length - 1; object result; if (IsList(fieldType)) { if (flag) { error = JoinPath(parts, index + 1) + " is a list, so it needs the name of an entry, e.g. " + JoinPath(parts, index + 1) + "." + ExampleKey(fieldType); return false; } if (!TryApplyToList(value2, fieldType, parts, index + 1, value, out result, out error)) { return false; } } else if (flag) { if (TweakValue.CanSet(fieldType)) { if (!value.TryApply(value2, fieldType, out result, out error)) { return false; } result = Limits.Clamp(fieldInfo, result); } else { if (!IsUniformGroup(fieldType)) { error = JoinPath(parts, index + 1) + " is a group of stats, so it needs the name of one" + ExampleField(fieldType, JoinPath(parts, index + 1)); return false; } result = value2; foreach (FieldInfo field in GetFields(fieldType)) { if (!value.TryApply(field.GetValue(result), field.FieldType, out var result2, out error)) { return false; } field.SetValue(result, Limits.Clamp(field, result2)); } } } else { if (typeof(Object).IsAssignableFrom(fieldType)) { error = JoinPath(parts, index + 1) + " points to another object, which can't be changed from here"; return false; } if (!IsGroup(fieldType)) { error = JoinPath(parts, index + 1) + " is a single stat, so it can't be followed by ." + parts[index + 1]; return false; } if (value2 == null) { return true; } if (!TryApplyToField(value2, parts, index + 1, value, out error)) { return false; } if (!fieldType.IsValueType) { return true; } result = value2; } Write(owner, fieldInfo, result); return true; } private static bool TryApplyToList(object current, Type listType, string[] parts, int index, TweakValue value, out object result, out string error) { result = current; error = null; Type type = ElementType(listType); ListEntry listEntry = FindListEntry(type); if (listEntry == null) { error = "TweakStats can't change the entries of " + JoinPath(parts, index); return false; } List<object> list = new List<object>(); if (current != null) { foreach (object item in (IEnumerable)current) { list.Add(item); } } string text = parts[index]; bool flag = index == parts.Length - 1; bool flag2 = flag && listEntry.RemoveWhenZero && value.Operation == TweakOperation.Set && value.IsNumber && value.Number == 0.0; List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { if (list[i] != null && string.Equals(listEntry.Key(list[i]), text, StringComparison.OrdinalIgnoreCase)) { list2.Add(i); } } if (list2.Count == 0) { if (flag2) { return true; } var (obj, text2) = listEntry.Create(text); if (obj == null) { error = text2; return false; } list.Add(obj); list2.Add(list.Count - 1); } if (flag2) { for (int num = list2.Count - 1; num >= 0; num--) { list.RemoveAt(list2[num]); } } else { foreach (int item2 in list2) { object obj2 = list[item2]; if (flag) { FieldInfo field = type.GetField(listEntry.DefaultField); if (!value.TryApply(field.GetValue(obj2), field.FieldType, out var result2, out error)) { return false; } Write(obj2, field, Limits.Clamp(field, result2)); } else if (!TryApplyToField(obj2, parts, index + 1, value, out error)) { return false; } list[item2] = obj2; } } result = MakeList(listType, type, list); return true; } private static void Write(object owner, FieldInfo field, object value) { if (owner.GetType().IsValueType) { field.SetValue(owner, value); } else { Originals.Set(owner, field, value); } } private static void ListFields(object owner, string prefix, List<string> lines, int depth, ICollection<string> skip) { foreach (FieldInfo field in GetFields(owner.GetType())) { if (skip != null && skip.Contains(field.Name)) { continue; } string text = prefix + PathName(field); Type fieldType = field.FieldType; object value = field.GetValue(owner); if (TweakValue.CanSet(fieldType)) { lines.Add(text + " = " + TweakValue.Format(value)); } else if (IsList(fieldType)) { ListEntry listEntry = FindListEntry(ElementType(fieldType)); if (listEntry == null || value == null) { continue; } string[] skip2 = new string[2] { listEntry.KeyField, listEntry.DefaultField }; foreach (object item in (IEnumerable)value) { string text2 = ((item != null) ? listEntry.Key(item) : null); if (!string.IsNullOrEmpty(text2)) { lines.Add(text + "." + text2 + " = " + TweakValue.Format(listEntry.ElementType.GetField(listEntry.DefaultField).GetValue(item))); ListFields(item, text + "." + text2 + ".", lines, depth + 1, skip2); } } } else if (IsGroup(fieldType) && value != null && depth < 5) { ListFields(value, text + ".", lines, depth + 1, null); } } } private static bool IsList(Type type) { if (!type.IsArray) { if (type.IsGenericType) { return type.GetGenericTypeDefinition() == typeof(List<>); } return false; } return true; } private static Type ElementType(Type listType) { if (!listType.IsArray) { return listType.GetGenericArguments()[0]; } return listType.GetElementType(); } private static object MakeList(Type listType, Type elementType, List<object> entries) { if (listType.IsArray) { Array array = Array.CreateInstance(elementType, entries.Count); for (int i = 0; i < entries.Count; i++) { array.SetValue(entries[i], i); } return array; } IList list = (IList)Activator.CreateInstance(listType); foreach (object entry in entries) { list.Add(entry); } return list; } private static ListEntry FindListEntry(Type elementType) { foreach (ListEntry listEntry in listEntries) { if (listEntry.ElementType == elementType) { return listEntry; } } return null; } private static bool IsGroup(Type type) { if (type.IsPrimitive || type.IsEnum || type == typeof(string) || IsList(type) || typeof(Object).IsAssignableFrom(type) || typeof(Delegate).IsAssignableFrom(type)) { return false; } return type.IsSerializable; } private static bool IsUniformGroup(Type type) { if (!type.IsValueType || !IsGroup(type)) { return false; } List<FieldInfo> fields = GetFields(type); if (fields.Count < 2 || !TweakValue.CanSet(fields[0].FieldType)) { return false; } foreach (FieldInfo item in fields) { if (item.FieldType != fields[0].FieldType) { return false; } } return true; } private static string JoinPath(string[] parts, int count) { return string.Join(".", parts, 0, count); } private static string Suggest(Type type, string name) { return Suggest(new Type[1] { type }, name); } public static string Suggest(IEnumerable<Type> types, string name) { List<string> list = new List<string>(); foreach (Type type in types) { foreach (FieldInfo field in GetFields(type)) { string text = PathName(field); if (((name.Length >= 3 && text.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) || Distance(text.ToLowerInvariant(), name.ToLowerInvariant()) <= 2) && !list.Contains(text)) { list.Add(text); } } } if (list.Count == 0 || list.Count > 5) { return ""; } return " - did you mean " + string.Join(" or ", list) + "?"; } private static int Distance(string a, string b) { int[] array = new int[b.Length + 1]; int[] array2 = new int[b.Length + 1]; for (int i = 0; i <= b.Length; i++) { array[i] = i; } for (int j = 1; j <= a.Length; j++) { array2[0] = j; for (int k = 1; k <= b.Length; k++) { int num = ((a[j - 1] != b[k - 1]) ? 1 : 0); array2[k] = Math.Min(Math.Min(array2[k - 1] + 1, array[k] + 1), array[k - 1] + num); } int[] array3 = array; array = array2; array2 = array3; } return array[b.Length]; } private static string ExampleKey(Type listType) { if (ElementType(listType) == typeof(DamageModPair)) { return "Fire"; } return "Wood"; } private static string ExampleField(Type type, string path) { foreach (FieldInfo field in GetFields(type)) { if (TweakValue.CanSet(field.FieldType)) { return ", e.g. " + path + "." + PathName(field); } } return ""; } private static (object entry, string error) CreateRequirement(string name) { //IL_0037: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown ItemDrop val = (((Object)(object)ObjectDB.instance != (Object)null) ? Prefabs.FindItem(name) : null); if ((Object)(object)val == (Object)null) { return (entry: null, error: "there's no item named \"" + name + "\""); } return (entry: (object)new Requirement { m_resItem = val, m_amount = 0, m_amountPerLevel = 0, m_recover = true }, error: null); } private static (object entry, string error) CreateDrop(string name) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown GameObject val = (GameObject)Prefabs.Find(name, typeof(GameObject)); if ((Object)(object)val == (Object)null) { return (entry: null, error: "there's no item or prefab named \"" + name + "\""); } return (entry: (object)new Drop { m_prefab = val, m_amountMin = 1, m_amountMax = 1, m_chance = 1f }, error: null); } private static (object entry, string error) CreateDamageModPair(string name) { //IL_0024: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) string[] names = Enum.GetNames(typeof(DamageType)); foreach (string text in names) { if (text.Equals(name, StringComparison.OrdinalIgnoreCase)) { return (entry: (object)new DamageModPair { m_type = (DamageType)Enum.Parse(typeof(DamageType), text), m_modifier = (DamageModifier)0 }, error: null); } } return (entry: null, error: "\"" + name + "\" isn't a damage type - use one of " + string.Join(", ", Enum.GetNames(typeof(DamageType)))); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] public static class ZNetNewConnectionPatch { private static void Postfix(ZNetPeer peer) { ServerSync.OnNewConnection(peer); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] public static class ZNetPeerInfoSyncPatch { private static bool Prefix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { return ServerSync.Allow(rpc); } return true; } private static void Postfix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null && peer.m_uid != 0L) { ServerSync.SendConfig(rpc); } } } } public class Target { public object Root; public string Header; public bool NeedsServer; public virtual bool TryApply(string path, TweakValue value, out string error) { return StatPath.TryApply(Root, path, value, out error); } public virtual void List(List<string> lines) { StatPath.List(Root, lines); } } public static class Targets { public static List<Target> Resolve(List<string> selectors, List<string> problems, List<string> skipped) { List<Target> list = new List<Target>(); HashSet<object> hashSet = new HashSet<object>(ReferenceComparer.Instance); foreach (string selector in selectors) { if (skipped != null && !Tweaks.WorldTweaksApply && IsWorldKind(selector)) { skipped.Add(selector); continue; } string error; foreach (Target item in Resolve(selector, out error)) { if (hashSet.Add(item.Root)) { list.Add(item); } } if (error != null) { problems.Add(error); } } return list; } private static List<Target> Resolve(string selector, out string error) { //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) List<Target> list = new List<Target>(); error = null; int num = selector.IndexOf(':'); string text = ((num >= 0) ? selector.Substring(0, num).Trim() : ""); string text2 = ((num >= 0) ? selector.Substring(num + 1).Trim() : selector.Trim()); if (text == "") { Regex regex = Pattern(text2); foreach (ItemDrop item in Prefabs.Items()) { if (regex.IsMatch(((Object)((Component)item).gameObject).name)) { list.Add(ItemTarget(item)); } } if (list.Count == 0) { error = (IsWildcard(text2) ? ("no items match \"" + text2 + "\"") : ("there's no item named \"" + text2 + "\" - the tweakstats find console command looks up an item's name")); } } else if (text.Equals("Skill", StringComparison.OrdinalIgnoreCase)) { if (!TryParseEnum<SkillType>(text2, out SkillType value)) { error = "\"" + text2 + "\" isn't a skill - use one of " + string.Join(", ", Enum.GetNames(typeof(SkillType))); return list; } foreach (ItemDrop item2 in Prefabs.Items()) { if (item2.m_itemData.m_shared.m_skillType == value) { list.Add(ItemTarget(item2)); } } } else if (text.Equals("Type", StringComparison.OrdinalIgnoreCase)) { if (!TryParseEnum<ItemType>(text2, out ItemType value2)) { error = "\"" + text2 + "\" isn't an item type - use one of " + string.Join(", ", Enum.GetNames(typeof(ItemType))); return list; } foreach (ItemDrop item3 in Prefabs.Items()) { if (item3.m_itemData.m_shared.m_itemType == value2) { list.Add(ItemTarget(item3)); } } } else if (text.Equals("Recipe", StringComparison.OrdinalIgnoreCase)) { Regex regex2 = Pattern(text2); foreach (Recipe item4 in Prefabs.Recipes()) { if (regex2.IsMatch(((Object)((Component)item4.m_item).gameObject).name) || regex2.IsMatch(((Object)item4).name)) { list.Add(new Target { Root = item4, Header = "Recipe:" + ((Object)item4).name }); } } if (list.Count == 0) { error = (IsWildcard(text2) ? ("no recipes match \"" + text2 + "\"") : ("there's no recipe for \"" + text2 + "\"")); } } else if (text.Equals("Creature", StringComparison.OrdinalIgnoreCase)) { Regex regex3 = Pattern(text2); foreach (GameObject item5 in Prefabs.Creatures()) { if (regex3.IsMatch(((Object)item5).name)) { list.Add(new PrefabTarget(item5, "Creature:" + ((Object)item5).name)); } } if (list.Count == 0) { error = (IsWildcard(text2) ? ("no creatures match \"" + text2 + "\"") : ("there's no creature named \"" + text2 + "\"")); } } else if (text.Equals("Piece", StringComparison.OrdinalIgnoreCase)) { Regex regex4 = Pattern(text2); foreach (GameObject item6 in Prefabs.Pieces()) { if (regex4.IsMatch(((Object)item6).name)) { list.Add(new PrefabTarget(item6, "Piece:" + ((Object)item6).name)); } } if (list.Count == 0) { error = (IsWildcard(text2) ? ("no build pieces match \"" + text2 + "\"") : ("there's no build piece named \"" + text2 + "\"")); } } else if (text.Equals("Effect", StringComparison.OrdinalIgnoreCase)) { Regex regex5 = Pattern(text2); HashSet<StatusEffect> hashSet = Prefabs.HitEffects(); foreach (StatusEffect item7 in Prefabs.Effects()) { if (regex5.IsMatch(((Object)item7).name)) { list.Add(new Target { Root = item7, Header = "Effect:" + ((Object)item7).name, NeedsServer = hashSet.Contains(item7) }); } } if (list.Count == 0) { error = (IsWildcard(text2) ? ("no status effects match \"" + text2 + "\"") : ("there's no status effect named \"" + text2 + "\"")); } } else { error = "\"" + text + ":\" isn't something TweakStats can change - use an item name, Skill:, Type:, Recipe:, Creature:, Piece: or Effect:"; } return list; } private static bool IsWorldKind(string selector) { string text = selector.TrimStart(); if (!text.StartsWith("Creature:", StringComparison.OrdinalIgnoreCase)) { return text.StartsWith("Piece:", StringComparison.OrdinalIgnoreCase); } return true; } private static Target ItemTarget(ItemDrop item) { return new Target { Root = item.m_itemData.m_shared, Header = ((Object)((Component)item).gameObject).name }; } private static bool IsWildcard(string name) { if (name.IndexOf('*') < 0) { return name.IndexOf('?') >= 0; } return true; } private static Regex Pattern(string name) { return new Regex("^" + Regex.Escape(name).Replace("\\*", ".*").Replace("\\?", ".") + "$", RegexOptions.IgnoreCase); } private static bool TryParseEnum<T>(string name, out T value) where T : struct { value = default(T); string[] names = Enum.GetNames(typeof(T)); foreach (string text in names) { if (text.Equals(name, StringComparison.OrdinalIgnoreCase)) { value = (T)Enum.Parse(typeof(T), text); return true; } } return false; } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] public static class TerminalInitPatch { private static void Postfix() { Commands.Register(); } } public class TweakLine { public int LineNumber; public string Path; public TweakValue Value; } public class TweakSection { public int LineNumber; public string Header; public List<string> Selectors = new List<string>(); public List<string> Worlds; public List<TweakLine> Lines = new List<TweakLine>(); } public class TweakFile { public List<TweakSection> Sections = new List<TweakSection>(); public Dictionary<string, string> Settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, string[]> settingValues = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) { { "requireMod", new string[3] { "auto", "true", "false" } } }; public List<string> Problems = new List<string>(); public static TweakFile Parse(string text) { TweakFile tweakFile = new TweakFile(); TweakSection tweakSection = null; List<string> list = null; int lineNumber = 0; bool flag = false; string[] array = text.Split('\n'); for (int i = 0; i < array.Length; i++) { int num = i + 1; string text2 = StripComment(array[i]).Trim(); if (text2 == "") { continue; } if (text2.StartsWith("[")) { flag = false; if (!text2.EndsWith("]")) { tweakFile.AddProblem(num, "\"" + text2 + "\" starts with [ but doesn't end with ]"); tweakSection = null; continue; } string text3 = text2.Substring(1, text2.Length - 2).Trim(); List<string> names; if (IsWorldsEnd(text3)) { if (list == null) { tweakFile.AddProblem(num, "[" + text3 + "] doesn't have a [Worlds: ...] before it"); } list = null; tweakSection = null; } else if (TryReadWorlds(text3, out names)) { if (list != null) { tweakFile.AddProblem(num, "[Worlds: ...] groups can't be inside each other - add [/Worlds] to end the group on line " + lineNumber + " first"); } if (names.Count == 0) { tweakFile.AddProblem(num, "[" + text3 + "] doesn't name any worlds"); } list = names; lineNumber = num; tweakSection = null; } else if (text3.Equals("Settings", StringComparison.OrdinalIgnoreCase)) { if (list != null) { tweakFile.AddProblem(num, "[Settings] can't be inside a [Worlds: ...] group, so its settings apply everywhere"); } flag = true; tweakSection = null; } else { tweakSection = new TweakSection { LineNumber = num, Header = text3, Selectors = SplitList(text3), Worlds = list }; if (tweakSection.Selectors.Count == 0) { tweakFile.AddProblem(num, "[] needs the name of what to change, e.g. [SwordIron]"); tweakSection = null; } else { tweakFile.Sections.Add(tweakSection); } } continue; } int num2 = text2.IndexOf('='); if (num2 < 0) { tweakFile.AddProblem(num, "\"" + text2 + "\" needs to be stat = value"); continue; } if (flag) { tweakFile.ReadSetting(num, text2.Substring(0, num2).Trim(), text2.Substring(num2 + 1).Trim()); continue; } if (tweakSection == null) { tweakFile.AddProblem(num, "\"" + text2 + "\" needs a [section] above it saying what it changes, e.g. [SwordIron]"); continue; } string text4 = text2.Substring(0, num2).Trim(); if (text4 == "") { tweakFile.AddProblem(num, "\"" + text2 + "\" needs the name of a stat before the ="); continue; } if (!TweakValue.TryParse(text2.Substring(num2 + 1), out var value, out var error)) { tweakFile.AddProblem(num, text4 + ": " + error); continue; } tweakSection.Lines.Add(new TweakLine { LineNumber = num, Path = text4, Value = value }); } if (list != null) { tweakFile.AddProblem(lineNumber, "[Worlds: ...] has no [/Worlds] to end it, so it runs to the end of the file"); } return tweakFile; } private void ReadSetting(int lineNumber, string name, string value) { if (!settingValues.TryGetValue(name, out var value2)) { AddProblem(lineNumber, "there's no setting \"" + name + "\" - use one of " + string.Join(", ", settingValues.Keys)); return; } string[] array = value2; foreach (string text in array) { if (text.Equals(value, StringComparison.OrdinalIgnoreCase)) { Settings[name] = text; return; } } AddProblem(lineNumber, name + " needs one of " + string.Join(", ", value2) + ", not \"" + value + "\""); } private void AddProblem(int lineNumber, string message) { Problems.Add("line " + lineNumber + ": " + message); } private static string StripComment(string line) { string text = line.TrimStart(); if (text.StartsWith("#") || text.StartsWith(";")) { return ""; } for (int i = 1; i < line.Length; i++) { if (line[i] == '#' && char.IsWhiteSpace(line[i - 1])) { return line.Substring(0, i); } } return line; } private static bool IsWorldsEnd(string header) { if (!header.Equals("/Worlds", StringComparison.OrdinalIgnoreCase)) { return header.Equals("/World", StringComparison.OrdinalIgnoreCase); } return true; } private static bool TryReadWorlds(string header, out List<string> names) { names = null; int num = header.IndexOf(':'); if (num < 0) { return false; } string text = header.Substring(0, num).Trim(); if (!text.Equals("Worlds", StringComparison.OrdinalIgnoreCase) && !text.Equals("World", StringComparison.OrdinalIgnoreCase)) { return false; } names = SplitList(header.Substring(num + 1)); return true; } private static List<string> SplitList(string text) { List<string> list = new List<string>(); string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2 != "") { list.Add(text2); } } return list; } } public static class Tweaks { private const int NamesShown = 3; public static World CurrentWorld; public static string LocalText = ""; private static TweakFile localFile = new TweakFile(); private static TweakFile serverFile; private static string pendingMessage; private static readonly List<string> skippedNotes = new List<string>(); private static bool skippedNoticed; private static readonly HashSet<string> reported = new HashSet<string>(); public static bool RunsWorld { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static bool UsingServerConfig => serverFile != null; public static bool WorldTweaksApply { get { if (!RunsWorld) { return UsingServerConfig; } return true; } } public static bool RequireMod { get { if (!localFile.Settings.TryGetValue("requireMod", out var value) || value == "auto") { return HasWorldSections(localFile); } return value == "true"; } } public static void Load() { reported.Clear(); string text; try { text = (File.Exists(Plugin.ConfigPath) ? File.ReadAllText(Plugin.ConfigPath) : ""); } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't read " + Plugin.ConfigPath + ": " + ex.Message)); return; } LocalText = text; localFile = TweakFile.Parse(text); foreach (string problem in localFile.Problems) { Report(Path.GetFileName(Plugin.ConfigPath) + " " + problem); } } public static void Reload() { Load(); if (RunsWorld) { ServerSync.SendConfigToAll(); } Apply(); Plugin.Log.LogInfo((object)("Reloaded " + Path.GetFileName(Plugin.ConfigPath))); string text = "Tweak Stats: config reloaded"; if (UsingServerConfig) { text += ", but this server's config is used while you're on it"; } else if (reported.Count > 0) { text = text + " with " + reported.Count + ((reported.Count == 1) ? " problem" : " problems") + " - see the BepInEx log"; } MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } if (skippedNoticed) { NoticeSkipped(); } } public static void UseServerConfig(string text) { serverFile = TweakFile.Parse(text); foreach (string problem in serverFile.Problems) { Report("the server's config " + problem); } Plugin.Log.LogInfo((object)"Using the server's config"); if (localFile.Sections.Count > 0) { pendingMessage = "Tweak Stats: this server has its own config, so your " + Path.GetFileName(Plugin.ConfigPath) + " is ignored while you're on it"; Plugin.Log.LogInfo((object)pendingMessage); } Apply(); } public static void ClearServerConfig() { serverFile = null; pendingMessage = null; skippedNoticed = false; } public static void ShowPendingMessage() { if (!((Object)(object)MessageHud.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if (pendingMessage != null) { MessageHud.instance.ShowMessage((MessageType)2, pendingMessage, 0, (Sprite)null, false, true); pendingMessage = null; } if (!skippedNoticed) { skippedNoticed = true; NoticeSkipped(); } } } private static void NoticeSkipped() { if (skippedNotes.Count == 0) { return; } foreach (string skippedNote in skippedNotes) { Report(skippedNote); } string text = "Tweak Stats: this server doesn't have Tweak Stats, so " + skippedNotes.Count + ((skippedNotes.Count == 1) ? " section is" : " sections are") + " skipped - see the BepInEx log"; MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } public static void Apply() { Instances.StartApply(); Originals.Restore(); skippedNotes.Clear(); if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (TweakSection section in (serverFile ?? localFile).Sections) { if (section.Worlds == null || InWorld(section.Worlds)) { ApplySection(section); } } } Instances.Sync(); Instances.RefreshEquipmentEffects(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Humanoid)localPlayer).GetInventory().Changed(false, false); } } public static void WithoutTweaks(Action action) { Originals.Restore(); try { action(); } finally { Apply(); } } private static void ApplySection(TweakSection section) { string text = "line " + section.LineNumber + " [" + section.Header + "]: "; string text2 = Source() + " "; List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<Target> list3 = Targets.Resolve(section.Selectors, list, list2); if (!WorldTweaksApply) { for (int num = list3.Count - 1; num >= 0; num--) { if (list3[num].NeedsServer) { list2.Add(list3[num].Header); list3.RemoveAt(num); } } } foreach (string item in list) { Report(text2 + text + item); } if (list2.Count > 0) { skippedNotes.Add(text2 + text + "skipped " + DescribeNames(list2) + " - creature, build piece and hit effect tweaks only apply in single player, when hosting, or on a server that has Tweak Stats"); } foreach (TweakLine line in section.Lines) { Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(); foreach (Target item2 in list3) { string error; bool num2 = item2.TryApply(line.Path, line.Value, out error); List<string> list4 = Limits.TakeNotes(); if (!num2 && error != null) { list4.Add(error); } foreach (string item3 in list4) { if (!dictionary.TryGetValue(item3, out var value)) { value = new List<string>(); dictionary.Add(item3, value); } value.Add(item2.Header); } } foreach (KeyValuePair<string, List<string>> item4 in dictionary) { string text3 = text2 + "line " + line.LineNumber + " [" + section.Header + "]"; if (item4.Value.Count < list3.Count) { text3 = text3 + " " + DescribeNames(item4.Value); } Report(text3 + ": " + line.Path + ": " + item4.Key); } } } private static bool InWorld(List<string> worlds) { if (CurrentWorld == null) { return false; } foreach (string world in worlds) { if (world.Equals("SinglePlayer", StringComparison.OrdinalIgnoreCase) && ZNet.IsSinglePlayer) { return true; } if (world.Equals(CurrentWorld.m_name, StringComparison.OrdinalIgnoreCase) || world == CurrentWorld.m_uid.ToString()) { return true; } if (IsCurrentServer(world)) { return true; } } return false; } private static bool IsCurrentServer(string entry) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return false; } string serverHost = ZNet.m_serverHost; if (!string.IsNullOrEmpty(serverHost) && (entry.Equals(serverHost, StringComparison.OrdinalIgnoreCase) || entry.Equals(serverHost + ":" + ZNet.m_serverHostPort, StringComparison.OrdinalIgnoreCase))) { return true; } if (ZNet.m_serverSteamID != 0L && entry == ZNet.m_serverSteamID.ToString()) { return true; } if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId)) { return entry.Equals(ZNet.m_serverPlayFabPlayerId, StringComparison.OrdinalIgnoreCase); } return false; } private static string DescribeNames(List<string> names) { if (names.Count <= 3) { return "(" + string.Join(", ", names) + ")"; } return "(" + string.Join(", ", names.GetRange(0, 3)) + " and " + (names.Count - 3) + " more)"; } private static void Report(string problem) { if (reported.Add(problem)) { Plugin.Log.LogWarning((object)problem); } } private static string Source() { if (!UsingServerConfig) { return Path.GetFileName(Plugin.ConfigPath); } return "the server's config"; } private static bool HasWorldSections(TweakFile file) { foreach (TweakSection section in file.Sections) { foreach (string selector in section.Selectors) { string text = selector.TrimStart(); if (text.StartsWith("Creature:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Piece:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Effect:", StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } public enum TweakOperation { Set, Add, Multiply } public class TweakValue { public TweakOperation Operation; public double Number; public bool IsNumber; public string Text; public static bool TryParse(string text, out TweakValue value, out string error) { value = new TweakValue(); error = null; text = text.Trim(); if (text.StartsWith("=")) { text = text.Substring(1).Trim(); value.Operation = TweakOperation.Set; value.IsNumber = TryParseNumber(text, out value.Number); } else if (text.Length > 1 && (text.EndsWith("x") || text.EndsWith("X")) && TryParseNumber(text.Substring(0, text.Length - 1), out value.Number)) { value.Operation = TweakOperation.Multiply; value.IsNumber = true; } else if ((text.StartsWith("+") || text.StartsWith("-")) && TryParseNumber(text, out value.Number)) { value.Operation = TweakOperation.Add; value.IsNumber = true; } else { value.Operation = TweakOperation.Set; value.IsNumber = TryParseNumber(text, out value.Number); } value.Text = text; if (text == "") { error = "there's no value after the ="; return false; } return true; } public bool TryApply(object current, Type type, out object result, out string error) { result = current; error = null; if (IsNumeric(type)) { if (!IsNumber) { error = "it needs a number, not \"" + Text + "\""; return false; } double num = Convert.ToDouble(current, CultureInfo.InvariantCulture); double num2 = num; num2 = ((Operation == TweakOperation.Add) ? (num2 + Number) : ((Operation != TweakOperation.Multiply) ? Number : (num2 * Number))); if (double.IsNaN(num2)) { error = "the result isn't a number the game can use"; return false; } return TryConvertNumber(Limits.ClampMagnitude(num2, num), type, out result, out error); } if (Operation != TweakOperation.Set) { error = "only numbers can be added to or multiplied, so it needs a value like " + ExampleValue(type); return false; } if (type == typeof(bool)) { if (Text.Equals("true", StringComparison.OrdinalIgnoreCase)) { result = true; return true; } if (Text.Equals("false", StringComparison.OrdinalIgnoreCase)) { result = false; return true; } error = "it needs true or false, not \"" + Text + "\""; return false; } if (type.IsEnum) { return TryParseEnum(Text, type, out result, out error); } if (type == typeof(string)) { result = Text; return true; } if (typeof(Object).IsAssignableFrom(type)) { if (Text.Equals("none", StringComparison.OrdinalIgnoreCase)) { result = null; return true; } Object val = Prefabs.Find(Text, type); if (val == (Object)null) { error = "there's no " + Prefabs.Describe(type) + " named \"" + Text + "\""; return false; } result = val; return true; } error = "TweakStats can't change a " + type.Name; return false; } public static bool CanSet(Type type) { if (!IsNumeric(type) && !(type == typeof(bool)) && !type.IsEnum && !(type == typeof(string))) { return Prefabs.CanFind(type); } return true; } public static string Format(object value) { if (value == null) { return "none"; } Object val = (Object)((value is Object) ? value : null); if (val != null) { if (!(val == (Object)null)) { return Prefabs.NameOf(val); } return "none"; } if (value is bool) { if (!(bool)value) { return "false"; } return "true"; } if (value is float || value is double) { return Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString("0.####", CultureInfo.InvariantCulture); } if (value is IFormattable formattable) { return formattable.ToString(null, CultureInfo.InvariantCulture); } return value.ToString(); } public static bool IsNumeric(Type type) { if (!(type == typeof(float)) && !(type == typeof(double)) && !(type == typeof(int)) && !(type == typeof(long)) && !(type == typeof(short)) && !(type == typeof(byte)) && !(type == typeof(uint))) { return type == typeof(ushort); } return true; } private static bool TryParseNumber(string text, out double number) { if (double.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out number) && !double.IsNaN(number)) { return !double.IsInfinity(number); } return false; } private static bool TryConvertNumber(double number, Type type, out object result, out string error) { result = null; error = null; if (double.IsInfinity(number) || (type == typeof(float) && float.IsInfinity((float)number))) { error = "the result is too large for the game to use"; return false; } if (type == typeof(float)) { result = (float)number; return true; } if (type == typeof(double)) { result = number; return true; } double num = Math.Round(number, MidpointRounding.AwayFromZero); try { result = Convert.ChangeType(num, type, CultureInfo.InvariantCulture); return true; } catch (OverflowException) { error = num.ToString(CultureInfo.InvariantCulture) + " is too large or small for it"; return false; } } private static bool TryParseEnum(string text, Type type, out object result, out string error) { result = null; error = null; string[] names = Enum.GetNames(type); foreach (string text2 in names) { if (text2.Equals(text, StringComparison.OrdinalIgnoreCase)) { result = Enum.Parse(type, text2); return true; } } error = "\"" + text + "\" isn't one of " + string.Join(", ", Enum.GetNames(type)); return false; } private static string ExampleValue(Type type) { if (type == typeof(bool)) { return "true"; } if (type.IsEnum) { return Enum.GetNames(type)[0]; } return "a name"; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } namespace Kriona.Shared { internal class ConfigMirror { private readonly ConfigFile config; private readonly Action changed; private bool syncing; public ConfigMirror(ConfigFile config, Action changed) { this.config = config; this.changed = changed; config.SaveOnConfigSet = false; config.SettingChanged += OnSettingChanged; } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, AcceptableValueBase acceptableValues, int? order, string displayName) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes(); configurationManagerAttributes.Order = order; configurationManagerAttributes.DispName = displayName; return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, acceptableValues, new object[1] { configurationManagerAttributes })); } public void Remove(ConfigEntryBase entry) { config.Remove(entry.Definition); } public void