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 CombatAdjustments v0.8.3
CombatAdjustments.ShieldRework.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using MushroomMods; using MushroomSync; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("CombatAdjustments.ShieldRework")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+fb6e188389caf8c4c70b703d0ed970d1e986a288")] [assembly: AssemblyProduct("CombatAdjustments.ShieldRework")] [assembly: AssemblyTitle("CombatAdjustments.ShieldRework")] [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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 MushroomMods { internal static class PatchIsolation { internal static int PatchAllIsolated(Harmony harmony, Assembly assembly, ManualLogSource log) { int num = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly); foreach (Type type in typesFromAssembly) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception ex) { num++; log.LogError((object)("Harmony patch class " + type.FullName + " was skipped and the rest of " + harmony.Id + " is still active. " + ex)); } } return num; } } } namespace CombatAdjustments.ShieldRework { internal static class ConfigPaths { internal static readonly Dictionary<string, string> OverlayDefaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); internal static bool UseDedicatedLayout => Application.isBatchMode; internal static string ClientPath(string pluginGuid) { return Path.Combine(Paths.ConfigPath, pluginGuid + ".cfg"); } internal static string DedicatedPath(string pluginGuid) { return Path.Combine(Paths.GameRootPath, "config", "bepinex", pluginGuid + ".cfg"); } internal static ConfigFile CreateMergedConfig(string pluginGuid) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown OverlayDefaults.Clear(); string text = ClientPath(pluginGuid); string text2 = DedicatedPath(pluginGuid); bool flag = File.Exists(text); bool flag2 = File.Exists(text2); if (UseDedicatedLayout) { return CreateDedicatedConfig(pluginGuid, text, text2, flag, flag2); } if (flag2) { LoadOverlayDefaults(text2); } ConfigFile result = new ConfigFile(flag ? text : text2, true); if (flag && flag2) { ShieldReworkPlugin.Log.LogInfo((object)("Config: client using " + text + " with overlay from " + text2 + ".")); } else if (flag2) { ShieldReworkPlugin.Log.LogInfo((object)("Config: client loaded " + text2 + ".")); } else if (flag) { ShieldReworkPlugin.Log.LogInfo((object)("Config: client loaded " + text + ".")); } else { ShieldReworkPlugin.Log.LogInfo((object)("Config: client — no file yet; will create " + text + " (also reads " + text2 + " when present).")); } return result; } private static ConfigFile CreateDedicatedConfig(string pluginGuid, string client, string dedicated, bool clientExists, bool dedicatedExists) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown ConfigFile result = new ConfigFile(dedicatedExists ? dedicated : client, true); if (dedicatedExists) { ShieldReworkPlugin.Log.LogInfo((object)("Config: dedicated server using " + dedicated + ".")); } else if (clientExists) { ShieldReworkPlugin.Log.LogWarning((object)("Config: dedicated server — no file at " + dedicated + "; falling back to " + client + ".")); } else { ShieldReworkPlugin.Log.LogInfo((object)("Config: dedicated server — no file yet; will create " + dedicated + ".")); } return result; } internal static void ApplyOverlayAfterObjectDbLoad(ConfigFile config) { if (!UseDedicatedLayout) { string text = DedicatedPath("Abortipus.CombatAdjustments.ShieldRework"); if (File.Exists(text)) { LoadOverlayDefaults(text); ApplyOverlayToBoundEntries(config); } } } internal static bool TryGetOverlayDefault(string section, string key, out string serialized) { return OverlayDefaults.TryGetValue(BuildKey(section, key), out serialized); } internal static void ApplyOverlayToStaticEntries(ConfigFile config) { if (!UseDedicatedLayout) { string path = ClientPath("Abortipus.CombatAdjustments.ShieldRework"); if (File.Exists(DedicatedPath("Abortipus.CombatAdjustments.ShieldRework")) && File.Exists(path)) { ApplyOverlayAfterObjectDbLoad(config); } } } private static void LoadOverlayDefaults(string overlayPath) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) OverlayDefaults.Clear(); if (!File.Exists(overlayPath)) { return; } foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in new ConfigFile(overlayPath, false)) { OverlayDefaults[BuildKey(item.Key.Section, item.Key.Key)] = item.Value.GetSerializedValue() ?? string.Empty; } } private static void ApplyOverlayToBoundEntries(ConfigFile target) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown foreach (KeyValuePair<string, string> overlayDefault in OverlayDefaults) { SplitKey(overlayDefault.Key, out string section, out string key); if (section == null || key == null) { continue; } ConfigDefinition val = new ConfigDefinition(section, key); foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in target) { if (item.Key.Equals(val)) { item.Value.SetSerializedValue(overlayDefault.Value); break; } } } } private static string BuildKey(string? section, string? key) { return (section ?? string.Empty) + "\u001f" + (key ?? string.Empty); } private static void SplitKey(string path, out string? section, out string? key) { int num = path.IndexOf('\u001f'); if (num < 0) { section = null; key = null; } else { section = path.Substring(0, num); key = path.Substring(num + 1); } } } internal static class ConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__PrintStagger; public static ConsoleEvent <1>__ToggleStaggerHud; public static ConsoleEvent <2>__DumpObjectDb; } private static bool _registered; internal static void Register() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0072: 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) //IL_0068: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown if (_registered) { return; } _registered = true; try { object obj = <>O.<0>__PrintStagger; if (obj == null) { ConsoleEvent val = PrintStagger; <>O.<0>__PrintStagger = val; obj = (object)val; } new ConsoleCommand("shieldstagger", "print current stagger bar breakdown (base HP + shield grant)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>O.<0>__PrintStagger; if (obj2 == null) { ConsoleEvent val2 = PrintStagger; <>O.<0>__PrintStagger = val2; obj2 = (object)val2; } new ConsoleCommand("sstagger", "alias for shieldstagger", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>O.<1>__ToggleStaggerHud; if (obj3 == null) { ConsoleEvent val3 = ToggleStaggerHud; <>O.<1>__ToggleStaggerHud = val3; obj3 = (object)val3; } new ConsoleCommand("staggerhud", "toggle on-screen stagger current/total under the stagger bar (optional: on|off)", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj4 = <>O.<1>__ToggleStaggerHud; if (obj4 == null) { ConsoleEvent val4 = ToggleStaggerHud; <>O.<1>__ToggleStaggerHud = val4; obj4 = (object)val4; } new ConsoleCommand("shud", "alias for staggerhud", (ConsoleEvent)obj4, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj5 = <>O.<2>__DumpObjectDb; if (obj5 == null) { ConsoleEvent val5 = DumpObjectDb; <>O.<2>__DumpObjectDb = val5; obj5 = (object)val5; } new ConsoleCommand("cadump", "write the ObjectDB / global-key diagnostics dump next to the mod config", (ConsoleEvent)obj5, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); ShieldReworkPlugin.Log.LogInfo((object)"Console commands registered: shieldstagger, sstagger, staggerhud, shud, cadump"); } catch (Exception ex) { ShieldReworkPlugin.Log.LogError((object)("Failed to register Shield Rework console commands; continuing without them: " + ex.Message)); } } private static void DumpObjectDb(ConsoleEventArgs args) { if ((Object)(object)ObjectDB.instance == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("<color=red>ObjectDB not loaded yet — join a world first.</color>"); } return; } string text = Diagnostics.Write(ObjectDB.instance, "cadump console command"); Terminal context2 = args.Context; if (context2 != null) { context2.AddString((text == null) ? "<color=red>Dump failed; see the BepInEx log.</color>" : ("Dump written to " + text)); } } private static void ToggleStaggerHud(ConsoleEventArgs args) { if (args.Length >= 2) { string text = args[1].ToLowerInvariant(); bool flag; switch (text) { case "on": case "1": case "true": flag = true; break; default: flag = false; break; } if (flag) { StaggerDebugHud.SetVisible(visible: true, args); return; } switch (text) { case "off": case "0": case "false": flag = true; break; default: flag = false; break; } if (flag) { StaggerDebugHud.SetVisible(visible: false, args); return; } } StaggerDebugHud.Toggle(args); } private static void PrintStagger(ConsoleEventArgs args) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Invalid comparison between Unknown and I4 Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("<color=red>No local player.</color>"); } return; } float maxHealth = ((Character)localPlayer).GetMaxHealth(); float num = maxHealth * ((Character)localPlayer).m_staggerDamageFactor; float num2 = ShieldStats.GrantForEquippedShield((Humanoid)(object)localPlayer); float value = Traverse.Create((object)localPlayer).Method("GetStaggerTreshold", Array.Empty<object>()).GetValue<float>(); float value2 = Traverse.Create((object)localPlayer).Field("m_staggerDamage").GetValue<float>(); float num3 = ((Character)localPlayer).GetStaggerPercentage() * 100f; ItemData leftItem = ((Humanoid)localPlayer).LeftItem; string text = "none"; if (leftItem?.m_shared != null && (int)leftItem.m_shared.m_itemType == 5) { string arg = (((Object)(object)leftItem.m_dropPrefab != (Object)null) ? ShieldStats.PrefabName(leftItem.m_dropPrefab) : leftItem.m_shared.m_name); text = $"{arg} (q{leftItem.m_quality})"; } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("=== Shield Rework: stagger ==="); } Terminal context3 = args.Context; if (context3 != null) { context3.AddString($"Max HP: {maxHealth:0.#}"); } Terminal context4 = args.Context; if (context4 != null) { context4.AddString($"Base bar (40% HP): {num:0.#}"); } Terminal context5 = args.Context; if (context5 != null) { context5.AddString("Shield equipped: " + text); } Terminal context6 = args.Context; if (context6 != null) { context6.AddString($"Shield grant: +{num2:0}"); } Terminal context7 = args.Context; if (context7 != null) { context7.AddString($"<color=#E85AC8>Total threshold: {value:0.#}</color>"); } Terminal context8 = args.Context; if (context8 != null) { context8.AddString($"Current fill: {value2:0.#} ({num3:0.#}%)"); } Terminal context9 = args.Context; if (context9 != null) { context9.AddString($"Drain rate: {value / 5f:0.#}/s (full bar empties in 5s)"); } if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, $"Stagger {value2:0}/{value:0} (+{num2:0} from shield)", 0, (Sprite)null, false, true); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class Terminal_InitTerminal_Patch { private static void Postfix() { try { ConsoleCommands.Register(); } catch (Exception ex) { ShieldReworkPlugin.Log.LogError((object)("Terminal.InitTerminal postfix failed: " + ex.Message)); } } } internal static class Diagnostics { private const string FileName = "CombatAdjustments.ShieldRework.objectdb-dump.txt"; private static bool _dumped; private static readonly (string Boss, string Key)[] BossKeys = new(string, string)[8] { ("Eikthyr", "defeated_eikthyr"), ("Elder", "defeated_gdking"), ("Bonemass", "defeated_bonemass"), ("Moder", "defeated_dragon"), ("Yagluth", "defeated_goblinking"), ("Queen", "defeated_queen"), ("Fader", "defeated_fader"), ("FrozenKing", "defeated_frozenking") }; internal static string DumpPath { get { string text = null; try { text = Path.GetDirectoryName(ShieldReworkPlugin.ModConfig.ConfigFilePath); } catch (Exception) { } if (string.IsNullOrEmpty(text)) { text = Paths.ConfigPath; } return Path.Combine(text, "CombatAdjustments.ShieldRework.objectdb-dump.txt"); } } internal static void DumpOnceIfEnabled(ObjectDB db) { if (!_dumped && ShieldReworkPlugin.DumpObjectDb != null && ShieldReworkPlugin.DumpObjectDb.Value) { _dumped = true; Write(db, "ObjectDB load, [Diagnostics] DumpObjectDb = true"); } } internal static string? Write(ObjectDB? db, string reason) { string dumpPath = DumpPath; try { StringBuilder stringBuilder = new StringBuilder(); Header(stringBuilder, reason); GlobalKeys(stringBuilder); TableCoverage(stringBuilder, db); Shields(stringBuilder, db); Foods(stringBuilder, db); AllItems(stringBuilder, db); stringBuilder.AppendLine("===== END DUMP ====="); File.WriteAllText(dumpPath, stringBuilder.ToString()); ShieldReworkPlugin.Log.LogInfo((object)("Diagnostics: ObjectDB dump written to " + dumpPath)); return dumpPath; } catch (Exception ex) { ShieldReworkPlugin.Log.LogError((object)("Diagnostics: could not write " + dumpPath + ": " + ex.Message)); return null; } } private static void Header(StringBuilder sb, string reason) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("===== COMBAT ADJUSTMENTS :: OBJECTDB DUMP ====="); sb.AppendLine("Plugin: Combat Adjustments - Shield Rework 0.8.3"); sb.AppendLine($"Game: {Version.CurrentVersion}"); sb.AppendLine("Written: " + DateTime.UtcNow.ToString("u", CultureInfo.InvariantCulture)); sb.AppendLine("Trigger: " + reason); sb.AppendLine(); } private static void GlobalKeys(StringBuilder sb) { sb.AppendLine("--- GLOBAL KEYS CURRENTLY SET ---"); ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { sb.AppendLine(" ZoneSystem not available (run this from inside a world)."); sb.AppendLine(); return; } bool flag = false; foreach (string globalKey in instance.GetGlobalKeys()) { sb.AppendLine(" " + globalKey); flag = true; } if (!flag) { sb.AppendLine(" (none set on this world)"); } sb.AppendLine(); sb.AppendLine("--- BOSS KEYS THE FEAST GATE USES ---"); (string, string)[] bossKeys = BossKeys; for (int i = 0; i < bossKeys.Length; i++) { var (text, text2) = bossKeys[i]; sb.AppendLine(" " + text.PadRight(12) + "'" + text2 + "'".PadRight(24) + " set: " + instance.GetGlobalKey(text2)); } sb.AppendLine(" NOTE: a boss you have killed whose row says set: False means the"); sb.AppendLine(" spelling above is wrong. Copy the real key from the list at"); sb.AppendLine(" the top of this section into FeastUnlocks."); sb.AppendLine(); } private static void TableCoverage(StringBuilder sb, ObjectDB? db) { sb.AppendLine("--- MOD TABLE COVERAGE ---"); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (db?.m_items != null) { foreach (GameObject item in db.m_items) { if (!((Object)(object)item == (Object)null) && (Object)(object)item.GetComponent<ItemDrop>() != (Object)null) { hashSet.Add(ShieldStats.PrefabName(item)); } } } if (hashSet.Count == 0) { sb.AppendLine(" ObjectDB not available; coverage cannot be checked."); sb.AppendLine(); return; } Coverage(sb, hashSet, "FeastUnlocks spice gates", FeastUnlocks.TrackedSpicePrefabs); Coverage(sb, hashSet, "FeastUnlocks recipe gates", FeastUnlocks.TrackedRecipePrefabs, FeastUnlocks.TrackedRecipeAliases); Coverage(sb, hashSet, "FeastStats", FeastStats.TrackedPrefabs, FeastStats.AliasPrefabs); Coverage(sb, hashSet, "WeaponBlockStats", WeaponBlockStats.TrackedPrefabs); Coverage(sb, hashSet, "ShieldStats tower seeds", ShieldStats.TrackedTowerSeedPrefabs); Coverage(sb, hashSet, "ShieldStats grant steps", ShieldStats.TrackedGrantStepPrefabs); sb.AppendLine(); sb.AppendLine(" NOTE: MISSING is a defect — a wrong spelling, or a prefab this game"); sb.AppendLine(" version does not have. 'alias' rows are not: they are spare"); sb.AppendLine(" spellings deliberately carried beside a real name, and ObjectDB"); sb.AppendLine(" is not expected to contain them. A healthy dump has zero MISSING."); sb.AppendLine(); } private static void Coverage(StringBuilder sb, HashSet<string> known, string label, IEnumerable<string> prefabs, IEnumerable<string>? aliases = null) { HashSet<string> hashSet = ((aliases == null) ? null : new HashSet<string>(aliases, StringComparer.OrdinalIgnoreCase)); sb.AppendLine(" [" + label + "]"); foreach (string prefab in prefabs) { string text = (known.Contains(prefab) ? "ok" : ((hashSet != null && hashSet.Contains(prefab)) ? "alias (not in this version)" : "MISSING — wrong spelling, or not in this game version")); sb.AppendLine(" " + prefab.PadRight(32) + text); } } private static void Shields(StringBuilder sb, ObjectDB? db) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Invalid comparison between Unknown and I4 sb.AppendLine("--- SHIELDS ---"); sb.AppendLine(" name / kind / maxQ / blockPower / perLevel / maxBlock / parryBonus / our grant"); sb.AppendLine(" Block numbers are the CURRENT values. On a TOWER with"); sb.AppendLine(" Shield.EnableTowerArmorBonus on they already carry this mod's +5%"); sb.AppendLine(" (ceil per field), so divide it back out before re-deriving a seed:"); sb.AppendLine(" a tower printed as 166/7 is vanilla 158/6, i.e. 170 at max quality."); if (db?.m_items == null) { sb.AppendLine(" ObjectDB not available."); sb.AppendLine(); return; } foreach (GameObject item in db.m_items) { SharedData val = SharedOf(item); if (val != null && (int)val.m_itemType == 5) { string text = ShieldStats.PrefabName(item); ShieldKind shieldKind = ShieldStats.Classify(val); sb.AppendLine(" " + text.PadRight(30) + shieldKind.ToString().PadRight(9) + "q" + val.m_maxQuality.ToString(CultureInfo.InvariantCulture).PadRight(4) + Num(val.m_blockPower).PadRight(9) + Num(val.m_blockPowerPerLevel).PadRight(9) + Num(ShieldStats.MaxBaseBlockPower(val)).PadRight(10) + Num(val.m_timedBlockBonus).PadRight(8) + "+" + Num(ShieldStats.GetMaxGrant(text))); } } sb.AppendLine(); } private static void Foods(StringBuilder sb, ObjectDB? db) { sb.AppendLine("--- FOODS (health / stamina / eitr / burn time) ---"); sb.AppendLine(" Values shown are the CURRENT shared values, i.e. vanilla plus this mod's"); sb.AppendLine(" feast bonuses. Feast rows also print the vanilla numbers FeastStats"); sb.AppendLine(" cached before it applied anything, so 'what is vanilla X?' is answered"); sb.AppendLine(" here without having to turn Feasts.EnableStatBonuses off and re-dump."); if (db?.m_items == null) { sb.AppendLine(" ObjectDB not available."); sb.AppendLine(); return; } foreach (GameObject item in db.m_items) { SharedData val = SharedOf(item); if (val != null && (!(val.m_food <= 0f) || !(val.m_foodStamina <= 0f) || !(val.m_foodEitr <= 0f))) { string text = ShieldStats.PrefabName(item); (float, float, float) original; string text2 = (FeastStats.TryGetOriginal(text, out original) ? (" vanilla " + Num(original.Item1) + " / " + Num(original.Item2) + " / " + Num(original.Item3)) : string.Empty); sb.AppendLine(" " + text.PadRight(32) + Num(val.m_food).PadRight(8) + Num(val.m_foodStamina).PadRight(8) + Num(val.m_foodEitr).PadRight(8) + Num(val.m_foodBurnTime).PadRight(8) + text2); } } sb.AppendLine(); } private static void AllItems(StringBuilder sb, ObjectDB? db) { sb.AppendLine("--- ALL ITEMDROP PREFABS ---"); if (db?.m_items == null) { sb.AppendLine(" ObjectDB not available."); return; } int num = 0; foreach (GameObject item in db.m_items) { SharedData val = SharedOf(item); if (val != null) { sb.AppendLine(" " + ShieldStats.PrefabName(item).PadRight(36) + ((object)Unsafe.As<ItemType, ItemType>(ref val.m_itemType)/*cast due to .constrained prefix*/).ToString()); num++; } } sb.AppendLine($" ({num} item prefabs)"); } private static SharedData? SharedOf(GameObject? go) { if ((Object)(object)go == (Object)null) { return null; } return go.GetComponent<ItemDrop>()?.m_itemData?.m_shared; } private static string Num(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } internal static class DifficultyScaling { internal static int UncappedPlayerDifficulty(Game game, Vector3 pos) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) int value = Traverse.Create((object)game).Field("m_forcePlayers").GetValue<int>(); if (value > 0) { return value; } int playersInRangeXZ = Player.GetPlayersInRangeXZ(pos, game.m_difficultyScaleRange); if (playersInRangeXZ >= 1) { return playersInRangeXZ; } return 1; } internal static float EnemyDamageTakenScale(Game game, int players) { float num = 1f + (float)(players - 1) * game.m_healthScalePerPlayer; return 1f / num; } } [HarmonyPatch(typeof(Game), "GetDifficultyDamageScaleEnemy")] internal static class Game_GetDifficultyDamageScaleEnemy_Patch { private static void Postfix(Game __instance, Vector3 pos, ref float __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ShieldReworkPlugin.EnableUncapHealthScaling.Value) { int players = DifficultyScaling.UncappedPlayerDifficulty(__instance, pos); __result = DifficultyScaling.EnemyDamageTakenScale(__instance, players); } } } internal static class FeastStats { private static readonly Dictionary<string, (float health, float stamina, float eitr)> Originals = new Dictionary<string, (float, float, float)>(StringComparer.OrdinalIgnoreCase); private static readonly string[] CanonicalPrefabs = new string[9] { "FeastMeadows", "FeastBlackforest", "FeastSwamps", "FeastOceans", "FeastMountains", "FeastPlains", "FeastMistlands", "FeastAshlands", "FeastDeepNorth" }; internal static readonly HashSet<string> AliasPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "FeastBlackForest", "FeastSwamp", "FeastOcean", "FeastMountain" }; private static readonly Dictionary<string, FeastKind> PrefabKinds = new Dictionary<string, FeastKind>(StringComparer.OrdinalIgnoreCase) { ["FeastMeadows"] = FeastKind.Meadows, ["FeastBlackforest"] = FeastKind.BlackForest, ["FeastBlackForest"] = FeastKind.BlackForest, ["FeastSwamps"] = FeastKind.Swamp, ["FeastSwamp"] = FeastKind.Swamp, ["FeastOceans"] = FeastKind.Sailors, ["FeastOcean"] = FeastKind.Sailors, ["FeastMountains"] = FeastKind.Mountains, ["FeastMountain"] = FeastKind.Mountains, ["FeastPlains"] = FeastKind.Plains, ["FeastMistlands"] = FeastKind.Mistlands, ["FeastAshlands"] = FeastKind.Ashlands, ["FeastDeepNorth"] = FeastKind.DeepNorth }; internal static IEnumerable<string> TrackedPrefabs => PrefabKinds.Keys; internal static void ApplyToObjectDB(ObjectDB db) { if (db?.m_items == null || db.m_items.Count == 0) { return; } int num = 0; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if (component?.m_itemData?.m_shared != null) { string prefab = ShieldStats.PrefabName(item); if (TryIdentify(prefab, out var kind)) { hashSet.Add(CanonicalPrefab(kind)); ApplyToShared(prefab, kind, component.m_itemData.m_shared); num++; } } } string[] canonicalPrefabs = CanonicalPrefabs; foreach (string text in canonicalPrefabs) { if (!hashSet.Contains(text)) { ShieldReworkPlugin.Log.LogWarning((object)("Feast stats: prefab '" + text + "' not found in ObjectDB.")); } } ShieldReworkPlugin.Log.LogInfo((object)$"Applied feast bonuses to {num} feast items."); } internal static void ApplyToShared(string prefab, FeastKind kind, SharedData shared) { if (!Originals.ContainsKey(prefab)) { Originals[prefab] = (shared.m_food, shared.m_foodStamina, shared.m_foodEitr); } (float, float, float) tuple = Originals[prefab]; if (!ShieldReworkPlugin.EnableFeastStatBonuses.Value) { (shared.m_food, shared.m_foodStamina, shared.m_foodEitr) = tuple; } else { shared.m_food = tuple.Item1 + HealthBonus(kind); shared.m_foodStamina = tuple.Item2 + StaminaBonus(kind); shared.m_foodEitr = tuple.Item3 + EitrBonus(kind); } } internal static bool TryGetOriginal(string prefab, out (float health, float stamina, float eitr) original) { return Originals.TryGetValue(prefab, out original); } internal static bool TryIdentify(string prefab, out FeastKind kind) { if (PrefabKinds.TryGetValue(prefab, out kind)) { return true; } kind = FeastKind.Meadows; return false; } private static float HealthBonus(FeastKind kind) { if (kind != FeastKind.Sailors) { return ShieldReworkPlugin.FeastHealthBonus.Value; } return ShieldReworkPlugin.SailorsFeastHealthBonus.Value; } private static float StaminaBonus(FeastKind kind) { if (kind != FeastKind.Sailors) { return ShieldReworkPlugin.FeastStaminaBonus.Value; } return ShieldReworkPlugin.SailorsFeastStaminaBonus.Value; } private static float EitrBonus(FeastKind kind) { return kind switch { FeastKind.Mistlands => ShieldReworkPlugin.MistlandsFeastEitrBonus.Value, FeastKind.Ashlands => ShieldReworkPlugin.AshlandsFeastEitrBonus.Value, FeastKind.DeepNorth => ShieldReworkPlugin.DeepNorthFeastEitrBonus.Value, _ => 0f, }; } private static string CanonicalPrefab(FeastKind kind) { return kind switch { FeastKind.Meadows => "FeastMeadows", FeastKind.BlackForest => "FeastBlackforest", FeastKind.Swamp => "FeastSwamps", FeastKind.Sailors => "FeastOceans", FeastKind.Mountains => "FeastMountains", FeastKind.Plains => "FeastPlains", FeastKind.Mistlands => "FeastMistlands", FeastKind.Ashlands => "FeastAshlands", FeastKind.DeepNorth => "FeastDeepNorth", _ => string.Empty, }; } } internal enum FeastKind { Meadows, BlackForest, Swamp, Sailors, Mountains, Plains, Mistlands, Ashlands, DeepNorth } internal static class FeastUnlocks { internal const string Eikthyr = "defeated_eikthyr"; internal const string Elder = "defeated_gdking"; internal const string Bonemass = "defeated_bonemass"; internal const string Moder = "defeated_dragon"; internal const string Yagluth = "defeated_goblinking"; internal const string Queen = "defeated_queen"; internal const string Fader = "defeated_fader"; internal const string FrozenKing = "defeated_frozenking"; private static readonly Dictionary<string, string> SpiceKeys = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["SpiceForests"] = "", ["SpiceMountains"] = "defeated_bonemass", ["SpicePlains"] = "defeated_dragon", ["SpiceMistlands"] = "defeated_goblinking", ["SpiceAshlands"] = "defeated_queen", ["SpiceDeepNorth"] = "defeated_fader" }; private static readonly Dictionary<string, string> RecipeKeys = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["FeastBlackforest"] = "defeated_eikthyr", ["FeastBlackForest"] = "defeated_eikthyr", ["FeastSwamps"] = "defeated_gdking", ["FeastSwamp"] = "defeated_gdking", ["FeastMountains"] = "defeated_bonemass", ["FeastMountain"] = "defeated_bonemass", ["FeastPlains"] = "defeated_dragon", ["FeastMistlands"] = "defeated_goblinking", ["FeastAshlands"] = "defeated_queen", ["FeastDeepNorth"] = "defeated_fader" }; private static readonly HashSet<int> RemappedTraders = new HashSet<int>(); internal static IEnumerable<string> TrackedSpicePrefabs => SpiceKeys.Keys; internal static IEnumerable<string> TrackedRecipePrefabs => RecipeKeys.Keys; internal static IEnumerable<string> TrackedRecipeAliases => FeastStats.AliasPrefabs; internal static void ApplySpiceKeys(Trader trader) { if (trader?.m_items == null) { return; } int num = 0; foreach (TradeItem item in trader.m_items) { if (!((Object)(object)item?.m_prefab == (Object)null)) { string key = ShieldStats.PrefabName(((Component)item.m_prefab).gameObject); if (SpiceKeys.TryGetValue(key, out string value) && !string.Equals(item.m_requiredGlobalKey, value, StringComparison.Ordinal)) { item.m_requiredGlobalKey = value; num++; } } } if (num != 0) { int instanceID = ((Object)trader).GetInstanceID(); if (RemappedTraders.Add(instanceID)) { ShieldReworkPlugin.Log.LogInfo((object)$"Feast unlocks: remapped {num} Bog Witch spice gate(s) on '{ShieldStats.PrefabName(((Component)trader).gameObject)}'."); } } } internal static bool IsRecipeLocked(Recipe? recipe) { ItemDrop val = recipe?.m_item; if ((Object)(object)val == (Object)null) { return false; } string key = ShieldStats.PrefabName(((Component)val).gameObject); if (!RecipeKeys.TryGetValue(key, out string value) || string.IsNullOrEmpty(value)) { return false; } ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return true; } return !instance.GetGlobalKey(value); } } [HarmonyPatch(typeof(Trader), "GetAvailableItems")] internal static class Trader_GetAvailableItems_FeastUnlocks_Patch { private static void Prefix(Trader __instance) { FeastUnlocks.ApplySpiceKeys(__instance); } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] internal static class Player_HaveRequirements_FeastUnlocks_Patch { private static void Postfix(Recipe recipe, ref bool __result) { if (__result && FeastUnlocks.IsRecipeLocked(recipe)) { __result = false; } } } internal static class ForgeOfPotential { internal const float IntendedBreakChance = 0.1f; private static readonly Dictionary<string, float> OriginalBreakChance = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static void ApplyToObjectDB(ObjectDB db) { if (db?.m_items == null || db.m_items.Count == 0) { return; } int num = 0; foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null || !IsIdolPrefab(((Object)item).name)) { continue; } SharedData val = item.GetComponent<ItemDrop>()?.m_itemData?.m_shared; if (val != null) { string name = ((Object)item).name; if (!OriginalBreakChance.ContainsKey(name)) { OriginalBreakChance[name] = val.m_breakChance; } val.m_breakChance = (ShieldReworkPlugin.EnableForgeOfPotentialOdds.Value ? 0.1f : OriginalBreakChance[name]); num++; } } if (num > 0) { ShieldReworkPlugin.Log.LogInfo((object)(ShieldReworkPlugin.EnableForgeOfPotentialOdds.Value ? $"Forge of Potential: set break chance to {0.1f:0.##} on {num} idols (65% / 25% downgrade / 10% break)." : $"Forge of Potential: restored vanilla break chance on {num} idols.")); } } internal static bool IsIdolPrefab(string? prefabName) { if (prefabName == null || prefabName.Length <= 0) { return false; } if (prefabName.StartsWith("Upgrader", StringComparison.Ordinal)) { if (!prefabName.EndsWith("Weapon", StringComparison.Ordinal)) { return prefabName.EndsWith("Armor", StringComparison.Ordinal); } return true; } return false; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class InventoryGui_DoCrafting_ForgeMinLevel_Patch { internal sealed class SharedBreakOverride { private readonly SharedData _shared; private readonly float _original; internal SharedBreakOverride(SharedData shared, float original) { _shared = shared; _original = original; } internal void Restore() { _shared.m_breakChance = _original; } } private static void Prefix(InventoryGui __instance, out SharedBreakOverride? __state) { __state = null; if (!ShieldReworkPlugin.EnableForgeOfPotentialOdds.Value) { return; } Player localPlayer = Player.m_localPlayer; CraftingStation val = ((localPlayer != null) ? localPlayer.GetCurrentCraftingStation() : null); if ((Object)(object)val == (Object)null || !val.m_upgrader) { return; } ItemData value = Traverse.Create((object)__instance).Field("m_craftUpgradeItem").GetValue<ItemData>(); if (value != null && value.m_quality <= 1) { SharedData val2 = FindIdolShared(Traverse.Create((object)__instance).Field("m_craftRecipe").GetValue<Recipe>()); if (val2 != null) { __state = new SharedBreakOverride(val2, val2.m_breakChance); val2.m_breakChance = 1f; } } } private static void Postfix(SharedBreakOverride? __state) { __state?.Restore(); } private static SharedData? FindIdolShared(Recipe? recipe) { if (recipe?.m_resources == null) { return null; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && val.m_upgraderResource) { return val.m_resItem?.m_itemData?.m_shared; } } return null; } } [HarmonyPatch(typeof(Localization), "Localize", new Type[] { typeof(string), typeof(string[]) })] internal static class Localization_Localize_UpgraderFailed_Patch { private static bool Prefix(Localization __instance, string text, string[] words, ref string __result) { if (!ShieldReworkPlugin.EnableForgeOfPotentialOdds.Value) { return true; } if (text != "$msg_upgrader_failed" || words == null || words.Length < 2) { return true; } string text2 = __instance.Localize(words[0]); __result = text2 + " downgraded to level " + words[1]; return false; } } internal static class OceanWeather { private const string ThunderStormName = "ThunderStorm"; private static readonly Dictionary<EnvEntry, float> OriginalWeights = new Dictionary<EnvEntry, float>(); internal static void Apply() { if ((Object)(object)EnvMan.instance == (Object)null) { return; } foreach (BiomeEnvSetup biome in EnvMan.instance.m_biomes) { ApplyToBiome(biome); } } internal static void ApplyToBiome(BiomeEnvSetup biome) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)biome.m_biome != 256) { return; } EnvEntry val = null; float num = 0f; foreach (EnvEntry environment in biome.m_environments) { if (environment.m_ashlandsOverride || environment.m_deepnorthOverride) { continue; } if (environment.m_environment == "ThunderStorm") { val = environment; if (!OriginalWeights.ContainsKey(environment)) { OriginalWeights[environment] = environment.m_weight; } } else { num += environment.m_weight; } } if (val == null) { return; } if (!ShieldReworkPlugin.EnableOceanStormChance.Value) { val.m_weight = OriginalWeights[val]; return; } float num2 = Mathf.Clamp01(ShieldReworkPlugin.OceanThunderStormChance.Value); if (num2 <= 0f) { val.m_weight = 0f; } else if (num2 >= 1f) { val.m_weight = Mathf.Max(num * 100f, 1f); } else { val.m_weight = num2 * num / (1f - num2); } } } [HarmonyPatch(typeof(EnvMan), "InitializeBiomeEnvSetup")] internal static class EnvMan_InitializeBiomeEnvSetup_Patch { private static void Postfix(BiomeEnvSetup biome) { OceanWeather.ApplyToBiome(biome); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class ObjectDB_Awake_Patch { private static void Postfix(ObjectDB __instance) { ShieldStats.ApplyToObjectDB(__instance); WeaponBlockStats.ApplyToObjectDB(__instance); FeastStats.ApplyToObjectDB(__instance); ForgeOfPotential.ApplyToObjectDB(__instance); Diagnostics.DumpOnceIfEnabled(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] internal static class ObjectDB_CopyOtherDB_Patch { private static void Postfix(ObjectDB __instance) { ShieldStats.ApplyToObjectDB(__instance); WeaponBlockStats.ApplyToObjectDB(__instance); FeastStats.ApplyToObjectDB(__instance); ForgeOfPotential.ApplyToObjectDB(__instance); Diagnostics.DumpOnceIfEnabled(__instance); } } [HarmonyPatch(typeof(Character), "GetStaggerTreshold")] internal static class Character_GetStaggerTreshold_Patch { private static void Postfix(Character __instance, ref float __result) { if (ShieldReworkPlugin.EnableStaggerGrant.Value) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { __result += ShieldStats.GrantForEquippedShield((Humanoid)(object)val); } } } } [HarmonyPatch(typeof(Character), "UpdateStagger")] internal static class Character_UpdateStagger_Patch { private static bool Prefix(Character __instance, float dt, ref float ___m_staggerDamage) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return true; } if (((Character)val).m_staggerDamageFactor <= 0f) { return false; } float value = Traverse.Create((object)val).Method("GetStaggerTreshold", Array.Empty<object>()).GetValue<float>(); ___m_staggerDamage -= value / 5f * dt; if (___m_staggerDamage < 0f) { ___m_staggerDamage = 0f; } return false; } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class Humanoid_EquipItem_Patch { private static void Prefix(Humanoid __instance, ItemData item, out float __state) { __state = StaggerPercent.CaptureIfShield(__instance, item); } private static void Postfix(Humanoid __instance, ItemData item, bool __result, float __state) { if (!(__state < 0f) && __result) { StaggerPercent.Apply((Character)(object)__instance, __state); } } } [HarmonyPatch(typeof(Humanoid), "UnequipItem")] internal static class Humanoid_UnequipItem_Patch { private static void Prefix(Humanoid __instance, ItemData item, out float __state) { __state = StaggerPercent.CaptureIfShield(__instance, item); } private static void Postfix(Humanoid __instance, ItemData item, float __state) { if (!(__state < 0f)) { StaggerPercent.Apply((Character)(object)__instance, __state); } } } internal static class StaggerPercent { private const float Skip = -1f; internal static float CaptureIfShield(Humanoid humanoid, ItemData? item) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if (!(humanoid is Player)) { return -1f; } if (item?.m_shared == null || (int)item.m_shared.m_itemType != 5) { return -1f; } return Capture((Character)(object)humanoid); } internal static float Capture(Character character) { float value = Traverse.Create((object)character).Method("GetStaggerTreshold", Array.Empty<object>()).GetValue<float>(); if (value <= 0f) { return 0f; } return Mathf.Clamp01(Traverse.Create((object)character).Field("m_staggerDamage").GetValue<float>() / value); } internal static void Apply(Character character, float percent) { float value = Traverse.Create((object)character).Method("GetStaggerTreshold", Array.Empty<object>()).GetValue<float>(); float num = Mathf.Clamp(percent, 0f, 1f) * Mathf.Max(0f, value); Traverse.Create((object)character).Field("m_staggerDamage").SetValue((object)num); } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] internal static class ItemData_GetTooltip_Patch { private static void Postfix(ItemData item, int qualityLevel, ref string __result) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (item?.m_shared == null || (int)item.m_shared.m_itemType != 5) { return; } int quality = item.m_quality; item.m_quality = qualityLevel; float num = ShieldStats.GrantForItem(item); item.m_quality = quality; if (num <= 0f) { return; } string text = $"\nStagger: <color=orange>+{num:0}</color>"; int num2 = __result.IndexOf("$item_blockarmor:", StringComparison.Ordinal); if (num2 >= 0) { int num3 = __result.IndexOf('\n', num2); if (num3 < 0) { __result += text; } else { __result = __result.Insert(num3, text); } } else { __result += text; } } } internal static class SailingWind { internal static float ForceFactor(float windIntensity) { float value = ShieldReworkPlugin.SailingCalmForceFactor.Value; float value2 = ShieldReworkPlugin.SailingKneeForceFactor.Value; float value3 = ShieldReworkPlugin.SailingMaxForceFactor.Value; float num = Mathf.Clamp01(ShieldReworkPlugin.SailingCalmWindCeiling.Value); windIntensity = Mathf.Clamp01(windIntensity); if (num <= 0f) { return Mathf.Lerp(value2, value3, windIntensity); } if (windIntensity <= num) { return Mathf.Lerp(value, value2, windIntensity / num); } if (num >= 1f) { return value2; } return Mathf.Lerp(value2, value3, (windIntensity - num) / (1f - num)); } } [HarmonyPatch(typeof(Ship), "GetSailForce")] internal static class Ship_GetSailForce_Patch { private static bool Prefix(Ship __instance, float sailSize, ref Vector3 __result, ref Vector3 ___m_sailForce, ref Vector3 ___m_windChangeVelocity) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //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_0041: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (!ShieldReworkPlugin.EnableSailingWindCurve.Value) { return true; } Vector3 windDir = EnvMan.instance.GetWindDir(); float num = SailingWind.ForceFactor(EnvMan.instance.GetWindIntensity()); float num2 = __instance.GetWindAngleFactor() * num; Vector3 val = Vector3.Normalize(windDir + ((Component)__instance).transform.forward) * (num2 * __instance.m_sailForceFactor * sailSize); ___m_sailForce = Vector3.SmoothDamp(___m_sailForce, val, ref ___m_windChangeVelocity, 1f, 99f); __result = ___m_sailForce; return false; } } [BepInPlugin("Abortipus.CombatAdjustments.ShieldRework", "Combat Adjustments - Shield Rework", "0.8.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class ShieldReworkPlugin : BaseUnityPlugin { public const string PluginGuid = "Abortipus.CombatAdjustments.ShieldRework"; public const string PluginName = "Combat Adjustments - Shield Rework"; public const string PluginVersion = "0.8.3"; public const float FlametalTowerGrant = 70f; public const float FlametalRoundGrant = 45f; public const float CarapaceBucklerGrant = 20f; public const float TowerArmorMult = 1.05f; public const float DurabilityMult = 1.2f; public const int CurrentGrantTableVersion = 3; internal static ManualLogSource Log; internal static ShieldReworkPlugin Instance; internal static ConfigFile ModConfig; internal static ConfigEntry<bool> EnableStaggerGrant; internal static ConfigEntry<bool> SyncConfigInMultiplayer; internal static ConfigEntry<bool> EnableTowerArmorBonus; internal static ConfigEntry<bool> EnableDurabilityBonus; internal static ConfigEntry<bool> EnableTwoHandedCombat; internal static ConfigEntry<float> GreatswordPrimaryStaggerMultiplier; internal static ConfigEntry<bool> AreaAdrenalinePerEnemy; internal static ConfigEntry<bool> EnableWeaponBlockPerLevel; internal static ConfigEntry<int> GrantTableVersion; internal static ConfigEntry<string> TooltipColorHex; internal static ConfigEntry<bool> EnableFeastStatBonuses; internal static ConfigEntry<float> FeastHealthBonus; internal static ConfigEntry<float> FeastStaminaBonus; internal static ConfigEntry<float> SailorsFeastHealthBonus; internal static ConfigEntry<float> SailorsFeastStaminaBonus; internal static ConfigEntry<float> MistlandsFeastEitrBonus; internal static ConfigEntry<float> AshlandsFeastEitrBonus; internal static ConfigEntry<float> DeepNorthFeastEitrBonus; internal static ConfigEntry<bool> DumpObjectDb; internal static ConfigEntry<bool> EnableSailingWindCurve; internal static ConfigEntry<float> SailingCalmForceFactor; internal static ConfigEntry<float> SailingKneeForceFactor; internal static ConfigEntry<float> SailingMaxForceFactor; internal static ConfigEntry<float> SailingCalmWindCeiling; internal static ConfigEntry<bool> EnableOceanStormChance; internal static ConfigEntry<float> OceanThunderStormChance; internal static ConfigEntry<bool> EnableUncapHealthScaling; internal static ConfigEntry<bool> EnableForgeOfPotentialOdds; internal static ConfigSync Sync; private Harmony? _harmony; private void Awake() { //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_0618: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig = ConfigPaths.CreateMergedConfig("Abortipus.CombatAdjustments.ShieldRework"); Sync = ConfigSync.Create("Abortipus.CombatAdjustments.ShieldRework", "0.8.3", ((BaseUnityPlugin)this).Logger).Protecting(ModConfig).GatedBy((Func<bool>)(() => SyncConfigInMultiplayer == null || SyncConfigInMultiplayer.Value)) .AcceptedWhen((Func<bool>)(() => SyncConfigInMultiplayer == null || SyncConfigInMultiplayer.Value)) .OnApplied((Action)ApplyRuntimeFromConfig) .Notifying((Action<string>)NotifyPlayer); EnableStaggerGrant = ModConfig.Bind<bool>("General", "EnableStaggerGrant", true, "Add flat stagger-bar capacity while a shield is equipped."); SyncConfigInMultiplayer = ModConfig.Bind<bool>("General", "SyncConfigInMultiplayer", true, "When hosting or on a dedicated server, send this installation's config to joining clients. Clients use host values at runtime without overwriting their local .cfg."); EnableTowerArmorBonus = ModConfig.Bind<bool>("General", "EnableTowerArmorBonus", true, "Apply +5% block armor (rounded up) to tower shields."); EnableDurabilityBonus = ModConfig.Bind<bool>("General", "EnableDurabilityBonus", true, "Apply +20% durability (ceil to nearest 5) to tower and round shields."); EnableTwoHandedCombat = ModConfig.Bind<bool>("Two-Handed Combat", "Enable", true, "Enable stagger-only hyper armor and damage/stagger adjustments for two-handed melee weapons."); GreatswordPrimaryStaggerMultiplier = ModConfig.Bind<float>("Two-Handed Combat", "GreatswordPrimaryStaggerMultiplier", 1.5f, "Final stagger multiplier for primary greatsword swings. 1.5 = +50%."); AreaAdrenalinePerEnemy = ModConfig.Bind<bool>("Two-Handed Combat", "AreaAdrenalinePerEnemy", true, "Two-handed club ground slams (Stagbreaker, Iron Sledge, Demolisher) grant adrenaline per enemy hit, like swing attacks, instead of once per slam."); EnableWeaponBlockPerLevel = ModConfig.Bind<bool>("Two-Handed Combat", "EnableWeaponBlockPerLevel", true, "Scale block armor per weapon quality for two-handed and dual-wield blocking weapons (replaces vanilla m_blockPowerPerLevel)."); GrantTableVersion = ModConfig.Bind<int>("General", "GrantTableVersion", 0, "Internal. Bump with plugin to re-seed StaggerGrants.* to designed max-quality values."); TooltipColorHex = ModConfig.Bind<string>("Tooltip", "StaggerColorHex", "#E85AC8", "Hex color for the stagger grant tooltip line (matches HUD stagger pink)."); EnableFeastStatBonuses = ModConfig.Bind<bool>("Feasts", "EnableStatBonuses", true, "Add extra health / stamina / eitr to feast foods. Boss unlocks are not configurable and always apply."); FeastHealthBonus = ModConfig.Bind<float>("Feasts", "HealthBonus", 10f, "Extra max health added to every feast except Sailor's Bounty."); FeastStaminaBonus = ModConfig.Bind<float>("Feasts", "StaminaBonus", 10f, "Extra max stamina added to every feast except Sailor's Bounty."); SailorsFeastHealthBonus = ModConfig.Bind<float>("Feasts", "SailorsHealthBonus", 15f, "Extra max health added to Sailor's Bounty (instead of HealthBonus)."); SailorsFeastStaminaBonus = ModConfig.Bind<float>("Feasts", "SailorsStaminaBonus", 15f, "Extra max stamina added to Sailor's Bounty (instead of StaminaBonus)."); MistlandsFeastEitrBonus = ModConfig.Bind<float>("Feasts", "MistlandsEitrBonus", 7f, "Extra eitr added to Mushrooms Galore à la Mistlands (vanilla 33 → 40). Also receives HealthBonus / StaminaBonus."); AshlandsFeastEitrBonus = ModConfig.Bind<float>("Feasts", "AshlandsEitrBonus", 12f, "Extra eitr added to Ashlands Gourmet Bowl (vanilla 38 → 50). Also receives HealthBonus / StaminaBonus."); DeepNorthFeastEitrBonus = ModConfig.Bind<float>("Feasts", "DeepNorthEitrBonus", 17f, "Extra eitr added to the Deep North feast (vanilla 43 → 60). Also receives HealthBonus / StaminaBonus."); DumpObjectDb = ModConfig.Bind<bool>("Diagnostics", "DumpObjectDb", false, "Write every ObjectDB item prefab, its item type, shield and food stats, and the world's global keys to a text file next to this config, once, on the next world load. Also available from the console as 'cadump'. Local only — never synced from a host."); EnableSailingWindCurve = ModConfig.Bind<bool>("Sailing", "EnableWindCurve", true, "Replace vanilla linear wind→sail force with a two-segment curve (calm matches vanilla to 60%, storms ramp higher)."); SailingCalmForceFactor = ModConfig.Bind<float>("Sailing", "CalmForceFactor", 0.287f, "Sail intensity factor at 0% wind. Vanilla uses ~0.287 at the 5% clamp floor."); SailingKneeForceFactor = ModConfig.Bind<float>("Sailing", "KneeForceFactor", 0.7f, "Sail intensity factor at CalmWindCeiling (default 60%). Matches vanilla Lerp(0.25,1,0.6)."); SailingMaxForceFactor = ModConfig.Bind<float>("Sailing", "MaxForceFactor", 2f, "Sail intensity factor at 100% wind (vanilla tops out at 1)."); SailingCalmWindCeiling = ModConfig.Bind<float>("Sailing", "CalmWindCeiling", 0.6f, "Wind intensity (0–1) where the calm segment ends and the storm ramp begins. Clear weather tops out here."); EnableOceanStormChance = ModConfig.Bind<bool>("Sailing", "EnableOceanStormChance", true, "Raise Ocean biome ThunderStorm weight so storms occur more often while sailing."); OceanThunderStormChance = ModConfig.Bind<float>("Sailing", "OceanThunderStormChance", 0.21f, "Target chance (0–1) of ThunderStorm on the Ocean biome. Vanilla is ~0.071 (7%). Default 0.21 = 21%."); EnableUncapHealthScaling = ModConfig.Bind<bool>("Difficulty", "EnableUncapHealthScaling", true, "Let effective enemy HP keep scaling with nearby players past vanilla's 5-player cap (+30% per extra player). Enemy damage dealt stays capped at 5."); EnableForgeOfPotentialOdds = ModConfig.Bind<bool>("Forge of Potential", "EnableIntendedOdds", true, "Restore code-default Forge of Potential odds: 65% success, 25% downgrade, 10% break. Vanilla idol prefabs set break chance to 1 so every failure destroys the item. Turn off when IronGate ships a real fix."); Sync.Register((ConfigEntryBase[])(object)new ConfigEntryBase[25] { (ConfigEntryBase)EnableStaggerGrant, (ConfigEntryBase)EnableTowerArmorBonus, (ConfigEntryBase)EnableDurabilityBonus, (ConfigEntryBase)EnableTwoHandedCombat, (ConfigEntryBase)GreatswordPrimaryStaggerMultiplier, (ConfigEntryBase)AreaAdrenalinePerEnemy, (ConfigEntryBase)EnableWeaponBlockPerLevel, (ConfigEntryBase)TooltipColorHex, (ConfigEntryBase)EnableFeastStatBonuses, (ConfigEntryBase)FeastHealthBonus, (ConfigEntryBase)FeastStaminaBonus, (ConfigEntryBase)SailorsFeastHealthBonus, (ConfigEntryBase)SailorsFeastStaminaBonus, (ConfigEntryBase)MistlandsFeastEitrBonus, (ConfigEntryBase)AshlandsFeastEitrBonus, (ConfigEntryBase)DeepNorthFeastEitrBonus, (ConfigEntryBase)EnableSailingWindCurve, (ConfigEntryBase)SailingCalmForceFactor, (ConfigEntryBase)SailingKneeForceFactor, (ConfigEntryBase)SailingMaxForceFactor, (ConfigEntryBase)SailingCalmWindCeiling, (ConfigEntryBase)EnableOceanStormChance, (ConfigEntryBase)OceanThunderStormChance, (ConfigEntryBase)EnableUncapHealthScaling, (ConfigEntryBase)EnableForgeOfPotentialOdds }); Sync.Exclude((ConfigEntryBase)(object)GrantTableVersion).Exclude((ConfigEntryBase)(object)SyncConfigInMultiplayer).Exclude((ConfigEntryBase)(object)DumpObjectDb); HookFeastConfigChange<bool>(EnableFeastStatBonuses); HookFeastConfigChange<float>(FeastHealthBonus); HookFeastConfigChange<float>(FeastStaminaBonus); HookFeastConfigChange<float>(SailorsFeastHealthBonus); HookFeastConfigChange<float>(SailorsFeastStaminaBonus); HookFeastConfigChange<float>(MistlandsFeastEitrBonus); HookFeastConfigChange<float>(AshlandsFeastEitrBonus); HookFeastConfigChange<float>(DeepNorthFeastEitrBonus); HookOceanWeatherConfigChange<bool>(EnableOceanStormChance); HookOceanWeatherConfigChange<float>(OceanThunderStormChance); HookForgeOddsConfigChange<bool>(EnableForgeOfPotentialOdds); ApplyOverlayToStaticEntries(); Sync.WatchForChanges(ModConfig).Start(); OceanWeather.Apply(); ConsoleCommands.Register(); _harmony = new Harmony("Abortipus.CombatAdjustments.ShieldRework"); int num = PatchIsolation.PatchAllIsolated(_harmony, Assembly.GetExecutingAssembly(), Log); Log.LogInfo((object)("Combat Adjustments - Shield Rework 0.8.3 loaded." + ((num > 0) ? $" {num} patch class(es) skipped - see the errors above." : string.Empty))); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static Color GetTooltipColor() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (ColorUtility.TryParseHtmlString(TooltipColorHex.Value, ref result)) { return result; } return new Color(0.91f, 0.35f, 0.78f); } internal static string ColorToHex(Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return "#" + ColorUtility.ToHtmlStringRGB(color); } private static void ApplyRuntimeFromConfig() { OceanWeather.Apply(); if (!((Object)(object)ObjectDB.instance == (Object)null)) { ShieldStats.ApplyToObjectDB(ObjectDB.instance); WeaponBlockStats.ApplyToObjectDB(ObjectDB.instance); FeastStats.ApplyToObjectDB(ObjectDB.instance); ForgeOfPotential.ApplyToObjectDB(ObjectDB.instance); } } private static void NotifyPlayer(string message) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null, false); } } private static void HookFeastConfigChange<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { if ((Object)(object)ObjectDB.instance != (Object)null) { FeastStats.ApplyToObjectDB(ObjectDB.instance); } }; } private static void HookForgeOddsConfigChange<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { if ((Object)(object)ObjectDB.instance != (Object)null) { ForgeOfPotential.ApplyToObjectDB(ObjectDB.instance); } }; } private static void HookOceanWeatherConfigChange<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { OceanWeather.Apply(); }; } private static void ApplyOverlayToStaticEntries() { ConfigPaths.ApplyOverlayToStaticEntries(ModConfig); } } internal static class ShieldStats { internal static readonly Dictionary<string, ConfigEntry<float>> GrantConfigs = new Dictionary<string, ConfigEntry<float>>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, float> RuntimeGrants = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, float> SharedNameToGrant = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, (float power, float perLevel)> OriginalArmor = new Dictionary<string, (float, float)>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, (float max, float perLevel)> OriginalDurability = new Dictionary<string, (float, float)>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, float> TowerLeftoverSeeds = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase) { ["ShieldWoodTower"] = 5f, ["ShieldBoneTower"] = 15f, ["ShieldIronTower"] = 25f, ["ShieldSerpentscale"] = 50f, ["ShieldBlackmetalTower"] = 55f, ["ShieldFlametalTower"] = 70f, ["ShieldGoldTower"] = 80f }; private static readonly HashSet<string> GrantStep3 = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ShieldFlametal", "ShieldFlametalTower", "ShieldGold", "ShieldGoldTower", "ShieldGoldBuckler" }; private static readonly HashSet<string> GrantStep2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ShieldSerpentscale", "ShieldBlackmetalTower", "ShieldSilver", "ShieldBlackmetal", "ShieldCarapace", "ShieldKnight" }; internal static IEnumerable<string> TrackedTowerSeedPrefabs => TowerLeftoverSeeds.Keys; internal static IEnumerable<string> TrackedGrantStepPrefabs { get { foreach (string item in GrantStep3) { yield return item; } foreach (string item2 in GrantStep2) { yield return item2; } } } internal static ShieldKind Classify(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 5) { return ShieldKind.Unknown; } float timedBlockBonus = shared.m_timedBlockBonus; if (timedBlockBonus >= 2.4f) { return ShieldKind.Buckler; } if (timedBlockBonus > 1.01f) { return ShieldKind.Round; } return ShieldKind.Tower; } internal static string PrefabName(GameObject go) { string name = ((Object)go).name; int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num < 0) { return name; } return name.Substring(0, num).TrimEnd(Array.Empty<char>()); } internal static float MaxBaseBlockPower(SharedData shared) { int num = Mathf.Max(1, shared.m_maxQuality); return shared.m_blockPower + (float)Mathf.Max(0, num - 1) * shared.m_blockPowerPerLevel; } internal static float SeedMaxGrant(ShieldKind kind, float maxBlockArmor, string? prefab = null) { if (kind == ShieldKind.Tower && prefab != null && TowerLeftoverSeeds.TryGetValue(prefab, out var value)) { return value; } float num = kind switch { ShieldKind.Tower => maxBlockArmor * (35f / 76f), ShieldKind.Round => maxBlockArmor * (5f / 14f), ShieldKind.Buckler => maxBlockArmor * (2f / 9f), _ => 0f, }; if (kind == ShieldKind.Round || kind == ShieldKind.Buckler || kind == ShieldKind.Tower) { return RoundToNearest5(num); } return Mathf.Round(num); } internal static float RoundToNearest5(float value) { return Mathf.Round(value / 5f) * 5f; } internal static float RoundDurability(float value) { return Mathf.Ceil(value * 1.2f / 5f) * 5f; } internal static float RoundArmorUp(float value) { return Mathf.Ceil(value * 1.05f); } internal static void EnsureGrantConfig(string prefab, float seed) { if (!GrantConfigs.ContainsKey(prefab)) { float num = prefab switch { "ShieldFlametalTower" => 70f, "ShieldFlametal" => 45f, "ShieldCarapaceBuckler" => 20f, _ => seed, }; if (ConfigPaths.TryGetOverlayDefault("StaggerGrants", prefab, out string serialized) && float.TryParse(serialized, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result)) { num = result; } GrantConfigs[prefab] = ShieldReworkPlugin.ModConfig.Bind<float>("StaggerGrants", prefab, num, "Max-quality flat stagger grant for " + prefab + ". 0 disables."); ShieldReworkPlugin.Sync.Register((ConfigEntryBase)(object)GrantConfigs[prefab]); } } internal static float GetMaxGrant(string prefab) { if (GrantConfigs.TryGetValue(prefab, out ConfigEntry<float> value)) { return value.Value; } if (!RuntimeGrants.TryGetValue(prefab, out var value2)) { return 0f; } return value2; } internal static float GrantForItem(ItemData? item) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (!ShieldReworkPlugin.EnableStaggerGrant.Value || item?.m_shared == null) { return 0f; } if ((int)item.m_shared.m_itemType != 5) { return 0f; } string prefab = null; float value = 0f; if ((Object)(object)item.m_dropPrefab != (Object)null) { prefab = PrefabName(item.m_dropPrefab); value = GetMaxGrant(prefab); } if (value <= 0f && !SharedNameToGrant.TryGetValue(item.m_shared.m_name, out value)) { return 0f; } if (value <= 0f) { return 0f; } float perStep = GrantPerQualityStep(prefab, Classify(item.m_shared)); return GrantAtQuality(value, item.m_quality, item.m_shared.m_maxQuality, perStep); } internal static float GrantPerQualityStep(string? prefab, ShieldKind kind = ShieldKind.Unknown) { if (kind == ShieldKind.Buckler) { return 1f; } if (string.IsNullOrEmpty(prefab)) { return 1f; } if (GrantStep3.Contains(prefab)) { return 3f; } if (GrantStep2.Contains(prefab)) { return 2f; } return 1f; } internal static float GrantAtQuality(float maxGrant, int quality, int maxQuality, float perStep) { if (maxGrant <= 0f) { return 0f; } int num = Mathf.Max(1, maxQuality); int num2 = Mathf.Clamp(quality, 1, num); if (num == 1 || num2 >= num) { return maxGrant; } float num3 = Mathf.Max(0f, perStep); float num4 = maxGrant / (float)(num - 1); if (num3 > num4) { num3 = num4; } return Mathf.Round(maxGrant - (float)(num - 1) * num3 + (float)(num2 - 1) * num3); } internal static float GrantForEquippedShield(Humanoid humanoid) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 ItemData leftItem = humanoid.LeftItem; if (leftItem == null || (int)leftItem.m_shared.m_itemType != 5) { return 0f; } return GrantForItem(leftItem); } internal static void ApplyToObjectDB(ObjectDB db) { if (db?.m_items == null || db.m_items.Count == 0) { return; } bool flag = ConfigSync.IsServerAuthority() && ShieldReworkPlugin.GrantTableVersion.Value < 3; if (flag) { ShieldReworkPlugin.Log.LogInfo((object)$"Re-seeding StaggerGrants to designed max-quality table (cfg v{ShieldReworkPlugin.GrantTableVersion.Value} → v{3})."); } int num = 0; foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if (component?.m_itemData?.m_shared == null) { continue; } SharedData shared = component.m_itemData.m_shared; ShieldKind shieldKind = Classify(shared); if (shieldKind != ShieldKind.Unknown) { string text = PrefabName(item); float maxBlockArmor = MaxBaseBlockPower(shared); float num2 = SeedMaxGrant(shieldKind, maxBlockArmor, text); EnsureGrantConfig(text, num2); if (flag && GrantConfigs.TryGetValue(text, out ConfigEntry<float> value)) { value.Value = num2; } float maxGrant = GetMaxGrant(text); RuntimeGrants[text] = maxGrant; SharedNameToGrant[shared.m_name] = maxGrant; num++; if (shieldKind == ShieldKind.Tower && !OriginalArmor.ContainsKey(text)) { OriginalArmor[text] = (shared.m_blockPower, shared.m_blockPowerPerLevel); } if ((shieldKind == ShieldKind.Tower || shieldKind == ShieldKind.Round) && !OriginalDurability.ContainsKey(text)) { OriginalDurability[text] = (shared.m_maxDurability, shared.m_durabilityPerLevel); } (float, float) value2; if (ShieldReworkPlugin.EnableTowerArmorBonus.Value && shieldKind == ShieldKind.Tower) { (float, float) tuple = OriginalArmor[text]; shared.m_blockPower = RoundArmorUp(tuple.Item1); shared.m_blockPowerPerLevel = RoundArmorUp(tuple.Item2); } else if (OriginalArmor.TryGetValue(text, out value2)) { (shared.m_blockPower, shared.m_blockPowerPerLevel) = value2; } (float, float) value3; if (ShieldReworkPlugin.EnableDurabilityBonus.Value && (shieldKind == ShieldKind.Tower || shieldKind == ShieldKind.Round)) { (float, float) tuple3 = OriginalDurability[text]; shared.m_maxDurability = RoundDurability(tuple3.Item1); shared.m_durabilityPerLevel = RoundDurability(tuple3.Item2); } else if (OriginalDurability.TryGetValue(text, out value3)) { (shared.m_maxDurability, shared.m_durabilityPerLevel) = value3; } } } if (flag) { ShieldReworkPlugin.GrantTableVersion.Value = 3; } ConfigPaths.ApplyOverlayAfterObjectDbLoad(ShieldReworkPlugin.ModConfig); ShieldReworkPlugin.Log.LogInfo((object)$"Processed {num} shields from ObjectDB."); } } internal enum ShieldKind { Unknown, Tower, Round, Buckler } internal static class StaggerDebugHud { private const string RootName = "CombatAdjustments_StaggerDebug"; private static bool _visible; private static TextMeshProUGUI? _label; internal static bool Visible => _visible; internal static void Toggle(ConsoleEventArgs? args = null) { SetVisible(!_visible, args); } internal static void SetVisible(bool visible, ConsoleEventArgs? args = null) { if (visible && !CanUseHudOverlay(out string message)) { _visible = false; if (args != null) { Terminal context = args.Context; if (context != null) { context.AddString(message); } } return; } _visible = visible; if (_visible) { if (!EnsureLabel()) { _visible = false; if (args != null) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString("<color=yellow>Stagger HUD unavailable here.</color> Use <color=orange>shieldstagger</color> / <color=orange>sstagger</color> on dedicated server."); } } return; } } else if ((Object)(object)_label != (Object)null) { ((Component)_label).gameObject.SetActive(false); } if (args != null) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString(_visible ? "Stagger HUD: <color=orange>on</color> (current / total under stagger bar)" : "Stagger HUD: off"); } } } private static bool CanUseHudOverlay(out string message) { if (IsHeadlessServerContext()) { message = "<color=yellow>Stagger HUD is client-only.</color> Dedicated server has no player HUD — use <color=orange>shieldstagger</color> / <color=orange>sstagger</color>."; return false; } if ((Object)(object)Hud.instance == (Object)null) { message = "<color=yellow>Stagger HUD requires the in-game HUD (not available from console-only session).</color>"; return false; } message = string.Empty; return true; } private static bool IsHeadlessServerContext() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } return (Object)(object)Player.m_localPlayer == (Object)null; } internal static void Refresh(Hud hud, Player player) { if (_visible && !((Object)(object)hud == (Object)null) && !((Object)(object)player == (Object)null) && EnsureLabel(hud)) { ((Component)_label).gameObject.SetActive(true); Animator value = Traverse.Create((object)hud).Field("m_staggerAnimator").GetValue<Animator>(); if (value != null) { value.SetBool("Visible", true); } float value2 = Traverse.Create((object)player).Method("GetStaggerTreshold", Array.Empty<object>()).GetValue<float>(); float value3 = Traverse.Create((object)player).Field("m_staggerDamage").GetValue<float>(); float num = ShieldStats.GrantForEquippedShield((Humanoid)(object)player); if (num > 0f) { ((TMP_Text)_label).text = $"{value3:0.#} / {value2:0.#} (+{num:0})"; } else { ((TMP_Text)_label).text = $"{value3:0.#} / {value2:0.#}"; } } } private static bool EnsureLabel(Hud? hud = null) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) if (hud == null) { hud = Hud.instance; } if ((Object)(object)hud == (Object)null) { return false; } if ((Object)(object)_label != (Object)null) { if (Object.op_Implicit((Object)(object)_label)) { return true; } _label = null; } GuiBar value = Traverse.Create((object)hud).Field("m_staggerProgress").GetValue<GuiBar>(); if ((Object)(object)value == (Object)null) { return false; } Transform transform = ((Component)value).transform; Transform val = transform.Find("CombatAdjustments_StaggerDebug"); GameObject val2; if ((Object)(object)val != (Object)null) { val2 = ((Component)val).gameObject; _label = val2.GetComponent<TextMeshProUGUI>(); if ((Object)(object)_label == (Object)null) { _label = val2.AddComponent<TextMeshProUGUI>(); } } else { val2 = new GameObject("CombatAdjustments_StaggerDebug", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(transform, false); _label = val2.AddComponent<TextMeshProUGUI>(); } RectTransform val3 = (RectTransform)val2.transform; val3.anchorMin = new Vector2(0.5f, 0f); val3.anchorMax = new Vector2(0.5f, 0f); val3.pivot = new Vector2(0.5f, 1f); val3.sizeDelta = new Vector2(280f, 28f); val3.anchoredPosition = new Vector2(0f, -6f); TMP_Text val4 = Traverse.Create((object)hud).Field("m_staminaText").GetValue<TMP_Text>() ?? Traverse.Create((object)hud).Field("m_healthText").GetValue<TMP_Text>(); if ((Object)(object)val4 != (Object)null) { ((TMP_Text)_label).font = val4.font; ((TMP_Text)_label).fontSharedMaterial = val4.fontSharedMaterial; ((TMP_Text)_label).fontSize = Mathf.Max(14f, val4.fontSize * 0.85f); } else { if ((Object)(object)((TMP_Text)_label).font == (Object)null) { return false; } ((TMP_Text)_label).fontSize = 16f; } ((TMP_Text)_label).alignment = (TextAlignmentOptions)514; ((Graphic)_label).color = new Color(1f, 0.65f, 0.2f, 1f); ((TMP_Text)_label).textWrappingMode = (TextWrappingModes)0; ((Graphic)_label).raycastTarget = false; ApplyOutline(_label); return true; } private static void ApplyOutline(TextMeshProUGUI label) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((TMP_Text)label).font == (Object)null) { return; } try { ((TMP_Text)label).outlineWidth = 0.2f; ((TMP_Text)label).outlineColor = new Color32((byte)0, (byte)0, (byte)0, (byte)200); } catch { } } } [HarmonyPatch(typeof(Hud), "UpdateStagger")] internal static class Hud_UpdateStagger_Patch { private static void Postfix(Hud __instance, Player player) { StaggerDebugHud.Refresh(__instance, player); } } internal static class TwoHandedCombat { internal const float TwoHandedDamageBonusFraction = 0.1f; internal static bool TooltipDamagePreview; private static readonly Dictionary<Player, Attack> ActiveHyperArmor = new Dictionary<Player, Attack>(); private static bool Enabled => ShieldReworkPlugin.EnableTwoHandedCombat.Value; internal static bool IsTwoHandedMelee(ItemData? weapon) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 if (weapon?.m_shared == null) { return false; } if ((int)weapon.m_shared.m_itemType != 14) { return false; } SkillType skillType = weapon.m_shared.m_skillType; if ((int)skillType != 1 && (int)skillType != 7 && (int)skillType != 3) { return (int)skillType == 4; } return true; } internal static bool HasBalancedHyperArmor(ItemData? weapon) { return HasTwoHandedDamageBonus(weapon); } internal static bool HasTwoHandedDamageBonus(ItemData? weapon) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_002e: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 if (!Enabled || weapon?.m_shared == null) { return false; } if ((int)weapon.m_shared.m_itemType != 14) { return false; } AnimationState animationState = weapon.m_shared.m_animationState; if ((int)animationState == 2 || (int)animationState == 8 || (int)animationState == 13) { return true; } return false; } internal static void ApplyRoundedTenPercentDamageBonus(ref DamageTypes damages) { damages.m_damage = AddRoundedPercentBonus(damages.m_damage); damages.m_blunt = AddRoundedPercentBonus(damages.m_blunt); damages.m_slash = AddRoundedPercentBonus(damages.m_slash); damages.m_pierce = AddRoundedPercentBonus(damages.m_pierce); damages.m_chop = AddRoundedPercentBonus(damages.m_chop); damages.m_pickaxe = AddRoundedPercentBonus(damages.m_pickaxe); damages.m_fire = AddRoundedPercentBonus(damages.m_fire); damages.m_frost = AddRoundedPercentBonus(damages.m_frost); damages.m_lightning = AddRoundedPercentBonus(damages.m_lightning); damages.m_poison = AddRoundedPercentBonus(damages.m_poison); damages.m_spirit = AddRoundedPercentBonus(damages.m_spirit); damages.m_nonPlayer = AddRoundedPercentBonus(damages.m_nonPlayer); } private static float AddRoundedPercentBonus(float value) { if (!(value <= 0f)) { return value + Mathf.Floor(value * 0.1f); } return value; } internal static bool IsTwoHandedClub(ItemData? weapon) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if (IsTwoHandedMelee(weapon)) { return (int)weapon.m_shared.m_skillType == 3; } return false; } internal static bool IsGreatswordPrimary(Attack attack, ItemData? weapon) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 if (weapon?.m_shared != null && (int)weapon.m_shared.m_itemType == 14 && (int)weapon.m_shared.m_skillType == 1) { return attack.m_attackChainLevels > 1; } return false; } internal static void BeginBalancedHyperArmor(Attack attack) { if (Enabled) { ItemData weapon = attack.GetWeapon(); Humanoid? obj = AttackOwner(attack); Player val = (Player)(object)((obj is Player) ? obj : null); if (val != null && ((Character)val).InAttack() && HasBalancedHyperArmor(weapon)) { ActiveHyperArmor[val] = attack; } } } internal static void EndBalancedHyperArmor(Attack attack) { Humanoid? obj = AttackOwner(attack); Player val = (Player)(object)((obj is Player) ? obj : null); if (val != null && ActiveHyperArmor.TryGetValue(val, out Attack value) && value == attack) { ActiveHyperArmor.Remove(val); } } internal static bool HasActiveHyperArmor(Player player) { if (Enabled && ActiveHyperArmor.TryGetValue(player, out Attack value) && ((Character)player).InAttack()) { return value.GetWeapon() == ((Humanoid)player).GetCurrentWeapon(); } return false; } private static Humanoid? AttackOwner(Attack attack) { return Traverse.Create((object)attack).Field("m_character").GetValue<Humanoid>(); } } internal static class AreaAdrenaline { private static Player? s_attacker; private static float s_multiplierSum; private static float s_multiplierMax; private static readonly HashSet<Character> s_counted = new HashSet<Character>(); internal static bool Open(Attack attack, Humanoid? owner) { if (!ShieldReworkPlugin.EnableTwoHandedCombat.Value || !ShieldReworkPlugin.AreaAdrenalinePerEnemy.Value) { return false; } Player val = (Player)(object)((owner is Player) ? owner : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return false; } if (!TwoHandedCombat.IsTwoHandedClub(attack.GetWeapon())) { return false; } s_attacker = val; s_multiplierSum = 0f; s_multiplierMax = 0f; s_counted.Clear(); return true; } internal static void RecordDamage(Character victim) { if (!((Object)(object)s_attacker == (Object)null) && !((Object)(object)victim == (Object)(object)s_attacker) && s_counted.Add(victim) && (BaseAI.IsEnemy((Character)(object)s_attacker, victim) || ((Object)(object)victim.GetBaseAI() != (Object)null && victim.GetBaseAI().IsAggravatable()))) { s_multiplierSum += victim.m_enemyAdrenalineMultiplier; if (victim.m_enemyAdrenalineMultiplier > s_multiplierMax) { s_multiplierMax = victim.m_enemyAdrenalineMultiplier; } } } internal static void CloseAndAward(Attack attack) { Player val = s_attacker; s_attacker = null; s_counted.Clear(); if (!((Object)(object)val == (Object)null)) { float num = attack.m_attackAdrenaline * (s_multiplierSum - s_multiplierMax); if (num > 0f) { ((Character)val).AddAdrenaline(num); } } } internal static void Abort() { s_attacker = null; } } [HarmonyPatch(typeof(Attack), "DoAreaAttack")] internal static class Attack_DoAreaAttack_Adrenaline_Patch { private static void Prefix(Attack __instance, Humanoid ___m_character) { AreaAdrenaline.Open(__instance, ___m_character); } private static void Postfix(Attack __instance) { AreaAdrenaline.CloseAndAward(__instance); } private static Exception? Finalizer(Exception __exception) { if (__exception != null) { AreaAdrenaline.Abort(); } return __exception; } } [HarmonyPatch(typeof(Character), "Damage")] internal static class Character_Damage_AreaAdrenaline_Patch { private static void Prefix(Character __instance) { AreaAdrenaline.RecordDamage(__instance); } } [HarmonyPatch(typeof(Attack), "Update")] internal static class Attack_Update_HyperArmor_Patch { private static void Postfix(Attack __instance) { TwoHandedCombat.BeginBalancedHyperArmor(__instance); } } [HarmonyPatch(typeof(Attack), "OnAttackTrigger")] internal static class Attack_OnAttackTrigger_HyperArmor_Patch { private static void Postfix(Attack __instance) { TwoHandedCombat.EndBalancedHyperArmor(__instance); } } [HarmonyPatch(typeof(Attack), "Stop")] internal static class Attack_Stop_HyperArmor_Patch { private static void Postfix(Attack __instance) { TwoHandedCombat.EndBalancedHyperArmor(__instance); } } [HarmonyPatch(typeof(Character), "AddStaggerDamage")] internal static class Character_AddStaggerDamage_HyperArmor_Patch { private static bool Prefix(Character __instance, ref bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && TwoHandedCombat.HasActiveHyperArmor(val)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(Character), "Stagger")] internal static class Character_Stagger_HyperArmor_Patch { private static bool Prefix(Character __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { return !TwoHandedCombat.HasActiveHyperArmor(val); } return true; } } [HarmonyPatch(typeof(Attack), "ModifyDamage")] internal static class Attack_ModifyDamage_TwoHanded_Patch { private static void Postfix(Attack __instance, HitData hitData) { if (!ShieldReworkPlugin.EnableTwoHandedCombat.Value) { return; } ItemData weapon = __instance.GetWeapon(); if (TwoHandedCombat.HasTwoHandedDamageBonus(weapon)) { TwoHandedCombat.ApplyRoundedTenPercentDamageBonus(ref hitData.m_damage); if (TwoHandedCombat.IsGreatswordPrimary(__instance, weapon)) { hitData.m_staggerMultiplier *= ShieldReworkPlugin.GreatswordPrimaryStaggerMultiplier.Value; } } } } [HarmonyPatch(typeof(ItemData), "GetDamage", new Type[] { typeof(int), typeof(float) })] internal static class ItemData_GetDamage_TwoHandedTooltip_Patch { private static void Postfix(ItemData __instance, ref DamageTypes __result) { if (TwoHandedCombat.TooltipDamagePreview && ShieldReworkPlugin.EnableTwoHandedCombat.Value && TwoHandedCombat.HasTwoHandedDamageBonus(__instance)) { TwoHandedCombat.ApplyRoundedTenPercentDamageBonus(ref __result); } } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] internal static class ItemData_GetTooltip_TwoHanded_Patch { private static void Prefix() { TwoHandedCombat.TooltipDamagePreview = true; } private static void Postfix(ItemData item, ref string __result) { TwoHandedCombat.TooltipDamagePreview = false; if (!TwoHandedCombat.HasBalancedHyperArmor(item) || __result.IndexOf("Hyper-armor", StringComparison.OrdinalIgnoreCase) >= 0) { return; } int num = __result.IndexOf("$item_knockback:", StringComparison.Ordinal); if (num >= 0) { int num2 = __result.IndexOf('\n', num); if (num2 < 0) { __result += "\n<color=orange>Hyper-armor</color>"; } else { __result = __result.Insert(num2, "\n<color=orange>Hyper-armor</color>"); } } else { __result += "\n<color=orange>Hyper-armor</color>"; } } } internal static class WeaponBlockStats { private static readonly Dictionary<string, float> BlockPerLevelByPrefab = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase) { ["THSwordWood"] = 1f, ["BattleaxeWood"] = 1f, ["SledgeWood"] = 1f, ["AtgeirWood"] = 1f, ["SledgeStagbreaker"] = 1f, ["AtgeirBronze"] = 1f, ["AxeEarly"] = 1f, ["FistBjornClaw"] = 1f, ["Battleaxe"] = 2f, ["SledgeIron"] = 2f, ["AtgeirIron"] = 2f, ["BattleaxeCrystal"] = 3f, ["BattleaxeBlackmetal"] = 4f, ["AtgeirBlackmetal"] = 4f, ["AxeBerzerkr"] = 4f, ["AxeBerzerkrBlood"] = 4f, ["AxeBerzerkrLightning"] = 4f, ["AxeBerzerkrNature"] = 4f, ["FistBjornUndeadClaw"] = 4f, ["AtgeirHimminAfl"] = 5f, ["KnifeSkollAndHati"] = 5f, ["FistFenrirClaw"] = 5f, ["THSwordKrom"] = 6f, ["THSwordSlayer"] = 6f, ["THSwordSlayerBlood"] = 6f, ["THSwordSlayerLightning"] = 6f, ["THSwordSlayerNature"] = 6f, ["BattleaxeSkullSplittur"] = 6f, ["SledgeDemolisher"] = 6f, ["THSwordGold"] = 7f, ["THSwordGold_FrostFire"] = 7f, ["THSwordGold_BloodLightning"] = 7f, ["BattleaxeGold"] = 7f, ["BattleaxeGold_FrostFire"] = 7f, ["BattleaxeGold_BloodLightning"] = 7f, ["SledgeGold"] = 7f, ["SledgeGold_FrostFire"] = 7f, ["SledgeGold_BloodLightning"] = 7f, ["AtgeirGold"] = 7f, ["AtgeirGold_FrostFire"] = 7f, ["AtgeirGold_BloodLightning"] = 7f, ["FistGold"] = 7f, ["FistGold_FrostFire"] = 7f, ["FistGold_BloodLightning"] = 7f, ["AxeJotunBane"] = 7f }; private static readonly Dictionary<string, float> OriginalPerLevel = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static IEnumerable<string> TrackedPrefabs => BlockPerLevelByPrefab.Keys; internal static void ApplyToObjectDB(ObjectDB db) { if (db?.m_items == null) { return; } int num = 0; foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if (component?.m_itemData?.m_shared == null) { continue; } string key = ShieldStats.PrefabName(item); if (BlockPerLevelByPrefab.TryGetValue(key, out var value)) { SharedData shared = component.m_itemData.m_shared; if (!OriginalPerLevel.ContainsKey(key)) { OriginalPerLevel[key] = shared.m_blockPowerPerLevel; } shared.m_blockPowerPerLevel = (ShieldReworkPlugin.EnableWeaponBlockPerLevel.Value ? value : OriginalPerLevel[key]); num++; } } if (num > 0) { ShieldReworkPlugin.Log.LogInfo((object)$"Weapon block per-level: applied table to {num} prefabs (enabled={ShieldReworkPlugin.EnableWeaponBlockPerLevel.Value})."); } } } }