using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimBB")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ValheimBB")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("1699c6b0-5753-49cf-903b-e6b635fbcab9")] [assembly: AssemblyFileVersion("0.2.2.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.2.2.0")] namespace ValheimBB { [BepInPlugin("com.valheimbb", "ValheimBB", "0.2.2")] public class ValheimBBPlugin : BaseUnityPlugin { internal enum WisplightOutsideMistlandsMode { VanillaOrbit, Off, LockedInPlace } [HarmonyPatch(typeof(BaseAI), "DoIdleSound")] public static class BaseAI_DoIdleSound_AttachedOriginSound_Patch { private static void Prefix(BaseAI __instance) { SetDeerAlertedIdleSoundState(__instance, (Object)(object)__instance != (Object)null && __instance.IsAlerted()); ForceAttachSoundEffects(__instance?.m_idleSound); } private static IEnumerable Transpiler(IEnumerable instructions) { return ReplaceEffectListCreateNullParentWithSelfTransform(instructions); } } [HarmonyPatch(typeof(BaseAI), "SetAlerted")] public static class BaseAI_SetAlerted_AttachedOriginSound_Patch { private static void Prefix(BaseAI __instance) { ForceAttachSoundEffects(__instance?.m_alertedEffects); } private static void Postfix(BaseAI __instance, bool __0) { SetDeerAlertedIdleSoundState(__instance, __0); } private static IEnumerable Transpiler(IEnumerable instructions) { return ReplaceEffectListCreateNullParentWithSelfTransform(instructions); } } [HarmonyPatch(typeof(BaseAI), "OnDestroy")] public static class BaseAI_OnDestroy_DeerAlertedScaredSfx_Patch { private static void Postfix(BaseAI __instance) { ClearDeerAlertedIdleSoundState(__instance); } } [HarmonyPatch(typeof(Attack), "Update")] public static class Attack_Update_AttachedOriginSound_Patch { private static void Prefix(Attack __instance) { NormalizeAttackStartSoundEffects(__instance); } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] public static class Attack_DoMeleeAttack_AttachedOriginSound_Patch { private static void Prefix(Attack __instance) { NormalizeAttackTriggerSoundEffects(__instance); } } [HarmonyPatch(typeof(Attack), "DoAreaAttack")] public static class Attack_DoAreaAttack_AttachedOriginSound_Patch { private static void Prefix(Attack __instance) { NormalizeAttackTriggerSoundEffects(__instance); } } [HarmonyPatch(typeof(Attack), "ProjectileAttackTriggered")] public static class Attack_ProjectileAttackTriggered_AttachedOriginSound_Patch { private static void Prefix(Attack __instance) { NormalizeAttackTriggerSoundEffects(__instance); } } [HarmonyPatch(typeof(Attack), "OnTrailStart")] public static class Attack_OnTrailStart_AttachedOriginSound_Patch { private static void Prefix(Attack __instance) { NormalizeAttackTrailSoundEffects(__instance); } } [HarmonyPatch(typeof(Character), "ApplyDamage")] public static class Character_ApplyDamage_FallDamageGrunt_Patch { private static void Prefix(Character __instance, HitData hit, ref float __state) { __state = 0f; if (ShouldPlayFallDamageGrunt(__instance, hit)) { __state = __instance.GetHealth(); } } private static void Postfix(Character __instance, HitData hit, float __state) { if (!(__state <= 0f) && ShouldPlayFallDamageGrunt(__instance, hit)) { float health = __instance.GetHealth(); if (!(health <= 0f) && !(__state - health <= 0.1f)) { TryBroadcastPlayerDamageGrunt((Player)(object)((__instance is Player) ? __instance : null)); } } } private static bool ShouldPlayFallDamageGrunt(Character character, HitData hit) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)Player.m_localPlayer && hit != null) { return (int)hit.m_hitType == 3; } return false; } } [HarmonyPatch(typeof(Character), "AddFireDamage")] public static class Character_AddFireDamage_FireDamageGrunt_Patch { private static void Postfix(Character __instance, float damage) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !(damage <= 0f)) { TryBroadcastPlayerDamageGrunt(val); } } } [HarmonyPatch(typeof(Player), "Awake")] public static class Player_Awake_PlayerSfxRpc_Patch { private static void Postfix(Player __instance) { RegisterPlayerSfxRpc(__instance); RegisterPickaxeBedrockAudioRpc(__instance); } } [HarmonyPatch(typeof(Player), "OnDeath")] public static class Player_OnDeath_DeathSplat_Patch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { TryBroadcastPlayerDeathSplat(__instance); } } } [HarmonyPatch(typeof(Attack), "SpawnOnHitTerrain")] public static class Attack_SpawnOnHitTerrain_PickaxeBedrockTracking_Patch { private static void Prefix(Vector3 hitPoint, GameObject prefab, Character character, ItemData weapon) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) RegisterLocalPickaxeTerrainHit(hitPoint, prefab, character, weapon); } } [HarmonyPatch(typeof(TerrainOp), "OnPlaced")] public static class TerrainOp_OnPlaced_PickaxeBedrockAudio_Patch { private static void Prefix(TerrainOp __instance) { TryHandlePickaxeBedrockHit(__instance); } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] private static class Attack_DoMeleeAttack_PickaxeVegetationAudioContext { private static void Prefix(Attack __instance, out bool __state) { __state = BeginLocalPickaxeVegetationAudio(__instance); } private static Exception Finalizer(bool __state, Exception __exception) { EndLocalPickaxeVegetationAudio(__state); return __exception; } } [HarmonyPatch(typeof(Destructible), "Damage")] private static class Destructible_Damage_PickaxeVegetationAudio_Patch { private static void Prefix(Destructible __instance, HitData hit) { try { TryReplayPickaxeVegetationHitEffect((Component)(object)__instance, __instance?.m_hitEffect, hit); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error replaying pickaxe destructible vegetation hit effect: {arg}"); } } } } [HarmonyPatch(typeof(WearNTear), "Damage")] private static class WearNTear_Damage_PickaxeVegetationAudio_Patch { private static void Prefix(WearNTear __instance, HitData hit) { try { TryReplayPickaxeVegetationHitEffect((Component)(object)__instance, __instance?.m_hitEffect, hit); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error replaying pickaxe wear-and-tear vegetation hit effect: {arg}"); } } } } [HarmonyPatch(typeof(Chat), "InputText")] public static class Chat_InputText_Patch { private const float DefaultRemovePinRadius = 10f; private static bool Prefix(ref Chat __instance) { string text = ((TMP_InputField)((Terminal)__instance).m_input).text; if (string.IsNullOrWhiteSpace(text)) { return true; } if (!text.StartsWith("/", StringComparison.Ordinal)) { return true; } if (text.StartsWith("/addpin", StringComparison.OrdinalIgnoreCase)) { HandleAddPin(__instance, text); return false; } if (text.StartsWith("/removepin", StringComparison.OrdinalIgnoreCase)) { HandleRemoveNearestPin(__instance, text); return false; } if (text.StartsWith("/cleardeaths", StringComparison.OrdinalIgnoreCase)) { HandleClearDeathPins(__instance); return false; } return true; } private static void HandleAddPin(Chat chat, string text) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)Minimap.instance == (Object)null) { return; } string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length >= 2 && array[1].Equals("help", StringComparison.OrdinalIgnoreCase)) { ShowHelp(); ((TMP_InputField)((Terminal)chat).m_input).text = string.Empty; return; } PinType val = (PinType)3; int num = 3; if (array.Length >= 2) { string text2 = array[1]; if (text2.StartsWith("icon", StringComparison.OrdinalIgnoreCase)) { text2 = text2.Substring(4); } if (int.TryParse(text2, out var result) && result >= 0 && result <= 4 && Enum.TryParse("Icon" + result, out PinType result2)) { val = result2; num = result; } } Vector3 position = ((Component)Player.m_localPlayer).transform.position; Minimap.instance.AddPin(position, val, "", true, false, 0L, default(PlatformUserID)); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] /addpin -> {val} at {position}"); } if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString("ValheimBB", $"Pin {num} added.\n" + "Options: Pin 0 = Campfire, Pin 1 = House, Pin 2 = Anchor, Pin 3 = Dot, Pin 4 = Portal", (Type)1, false); } ((TMP_InputField)((Terminal)chat).m_input).text = string.Empty; } private static void HandleRemoveNearestPin(Chat chat, string text) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Minimap.instance == (Object)null)) { float num = 10f; string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length >= 2 && float.TryParse(array[1], out var result) && result > 0f) { num = result; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; bool flag = Minimap.instance.RemovePin(position, num); if ((Object)(object)Chat.instance != (Object)null) { string text2 = (flag ? $"Removed nearest pin within {num:0.#}m." : $"No pin found within {num:0.#}m."); ((Terminal)Chat.instance).AddString("ValheimBB", text2, (Type)1, false); } ((TMP_InputField)((Terminal)chat).m_input).text = string.Empty; } } private static void HandleClearDeathPins(Chat chat) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 if ((Object)(object)Minimap.instance == (Object)null) { return; } int num = 0; try { Minimap instance = Minimap.instance; FieldInfo field = typeof(Minimap).GetField("m_pins", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Could not find Minimap.m_pins via reflection."); } return; } if (!(field.GetValue(instance) is List list)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)"[ValheimBB] Minimap.m_pins is null or of unexpected type."); } return; } List list2 = new List(); foreach (PinData item in list) { if ((int)item.m_type == 4) { list2.Add(item); } } num = list2.Count; foreach (PinData item2 in list2) { instance.RemovePin(item2); } if ((Object)(object)Chat.instance != (Object)null) { string text = ((num > 0) ? $"Removed {num} death marker(s)." : "No death markers found."); ((Terminal)Chat.instance).AddString("ValheimBB", text, (Type)1, false); } } catch (Exception arg) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogError((object)$"[ValheimBB] Error clearing death pins: {arg}"); } } finally { ((TMP_InputField)((Terminal)chat).m_input).text = string.Empty; } } public static void ShowHelp() { if (!((Object)(object)Chat.instance == (Object)null)) { string text = "ValheimBB command help:\n/addpin [index] - Add a map pin at your current position.\n index: 0 = Campfire, 1 = House, 2 = Anchor, 3 = Dot, 4 = Portal (default: 3)\n/removepin [radius] - Remove the nearest pin within the given radius (meters).\n radius: optional, default is 10.\n/cleardeaths - Remove all death markers from the map."; ((Terminal)Chat.instance).AddString("ValheimBB", text, (Type)1, false); } } } private sealed class ValheimBBCompendiumEntry { private readonly Func _textFactory; private string _text; public string Label { get; } public string Name { get; } public string Topic { get; } public string Text { get { if (_text == null) { _text = _textFactory() ?? string.Empty; } return _text; } } public ValheimBBCompendiumEntry(string name, string topic, string label, string text) : this(name, topic, label, () => text) { } public ValheimBBCompendiumEntry(string name, string topic, string label, Func textFactory) { if (textFactory == null) { throw new ArgumentNullException("textFactory"); } Name = name; Topic = topic; Label = label; _textFactory = textFactory; } } [HarmonyPatch] private static class Tutorial_Awake_ValheimBBCompendium_Patch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("Tutorial"); if (!(type == null)) { return AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); } return null; } private static void Postfix(object __instance) { EnsureValheimBBCompendiumEntries(__instance); } } [HarmonyPatch(typeof(Player), "GetKnownTexts")] private static class Player_GetKnownTexts_ValheimBBCompendium_Patch { private static void Postfix(ref List> __result) { EnsureValheimBBKnownTexts(__result); } } private sealed class AmmoRecipeBatchTweakDefinition { public string RecipeName; public string OutputPrefabName; public HashSet StaticRequirementPrefabNames; public Dictionary VanillaRequirementAmounts; public bool TryGetTargetRequirementAmount(string requirementPrefabName, out int amount) { amount = 0; if (string.IsNullOrWhiteSpace(requirementPrefabName) || VanillaRequirementAmounts == null || !VanillaRequirementAmounts.TryGetValue(requirementPrefabName, out var value)) { return false; } amount = ((StaticRequirementPrefabNames != null && StaticRequirementPrefabNames.Contains(requirementPrefabName)) ? value : (value * 5)); return true; } } private struct AmmoRequirementBaseline { public string PrefabName; public int Amount; public AmmoRequirementBaseline(string prefabName, int amount) { PrefabName = prefabName; Amount = amount; } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDB_Awake_AmmoRecipeBatchTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyAmmoRecipeBatchTweaks(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDB_CopyOtherDB_AmmoRecipeBatchTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyAmmoRecipeBatchTweaks(__instance); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDB_Awake_BearHeaddressRecipe_Patch { private static void Postfix(ObjectDB __instance) { UpdateBearHeaddressRecipe(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDB_CopyOtherDB_BearHeaddressRecipe_Patch { private static void Postfix(ObjectDB __instance) { UpdateBearHeaddressRecipe(__instance); } } [HarmonyPatch(typeof(Character), "ApplyPushback", new Type[] { typeof(Vector3), typeof(float) })] private static class Character_ApplyPushback_CreatureMassScaling_Patch { private static void Prefix(Character __instance, ref float pushForce) { if (IsFeatureEnabled(EnableCreaturePushbackScaling) && !((Object)(object)__instance == (Object)null) && !__instance.IsPlayer() && pushForce != 0f && !Mathf.Approximately(0.5f, 1f)) { pushForce /= 0.5f; } } } private struct PreviousLoadedWeaponState { public ItemData Weapon; } [HarmonyPatch] private static class Player_UpdateWeaponLoading_PersistentCrossbowLoad_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "UpdateWeaponLoading", (Type[])null, (Type[])null); } private static bool Prefix(Player __instance, ItemData weapon, float dt) { if (!IsFeatureEnabled(EnableCrossbowLoadedPersistence)) { return true; } ClearInvalidLoadedWeapon(__instance); ItemData loadedWeapon = GetLoadedWeapon(__instance); if (!RequiresReload(weapon)) { return false; } if (HasPersistentLoadedFlag(weapon)) { if (loadedWeapon != weapon) { SetLoadedWeapon(__instance, weapon); } return false; } if (!IsReloadActionQueued(__instance) && ((Character)__instance).TryUseEitr(weapon.m_shared.m_attack.m_reloadEitrDrain)) { QueueReloadAction(__instance); } return false; } } [HarmonyPatch] private static class Player_SetWeaponLoaded_PersistentCrossbowLoad_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "SetWeaponLoaded", (Type[])null, (Type[])null); } private static void Prefix(Player __instance, out PreviousLoadedWeaponState __state) { __state = new PreviousLoadedWeaponState { Weapon = (IsFeatureEnabled(EnableCrossbowLoadedPersistence) ? GetLoadedWeapon(__instance) : null) }; } private static void Postfix(Player __instance, ItemData weapon, PreviousLoadedWeaponState __state) { if (IsFeatureEnabled(EnableCrossbowLoadedPersistence)) { if (weapon == null && __state.Weapon != null) { SetPersistentLoadedFlag(__state.Weapon, isLoaded: false); } if (RequiresReload(weapon)) { SetPersistentLoadedFlag(weapon, isLoaded: true); } } } } [HarmonyPatch] private static class Player_IsWeaponLoaded_PersistentCrossbowLoad_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "IsWeaponLoaded", (Type[])null, (Type[])null); } private static void Postfix(Player __instance, ref bool __result) { if (!IsFeatureEnabled(EnableCrossbowLoadedPersistence) || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon(); if (!RequiresReload(currentWeapon)) { __result = false; } else if (HasPersistentLoadedFlag(currentWeapon)) { if (GetLoadedWeapon(__instance) != currentWeapon) { SetLoadedWeapon(__instance, currentWeapon); } __result = true; } else { if (GetLoadedWeapon(__instance) == currentWeapon) { ClearLoadedWeapon(__instance); } __result = false; } } } [HarmonyPatch(typeof(Humanoid), "UnequipItem")] private static class Humanoid_UnequipItem_PersistentCrossbowLoad_Patch { private static void Prefix(Humanoid __instance, ItemData item) { s_loadedWeaponPreservePlayer = null; s_loadedWeaponPreserveItem = null; if (!IsFeatureEnabled(EnableCrossbowLoadedPersistence)) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && item != null) { ItemData loadedWeapon = GetLoadedWeapon(val); if (CanPersistLoadedWeapon(val, loadedWeapon)) { s_loadedWeaponPreservePlayer = val; s_loadedWeaponPreserveItem = loadedWeapon; } } } private static void Finalizer() { s_loadedWeaponPreservePlayer = null; s_loadedWeaponPreserveItem = null; } } [HarmonyPatch(typeof(Player), "ResetLoadedWeapon")] private static class Player_ResetLoadedWeapon_PersistentCrossbowLoad_Patch { private static bool Prefix(Player __instance) { if (!IsFeatureEnabled(EnableCrossbowLoadedPersistence)) { return true; } if ((Object)(object)s_loadedWeaponPreservePlayer != (Object)(object)__instance || s_loadedWeaponPreserveItem == null) { return true; } if (!CanPersistLoadedWeapon(__instance, s_loadedWeaponPreserveItem)) { return true; } CancelReloadAction(__instance); return false; } } [HarmonyPatch(typeof(Attack), "Start", new Type[] { typeof(Humanoid), typeof(Rigidbody), typeof(ZSyncAnimation), typeof(CharacterAnimEvent), typeof(VisEquipment), typeof(ItemData), typeof(Attack), typeof(float), typeof(float) })] private static class Attack_Start_PersistentCrossbowLoad_Patch { private static bool Prefix(Humanoid character, ItemData weapon, ref bool __result) { if (!IsFeatureEnabled(EnableCrossbowLoadedPersistence)) { return true; } Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || !RequiresReload(weapon)) { return true; } if (HasPersistentLoadedFlag(weapon)) { if (GetLoadedWeapon(val) != weapon) { SetLoadedWeapon(val, weapon); } return true; } __result = false; return false; } } [HarmonyPatch(typeof(Fireplace), "Interact")] public static class Fireplace_Interact_AltFuel_Patch { private static void Prefix(Fireplace __instance, Humanoid user, bool hold, bool alt, ref ItemDrop __state) { __state = null; try { if (hold || alt || (Object)(object)user == (Object)null) { return; } Inventory inventory = user.GetInventory(); if (inventory == null || inventory.GetItem("Wood", -1, true) != null) { return; } ItemData val = inventory.GetItem("RoundLog", -1, true) ?? inventory.GetItem("ElderBark", -1, true); if (val != null && !((Object)(object)val.m_dropPrefab == (Object)null)) { ItemDrop component = val.m_dropPrefab.GetComponent(); if (!((Object)(object)component == (Object)null)) { __state = __instance.m_fuelItem; __instance.m_fuelItem = component; } } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error in Fireplace.Interact alt-fuel Prefix: {arg}"); } } } private static void Postfix(Fireplace __instance, ItemDrop __state) { try { if ((Object)(object)__state != (Object)null) { __instance.m_fuelItem = __state; } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error restoring fireplace fuel item: {arg}"); } } } } [HarmonyPatch(typeof(Player), "ActivateGuardianPower")] public static class Player_ActivateGuardianPower_ModerDuration_Patch { private static void Prefix(StatusEffect ___m_guardianSE) { try { if (!((Object)(object)___m_guardianSE == (Object)null) && !(((Object)___m_guardianSE).name != "GP_Moder")) { float ttl = ___m_guardianSE.m_ttl; ___m_guardianSE.m_ttl = 600f; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Moder buff duration changed from {ttl} to {___m_guardianSE.m_ttl} seconds."); } } } catch (Exception arg) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"[ValheimBB] Error while extending Moder power duration: {arg}"); } } } } [HarmonyPatch(typeof(EnvMan), "GetWindDir")] public static class EnvMan_GetWindDir_ModerSideWind_Patch { private static void Postfix(ref Vector3 __result) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } SEMan sEMan = ((Character)Player.m_localPlayer).GetSEMan(); if (sEMan == null) { return; } bool flag = false; List statusEffects = sEMan.GetStatusEffects(); if (statusEffects == null) { return; } foreach (StatusEffect item in statusEffects) { if ((Object)(object)item != (Object)null && ((Object)item).name == "GP_Moder") { flag = true; break; } } if (!flag) { return; } Ship localShip = Ship.GetLocalShip(); if ((Object)(object)localShip == (Object)null) { return; } Vector3 forward = ((Component)localShip).transform.forward; forward.y = 0f; if (!(((Vector3)(ref forward)).sqrMagnitude < 0.0001f)) { ((Vector3)(ref forward)).Normalize(); Vector3 val = Vector3.Cross(Vector3.up, forward); if (!(((Vector3)(ref val)).sqrMagnitude < 0.0001f)) { ((Vector3)(ref val)).Normalize(); __result = val; } } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error in Moder side-wind GetWindDir patch: {arg}"); } } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDB_Awake_MuckshakeFoodTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyMuckshakeFoodTweaks(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDB_CopyOtherDB_MuckshakeFoodTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyMuckshakeFoodTweaks(__instance); } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class Player_OnSpawned_MuckshakeFoodRepair_Patch { private static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { RepairMuckshakeInventory(((Humanoid)__instance).GetInventory()); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while repairing Muckshake items on player spawn: {arg}"); } } } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] private static class Inventory_AddItem_MuckshakeFoodRepair_Patch { private static void Prefix(ItemData item) { NormalizeMuckshakeItem(item); } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(Vector2i) })] private static class Inventory_AddItemAtPos_MuckshakeFoodRepair_Patch { private static void Prefix(ItemData item) { NormalizeMuckshakeItem(item); } } [HarmonyPatch] private static class Inventory_AddItemToGrid_MuckshakeFoodRepair_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Inventory), "AddItem", new Type[4] { typeof(ItemData), typeof(int), typeof(int), typeof(int) }, (Type[])null); } private static void Prefix(ItemData item) { NormalizeMuckshakeItem(item); } } private sealed class CraftRecipeSeenClickHandler : MonoBehaviour { public InventoryGui InventoryGui; public GameObject Element; public void HandleClick() { if (IsFeatureEnabled(EnableBuildAndRecipeUnlockMarkers)) { MarkCraftRecipeSeenByElement(InventoryGui, Element); } } } [HarmonyPatch(typeof(Hud), "UpdatePieceList")] private static class Hud_UpdatePieceList_NewBuildPieceMarkers_Patch { private static void Postfix(Hud __instance, Player player) { RefreshNewBuildPieceMarkers(__instance, player); } } [HarmonyPatch(typeof(Hud), "OnHoverPiece")] private static class Hud_OnHoverPiece_NewBuildPieceMarkers_Patch { private static void Postfix(Hud __instance, UIInputHandler ih) { if (!IsFeatureEnabled(EnableBuildAndRecipeUnlockMarkers)) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)ih == (Object)null)) { Piece buildPieceForIcon = GetBuildPieceForIcon(__instance, localPlayer, ((Component)ih).gameObject); if (!((Object)(object)buildPieceForIcon == (Object)null) && MarkBuildPieceSeen(localPlayer, buildPieceForIcon)) { RefreshNewBuildPieceMarkers(__instance, localPlayer); } } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")] private static class InventoryGui_UpdateRecipeList_NewRecipeMarkers_Patch { private static void Postfix(InventoryGui __instance) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && __instance.InCraftTab()) { if (IsFeatureEnabled(EnableBuildAndRecipeUnlockMarkers)) { WireCraftRecipeHoverHandlers(__instance); } RefreshCraftRecipeMarkers(__instance, localPlayer); } } } [HarmonyPatch(typeof(Minimap), "Update")] private static class Minimap_Update_NoMapBiomeLabel_Patch { private static void Postfix(Minimap __instance) { UpdateNoMapBiomeLabel(__instance); } } [HarmonyPatch(typeof(Minimap), "OnDestroy")] private static class Minimap_OnDestroy_NoMapBiomeLabel_Patch { private static void Prefix() { s_noMapBiomeLabel = null; } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] private static class Attack_DoMeleeAttack_BedrockEffectContext { private static void Prefix(Attack __instance, ref bool __state) { __state = false; if (IsPickaxeAttack(__instance)) { if (s_activePickaxeAttackDepth == 0) { s_activePickaxeAttack = __instance; } s_activePickaxeAttackDepth++; __state = true; } } private static Exception Finalizer(bool __state, Exception __exception) { if (__state && s_activePickaxeAttackDepth > 0) { s_activePickaxeAttackDepth--; if (s_activePickaxeAttackDepth == 0) { s_activePickaxeAttack = null; } } return __exception; } } [HarmonyPatch(typeof(TerrainModifier), "OnPlaced")] private static class TerrainModifier_OnPlaced_BedrockEffectContext { private static void Prefix(TerrainModifier __instance, ref bool __state) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //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_0041: Unknown result type (might be due to invalid IL or missing references) __state = false; if (!((Object)(object)__instance == (Object)null)) { Vector3 checkPosition = ((Component)__instance).transform.position + Vector3.up * s_terrainModifierLevelOffsetRef.Invoke(__instance); __state = TryBeginSuppressedPlacementEffect(s_terrainModifierOnPlacedEffectRef.Invoke(__instance), checkPosition); } } private static Exception Finalizer(bool __state, Exception __exception) { EndSuppressedPlacementEffect(__state); return __exception; } } [HarmonyPatch(typeof(TerrainOp), "OnPlaced")] private static class TerrainOp_OnPlaced_BedrockEffectContext { private static void Prefix(TerrainOp __instance, ref bool __state) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) __state = false; if (!((Object)(object)__instance == (Object)null)) { Settings val = s_terrainOpSettingsRef.Invoke(__instance); Vector3 checkPosition = ((Component)__instance).transform.position + Vector3.up * val.m_levelOffset; __state = TryBeginSuppressedPlacementEffect(s_terrainOpOnPlacedEffectRef.Invoke(__instance), checkPosition); } } private static Exception Finalizer(bool __state, Exception __exception) { EndSuppressedPlacementEffect(__state); return __exception; } } [HarmonyPatch(typeof(EffectList), "Create", new Type[] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) })] private static class EffectList_Create_BedrockParticleSuppress { private static bool Prefix(EffectList __instance, Vector3 __0, ref GameObject[] __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!IsSuppressedPlacementEffect(__instance) && !ShouldSuppressPickaxeTerrainEffect(__instance, __0)) { return true; } __result = Array.Empty(); return false; } } private struct ActivePickaxeSwingState { public bool Active; public Player Player; public float PaidStaminaCost; public bool HitDirt; public bool HitNonDirt; } [HarmonyPatch(typeof(Attack), "GetAttackStamina")] public static class Attack_GetAttackStamina_PickaxeCost_Patch { [HarmonyPostfix] private static void Postfix(Attack __instance, ref float __result) { try { if (IsFeatureEnabled(EnablePickaxeStaminaTweaks) && TryGetLocalPickaxeSwingPlayer(__instance, out var _) && TryGetOverridePickaxeStaminaCost(__instance, out var staminaCost)) { __result = staminaCost; } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error overriding pickaxe stamina: {arg}"); } } } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] public static class Attack_DoMeleeAttack_PickaxeRefund_Patch { [HarmonyPrefix] private static void Prefix(Attack __instance, out bool __state) { __state = false; try { if (IsFeatureEnabled(EnablePickaxeDirtOnlyRefund) && TryGetLocalPickaxeSwingPlayer(__instance, out var player)) { float paidStaminaCost = Mathf.Max(0f, GetCurrentAttackStaminaCost(__instance)); BeginLocalPickaxeSwing(player, paidStaminaCost); __state = true; } } catch (Exception arg) { s_activePickaxeSwing = default(ActivePickaxeSwingState); ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error starting pickaxe refund tracking: {arg}"); } } } [HarmonyPostfix] private static void Postfix(bool __state) { if (!__state) { return; } try { FinishLocalPickaxeSwing(); } catch (Exception arg) { s_activePickaxeSwing = default(ActivePickaxeSwingState); ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error finishing pickaxe refund tracking: {arg}"); } } } [HarmonyFinalizer] private static Exception Finalizer(bool __state, Exception __exception) { if (__state) { s_activePickaxeSwing = default(ActivePickaxeSwingState); } return __exception; } } [HarmonyPatch(typeof(Attack), "SpawnOnHit")] public static class Attack_SpawnOnHit_PickaxeRefund_Patch { [HarmonyPrefix] private static void Prefix(GameObject target) { try { if (IsFeatureEnabled(EnablePickaxeDirtOnlyRefund) && (Object)(object)target != (Object)null) { RegisterLocalPickaxeNonDirtHit(); } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error registering pickaxe non-dirt hit: {arg}"); } } } } [HarmonyPatch(typeof(Attack), "SpawnOnHitTerrain")] public static class Attack_SpawnOnHitTerrain_PickaxeRefund_Patch { [HarmonyPrefix] private static void Prefix(Vector3 hitPoint) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) try { if (IsFeatureEnabled(EnablePickaxeDirtOnlyRefund)) { if (Heightmap.AtMaxLevelDepth(hitPoint)) { RegisterLocalPickaxeNonDirtHit(); } else { RegisterLocalPickaxeDirtHit(); } } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error registering pickaxe terrain hit: {arg}"); } } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] public static class ObjectDB_Awake_RestingDelay_Patch { private static void Postfix(ObjectDB __instance) { ApplyRestingDelayToTemplates(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] public static class ObjectDB_CopyOtherDB_RestingDelay_Patch { private static void Postfix(ObjectDB __instance) { ApplyRestingDelayToTemplates(__instance); } } [HarmonyPatch(typeof(SE_Cozy), "Setup")] public static class SE_Cozy_Setup_RestingDelay_Patch { private static void Postfix(SE_Cozy __instance) { ApplyRestingDelay(__instance); } } [HarmonyPatch(typeof(SE_Cozy), "UpdateStatusEffect")] public static class SE_Cozy_UpdateStatusEffect_RestingDelay_Patch { private static void Prefix(SE_Cozy __instance) { ApplyRestingDelay(__instance); } } [HarmonyPatch(typeof(Hud), "UpdateStatusEffects")] public static class Hud_UpdateStatusEffects_RestingCountdown_Patch { private static void Postfix(Hud __instance, List statusEffects) { if (!((Object)(object)__instance == (Object)null) && statusEffects != null && HudStatusEffectsField?.GetValue(__instance) is List list) { int num = Mathf.Min(statusEffects.Count, list.Count); for (int i = 0; i < num; i++) { RectTransform statusEffectRoot = list[i]; StatusEffect obj = statusEffects[i]; UpdateRestingCountdown(statusEffectRoot, (SE_Cozy)(object)((obj is SE_Cozy) ? obj : null)); } } } private static void UpdateRestingCountdown(RectTransform statusEffectRoot, SE_Cozy cozyEffect) { if ((Object)(object)statusEffectRoot == (Object)null) { return; } if (!IsRestingEffect(cozyEffect)) { HideRestingCountdown(statusEffectRoot); return; } if (!IsFeatureEnabled(EnableRestingTweaks)) { ApplyRestingDelay(cozyEffect); HideRestingCountdown(statusEffectRoot); return; } ApplyRestingDelay(cozyEffect); TextMeshProUGUI val = EnsureRestingCountdownLabel(statusEffectRoot); if (!((Object)(object)val == (Object)null)) { PositionRestingCountdownLabel(statusEffectRoot, ((TMP_Text)val).rectTransform); int num = Mathf.CeilToInt(15f - ((StatusEffect)cozyEffect).GetDuration()); if (num > 0) { ((TMP_Text)val).text = num.ToString(); ((Component)val).gameObject.SetActive(true); } else { ((Component)val).gameObject.SetActive(false); } } } private static void HideRestingCountdown(RectTransform statusEffectRoot) { Transform val = ((Transform)statusEffectRoot).Find("BB_RestingCountdown"); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(false); } } private static TextMeshProUGUI EnsureRestingCountdownLabel(RectTransform statusEffectRoot) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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_00b2: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) Transform val = ((Transform)statusEffectRoot).Find("BB_RestingCountdown"); if ((Object)(object)val != (Object)null) { return ((Component)val).GetComponent(); } Transform obj = ((Transform)statusEffectRoot).Find("Icon"); Transform obj2 = ((obj is RectTransform) ? obj : null); Transform obj3 = ((Transform)statusEffectRoot).Find("TimeText"); TMP_Text val2 = ((obj3 != null) ? ((Component)obj3).GetComponent() : null); if ((Object)(object)obj2 == (Object)null || (Object)(object)val2 == (Object)null) { return null; } GameObject val3 = new GameObject("BB_RestingCountdown", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }) { layer = ((Component)statusEffectRoot).gameObject.layer }; RectTransform component = val3.GetComponent(); ((Transform)component).SetParent((Transform)(object)statusEffectRoot, false); ((Transform)component).SetAsLastSibling(); component.pivot = new Vector2(1f, 0f); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.sizeDelta = new Vector2(26f, 16f); PositionRestingCountdownLabel(statusEffectRoot, component); TextMeshProUGUI component2 = val3.GetComponent(); ((TMP_Text)component2).font = val2.font; ((TMP_Text)component2).fontSharedMaterial = val2.fontSharedMaterial; ((TMP_Text)component2).fontStyle = val2.fontStyle; ((TMP_Text)component2).fontSize = val2.fontSize; ((TMP_Text)component2).characterSpacing = val2.characterSpacing; ((TMP_Text)component2).wordSpacing = val2.wordSpacing; ((TMP_Text)component2).lineSpacing = val2.lineSpacing; ((TMP_Text)component2).enableAutoSizing = val2.enableAutoSizing; ((TMP_Text)component2).fontSizeMin = val2.fontSizeMin; ((TMP_Text)component2).fontSizeMax = val2.fontSizeMax; ((Graphic)component2).color = ((Graphic)val2).color; ((TMP_Text)component2).alignment = (TextAlignmentOptions)1028; ((TMP_Text)component2).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)component2).overflowMode = (TextOverflowModes)0; ((Graphic)component2).raycastTarget = false; ((Component)component2).gameObject.SetActive(false); return component2; } private static void PositionRestingCountdownLabel(RectTransform statusEffectRoot, RectTransform countdownRect) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) Transform obj = ((Transform)statusEffectRoot).Find("Icon"); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)countdownRect == (Object)null)) { Vector2 anchoredPosition = val.anchoredPosition; Rect rect = val.rect; float num = ((Rect)(ref rect)).width * (1f - val.pivot.x); rect = val.rect; Vector2 val2 = anchoredPosition + new Vector2(num, (0f - ((Rect)(ref rect)).height) * val.pivot.y); countdownRect.anchoredPosition = val2 + new Vector2(-2f, 4f); } } } private struct PendingThrownSpearContext { public bool Active; public string ThrowerIdentity; public string ExpectedItemName; } [HarmonyPatch(typeof(Projectile), "OnHit")] private static class Projectile_OnHit_ThrownSpearPickupRules_Patch { private static void Prefix(Projectile __instance, out PendingThrownSpearContext __state) { __state = s_pendingThrownSpearContext; if (TryCreatePendingThrownSpearContext(__instance, out var context)) { s_pendingThrownSpearContext = context; } } private static void Finalizer(PendingThrownSpearContext __state) { s_pendingThrownSpearContext = __state; } } [HarmonyPatch(typeof(ItemDrop), "DropItem", new Type[] { typeof(ItemData), typeof(int), typeof(Vector3), typeof(Quaternion) })] private static class ItemDrop_DropItem_ThrownSpearPickupRules_Patch { private static void Prefix(ItemData item) { TryTagPendingThrownSpearItem(item); } } [HarmonyPatch(typeof(Player), "AutoPickup")] private static class Player_AutoPickup_ThrownSpearPickupRules_Patch { private static void Prefix(out int __state) { __state = s_autoPickupContextDepth; s_autoPickupContextDepth = __state + 1; } private static void Finalizer(int __state) { s_autoPickupContextDepth = __state; } } [HarmonyPatch(typeof(ItemDrop), "CanPickup", new Type[] { typeof(bool) })] private static class ItemDrop_CanPickup_ThrownSpearPickupRules_Patch { private static void Postfix(ItemDrop __instance, ref bool __result) { if (IsFeatureEnabled(EnableThrownSpearPickupProtection) && __result && IsInAutoPickupContext() && __instance?.m_itemData != null && TryGetThrownSpearThrowerIdentity(__instance.m_itemData, out var throwerIdentity) && TryGetPlayerIdentity(Player.m_localPlayer, out var playerIdentity) && !string.Equals(playerIdentity, throwerIdentity, StringComparison.Ordinal)) { __result = false; } } } [HarmonyPatch(typeof(ItemDrop), "RequestOwn")] private static class ItemDrop_RequestOwn_ThrownSpearPickupRules_Patch { private static bool Prefix(ItemDrop __instance) { if (!IsFeatureEnabled(EnableThrownSpearPickupProtection)) { return true; } if (!IsInAutoPickupContext() || __instance?.m_itemData == null) { return true; } if (!TryGetThrownSpearThrowerIdentity(__instance.m_itemData, out var throwerIdentity)) { return true; } if (!TryGetPlayerIdentity(Player.m_localPlayer, out var playerIdentity)) { return true; } return string.Equals(playerIdentity, throwerIdentity, StringComparison.Ordinal); } } [HarmonyPatch(typeof(Humanoid), "Pickup", new Type[] { typeof(GameObject), typeof(bool), typeof(bool) })] private static class Humanoid_Pickup_ThrownSpearPickupRules_Patch { private static void Postfix(GameObject go, bool __result) { if (__result && !((Object)(object)go == (Object)null)) { ItemDrop component = go.GetComponent(); if (component?.m_itemData != null) { ClearThrownSpearThrowerIdentity(component.m_itemData); } } } } [HarmonyPatch(typeof(ProximityState), "OnTriggerEnter")] private static class ProximityState_OnTriggerEnter_StoneOvenDoorDelay_Patch { private static void Postfix(ProximityState __instance, Collider other) { if (ShouldDelayStoneOvenDoor(__instance) && IsAcceptedProximityCollider(__instance, other)) { CancelStoneOvenDoorClose(__instance); } } } [HarmonyPatch(typeof(ProximityState), "Start")] private static class ProximityState_Start_StoneOvenDoorRadius_Patch { private static void Postfix(ProximityState __instance) { if (ShouldDelayStoneOvenDoor(__instance)) { TryApplyStoneOvenDoorTriggerRadius(__instance, out var _, out var _); } } } [HarmonyPatch(typeof(ProximityState), "OnTriggerExit")] private static class ProximityState_OnTriggerExit_StoneOvenDoorDelay_Patch { private static bool Prefix(ProximityState __instance, Collider other) { if (!ShouldDelayStoneOvenDoor(__instance)) { return true; } List list = s_proximityStateNearRef.Invoke(__instance); list?.Remove(other); if ((list == null || list.Count == 0) && (Object)(object)__instance.m_animator != (Object)null && __instance.m_animator.GetBool("near")) { ScheduleStoneOvenDoorClose(__instance); } return false; } } [HarmonyPatch] private static class Player_UpdateTeleport_TransitionFailsafe_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "UpdateTeleport", (Type[])null, (Type[])null); } private static void Postfix(Player __instance) { TrackLocalTeleportState(__instance); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDB_Awake_WeaponKnockbackTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyWeaponKnockbackTweaks(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDB_CopyOtherDB_WeaponKnockbackTweaks_Patch { private static void Postfix(ObjectDB __instance) { ApplyWeaponKnockbackTweaks(__instance); } } [HarmonyPatch(typeof(SE_Demister), "Setup")] private static class SE_Demister_Setup_WisplightAutoRetract_Patch { private static void Prefix(SE_Demister __instance) { if (GetWisplightOutsideMistlandsBehavior() != 0 && (Object)(object)__instance != (Object)null) { ((StatusEffect)__instance).m_startMessage = string.Empty; } } } [HarmonyPatch(typeof(SE_Demister), "UpdateStatusEffect")] private static class SE_Demister_UpdateStatusEffect_WisplightAutoRetract_Patch { private static bool Prefix(SE_Demister __instance) { if (!ShouldOverrideWisplightBall(__instance)) { return true; } if (GetWisplightOutsideMistlandsBehavior() == WisplightOutsideMistlandsMode.Off) { HideWisplightBall(__instance); } else { PinWisplightBallToChest(__instance); } return false; } } [HarmonyPatch(typeof(ObjectDB), "Awake")] public static class ObjectDB_Awake_Patch { private static readonly MethodInfo s_updateRegistersMethod = AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null); private static void Postfix(ObjectDB __instance) { if (!IsFeatureEnabled(EnableCustomHoes)) { return; } try { EnsureCustomHoeRegistrations(__instance); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while registering custom hoe recipes: {arg}"); } } } private static void EnsureFlintHoeRegistered(ObjectDB objectDB) { EnsureCustomHoeRegistered(objectDB, FlintHoePrefab, "HoeFlint", "Recipe_HoeFlint", "Flint", "Flint Hoe"); } private static void EnsureBronzeHoeRegistered(ObjectDB objectDB) { EnsureCustomHoeRegistered(objectDB, BronzeHoePrefab, "HoeBronze", "Recipe_HoeBronze", "Bronze", "Bronze Hoe"); } internal static void EnsureCustomHoeRegistrations(ObjectDB objectDB) { if (IsFeatureEnabled(EnableCustomHoes)) { EnsureCustomHoePrefabsAvailable(objectDB); EnsureFlintHoeRegistered(objectDB); EnsureBronzeHoeRegistered(objectDB); } } internal static void EnsureCustomHoePrefabsAvailable(ObjectDB objectDB) { EnsureCustomHoePrefabAvailable(objectDB, ref FlintHoePrefab, "HoeFlint", "Flint Hoe", "A flint-edged hoe for wider roadwork and brush clearing.", ref FlintHoeBaseStaminaCost, ref FlintHoeExtraClearStaminaCost); EnsureCustomHoePrefabAvailable(objectDB, ref BronzeHoePrefab, "HoeBronze", "Bronze Hoe", "A bronze hoe that clears more brush with each swing. Can also clear dead tree logs and stumps.", ref BronzeHoeBaseStaminaCost, ref BronzeHoeExtraClearStaminaCost); } private static void EnsureCustomHoePrefabAvailable(ObjectDB objectDB, ref GameObject customHoePrefab, string customHoePrefabName, string displayName, string description, ref float baseStaminaCost, ref float extraClearStaminaCost) { if ((Object)(object)objectDB == (Object)null) { return; } GameObject itemPrefab = objectDB.GetItemPrefab("Hoe"); ItemDrop val = ((itemPrefab != null) ? itemPrefab.GetComponent() : null); if ((Object)(object)itemPrefab == (Object)null || (Object)(object)val == (Object)null) { return; } val.m_itemData.m_shared.m_name = "Stone Hoe"; if ((Object)(object)customHoePrefab == (Object)null) { customHoePrefab = CreateDetachedCustomHoePrefab(itemPrefab, customHoePrefabName); } ItemDrop component = customHoePrefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] ItemDrop missing on " + displayName + " prefab.")); } return; } component.m_itemData.m_dropPrefab = customHoePrefab; component.m_itemData.m_shared.m_name = displayName; component.m_itemData.m_shared.m_description = description; float num = val.m_itemData.m_shared.m_attack?.m_attackStamina ?? 5f; baseStaminaCost = num * 0.2f; extraClearStaminaCost = Mathf.Max(0f, num * 0.5f - baseStaminaCost); if (component.m_itemData.m_shared.m_attack != null) { component.m_itemData.m_shared.m_attack.m_attackStamina = baseStaminaCost; } EnsureDetachedClonedPieceTable(val, component, customHoePrefabName + "_PieceTable"); } private static GameObject CreateDetachedCustomHoePrefab(GameObject originalHoePrefab, string prefabName) { bool activeSelf = originalHoePrefab.activeSelf; originalHoePrefab.SetActive(false); GameObject obj = Object.Instantiate(originalHoePrefab); originalHoePrefab.SetActive(activeSelf); ((Object)obj).name = prefabName; obj.SetActive(false); Object.DontDestroyOnLoad((Object)(object)obj); return obj; } private static PieceTable EnsureDetachedClonedPieceTable(ItemDrop originalHoeItem, ItemDrop customHoeItem, string pieceTableName) { PieceTable val = customHoeItem.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null && ((Object)((Component)val).gameObject).name == pieceTableName) { return val; } PieceTable val2 = originalHoeItem.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null) { return null; } GameObject obj = Object.Instantiate(((Component)val2).gameObject); ((Object)obj).name = pieceTableName; Object.DontDestroyOnLoad((Object)(object)obj); PieceTable component = obj.GetComponent(); customHoeItem.m_itemData.m_shared.m_buildPieces = component; return component; } private static void EnsureCustomHoeRegistered(ObjectDB objectDB, GameObject customHoePrefab, string customHoePrefabName, string recipeName, string replacementRequirementPrefab, string displayName) { if ((Object)(object)objectDB == (Object)null || objectDB.m_items == null || objectDB.m_recipes == null || (Object)(object)customHoePrefab == (Object)null) { return; } GameObject itemPrefab = objectDB.GetItemPrefab("Hoe"); if ((Object)(object)itemPrefab != (Object)null) { ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_itemData.m_shared.m_name = "Stone Hoe"; } } if (!objectDB.m_items.Contains(customHoePrefab)) { objectDB.m_items.Add(customHoePrefab); } ItemDrop component2 = customHoePrefab.GetComponent(); if ((Object)(object)component2 == (Object)null) { return; } Recipe val = null; Recipe val2 = null; foreach (Recipe recipe in objectDB.m_recipes) { if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null)) { string text = CleanPrefabName(((Object)recipe.m_item).name); if (text == "Hoe") { val = recipe; } if (text == customHoePrefabName || string.Equals(((Object)recipe).name, recipeName, StringComparison.OrdinalIgnoreCase)) { val2 = recipe; } } } if ((Object)(object)val == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not find the vanilla Hoe recipe to clone for " + displayName + ".")); } RefreshObjectDB(objectDB); return; } if ((Object)(object)val2 == (Object)null) { val2 = Object.Instantiate(val); objectDB.m_recipes.Add(val2); } ((Object)val2).name = recipeName; val2.m_item = component2; val2.m_enabled = true; val2.m_resources = BuildCustomHoeRequirements(objectDB, val.m_resources, replacementRequirementPrefab); RefreshObjectDB(objectDB); } private static Requirement[] BuildCustomHoeRequirements(ObjectDB objectDB, Requirement[] sourceRequirements, string replacementRequirementPrefab) { //IL_003a: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown if (sourceRequirements == null || sourceRequirements.Length == 0) { return Array.Empty(); } List list = new List(sourceRequirements.Length); foreach (Requirement val in sourceRequirements) { if (val == null || (Object)(object)val.m_resItem == (Object)null) { continue; } Requirement val2 = new Requirement { m_resItem = val.m_resItem, m_amount = val.m_amount, m_amountPerLevel = val.m_amountPerLevel, m_recover = val.m_recover }; if (string.Equals(CleanPrefabName(((Object)val.m_resItem).name), "Stone", StringComparison.OrdinalIgnoreCase)) { GameObject itemPrefab = objectDB.GetItemPrefab(replacementRequirementPrefab); ItemDrop val3 = ((itemPrefab != null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val3 != (Object)null) { val2.m_resItem = val3; } } list.Add(val2); } return list.ToArray(); } private static void RefreshObjectDB(ObjectDB objectDB) { s_updateRegistersMethod?.Invoke(objectDB, null); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] public static class ObjectDB_CopyOtherDB_CustomHoeRegistration_Patch { private static void Postfix(ObjectDB __instance) { if (!IsFeatureEnabled(EnableCustomHoes)) { return; } try { ObjectDB_Awake_Patch.EnsureCustomHoeRegistrations(__instance); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while re-registering custom hoes after ObjectDB copy: {arg}"); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] public static class Player_OnSpawned_CustomHoeRepair_Patch { private static void Postfix(Player __instance) { if (!IsFeatureEnabled(EnableCustomHoes) || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { RepairCustomItemInventory(((Humanoid)__instance).GetInventory()); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while repairing custom hoe inventory items: {arg}"); } } } } [HarmonyPatch(typeof(Inventory), "Save")] public static class Inventory_Save_CustomHoeRepair_Patch { private static void Prefix(Inventory __instance, out List __state) { __state = null; if (!IsFeatureEnabled(EnableCustomHoes)) { return; } try { __state = PrepareCustomItemInventoryForSave(__instance); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while repairing custom hoes before inventory save: {arg}"); } } } private static void Postfix(List __state) { RestoreCustomItemInventoryAfterSave(__state); } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] public static class Inventory_AddItem_CustomHoeRepair_Patch { private static void Prefix(ItemData item) { if (IsFeatureEnabled(EnableCustomHoes)) { NormalizeCustomItem(item); } } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(Vector2i) })] public static class Inventory_AddItemAtPos_CustomHoeRepair_Patch { private static void Prefix(ItemData item) { if (IsFeatureEnabled(EnableCustomHoes)) { NormalizeCustomItem(item); } } } [HarmonyPatch] public static class Inventory_AddItemToGrid_CustomHoeRepair_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Inventory), "AddItem", new Type[4] { typeof(ItemData), typeof(int), typeof(int), typeof(int) }, (Type[])null); } private static void Prefix(ItemData item) { if (IsFeatureEnabled(EnableCustomHoes)) { NormalizeCustomItem(item); } } } [HarmonyPatch] public static class Inventory_AddSerializedItem_CustomHoeAlias_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Inventory), "AddItem", new Type[12] { typeof(string), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary), typeof(int), typeof(bool) }, (Type[])null); } private static void Prefix(ref string name, Dictionary customData) { if (!IsFeatureEnabled(EnableCustomHoes)) { return; } string text = name; if (!TryResolveSerializedCustomItemRuntimePrefab(name, customData, out var resolvedName)) { return; } name = resolvedName; if (!string.Equals(CleanPrefabName(text), resolvedName, StringComparison.OrdinalIgnoreCase)) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ValheimBB] Rewrote serialized custom item save name '" + text + "' to '" + resolvedName + "'.")); } } } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] public static class Humanoid_EquipItem_CustomHoeRepair_Patch { private static void Prefix(ItemData item) { if (IsFeatureEnabled(EnableCustomHoes) && IsCustomHoeItem(item)) { NormalizeCustomItem(item); } } } [HarmonyPatch(typeof(ObjectDB), "GetItemPrefab", new Type[] { typeof(string) })] public static class ObjectDB_GetItemPrefab_CustomHoeAlias_Patch { private static bool Prefix(ObjectDB __instance, string name, ref GameObject __result) { if (!IsFeatureEnabled(EnableCustomHoes)) { return true; } if (!TryResolveCustomItemPrefab(__instance, null, name, out var prefab)) { return true; } __result = prefab; return false; } } [HarmonyPatch] public static class ObjectDB_TryGetItemPrefab_CustomHoeAlias_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(ObjectDB), "TryGetItemPrefab", new Type[2] { typeof(string), typeof(GameObject).MakeByRefType() }, (Type[])null); } private static bool Prefix(string name, ref bool __result, ref GameObject prefab) { if (!IsFeatureEnabled(EnableCustomHoes)) { return true; } if (!TryResolveCustomItemPrefab(ObjectDB.instance, null, name, out var prefab2)) { return true; } prefab = prefab2; __result = (Object)(object)prefab2 != (Object)null; return false; } } private struct TreeBaseDropSuppressState { public bool Active; public DropTable DropWhenDestroyed; } private struct TreeLogDropSuppressState { public bool Active; public DropTable DropWhenDestroyed; public GameObject SubLogPrefab; } private struct DestructibleDropSuppressState { public bool Active; public GameObject SpawnWhenDestroyed; } private struct HoeClearDropSuppressState { public bool Active; } [HarmonyPatch] private static class DamageText_ShowText_HoeClearSuppress_Patch { [CompilerGenerated] private sealed class d__0 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private MethodBase <>2__current; private int <>l__initialThreadId; private MethodInfo[] <>7__wrap1; private int <>7__wrap2; MethodBase IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__0(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_006e; } <>1__state = -1; <>7__wrap1 = typeof(DamageText).GetMethods(BindingFlags.Instance | BindingFlags.Public); <>7__wrap2 = 0; goto IL_007c; IL_006e: <>7__wrap2++; goto IL_007c; IL_007c: if (<>7__wrap2 < <>7__wrap1.Length) { MethodInfo methodInfo = <>7__wrap1[<>7__wrap2]; if (methodInfo.Name == "ShowText") { <>2__current = methodInfo; <>1__state = 1; return true; } goto IL_006e; } <>7__wrap1 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__0(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [IteratorStateMachine(typeof(d__0))] private static IEnumerable TargetMethods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__0(-2); } private static bool Prefix() { if (s_hoeClearDamageTextSuppressDepth <= 0) { return Time.realtimeSinceStartup >= s_hoeClearDamageTextSuppressUntil; } return false; } } [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] private static class TreeBase_RPCDamage_HoeClearNoDrops_Patch { private static void Prefix(TreeBase __instance, HitData hit, out TreeBaseDropSuppressState __state) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown __state = default(TreeBaseDropSuppressState); if (!((Object)(object)__instance == (Object)null) && IsHoeClearNoDropsHit(hit)) { __state = new TreeBaseDropSuppressState { Active = true, DropWhenDestroyed = __instance.m_dropWhenDestroyed }; BeginHoeClearDropSuppress(); __instance.m_dropWhenDestroyed = new DropTable(); } } private static void Finalizer(TreeBase __instance, TreeBaseDropSuppressState __state) { if (__state.Active) { if ((Object)(object)__instance != (Object)null) { __instance.m_dropWhenDestroyed = __state.DropWhenDestroyed; } EndHoeClearDropSuppress(); } } } [HarmonyPatch(typeof(TreeBase), "SpawnLog")] private static class TreeBase_SpawnLog_HoeClearNoDrops_Patch { private static bool Prefix() { return !ShouldSuppressHoeClearDrops(); } } [HarmonyPatch(typeof(TreeLog), "Destroy")] private static class TreeLog_Destroy_HoeClearNoDrops_Patch { private static void Prefix(TreeLog __instance, HitData hitData, out TreeLogDropSuppressState __state) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown __state = default(TreeLogDropSuppressState); if (!((Object)(object)__instance == (Object)null) && IsHoeClearNoDropsHit(hitData)) { __state = new TreeLogDropSuppressState { Active = true, DropWhenDestroyed = __instance.m_dropWhenDestroyed, SubLogPrefab = __instance.m_subLogPrefab }; BeginHoeClearDropSuppress(); __instance.m_dropWhenDestroyed = new DropTable(); __instance.m_subLogPrefab = null; } } private static void Finalizer(TreeLog __instance, TreeLogDropSuppressState __state) { if (__state.Active) { if ((Object)(object)__instance != (Object)null) { __instance.m_dropWhenDestroyed = __state.DropWhenDestroyed; __instance.m_subLogPrefab = __state.SubLogPrefab; } EndHoeClearDropSuppress(); } } } [HarmonyPatch(typeof(Destructible), "Destroy", new Type[] { typeof(HitData) })] private static class Destructible_Destroy_HoeClearNoDrops_Patch { private static void Prefix(Destructible __instance, HitData hit, out DestructibleDropSuppressState __state) { __state = default(DestructibleDropSuppressState); if (!((Object)(object)__instance == (Object)null) && IsHoeClearNoDropsHit(hit)) { __state = new DestructibleDropSuppressState { Active = true, SpawnWhenDestroyed = __instance.m_spawnWhenDestroyed }; BeginHoeClearDropSuppress(); __instance.m_spawnWhenDestroyed = null; } } private static void Finalizer(Destructible __instance, DestructibleDropSuppressState __state) { if (__state.Active) { if ((Object)(object)__instance != (Object)null) { __instance.m_spawnWhenDestroyed = __state.SpawnWhenDestroyed; } EndHoeClearDropSuppress(); } } } [HarmonyPatch(typeof(WearNTear), "Destroy", new Type[] { typeof(HitData), typeof(bool) })] private static class WearNTear_Destroy_HoeClearNoDrops_Patch { private static void Prefix(HitData hitData, ref bool blockDrop, out HoeClearDropSuppressState __state) { __state = default(HoeClearDropSuppressState); if (IsHoeClearNoDropsHit(hitData)) { __state = new HoeClearDropSuppressState { Active = true }; BeginHoeClearDropSuppress(); blockDrop = true; } } private static void Finalizer(HoeClearDropSuppressState __state) { if (__state.Active) { EndHoeClearDropSuppress(); } } } [HarmonyPatch(typeof(DropOnDestroyed), "OnDestroyed")] private static class DropOnDestroyed_OnDestroyed_HoeClearNoDrops_Patch { private static bool Prefix() { return !ShouldSuppressHoeClearDrops(); } } [HarmonyPatch(typeof(Player), "PlacePiece")] internal static class PathenShrubClearPatch { private static readonly FieldInfo s_placementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost"); private static readonly int s_shrubMask = LayerMask.GetMask(new string[5] { "Default", "Default_small", "piece", "piece_nonsolid", "static_solid" }); [HarmonyPostfix] private static void Postfix(Player __instance, Piece piece) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) if (!IsFeatureEnabled(EnableCustomHoes)) { ClearPendingClearState(); } else { if ((Object)(object)piece == (Object)null) { return; } if (!IsPathPiece(piece)) { ClearPendingClearState(); return; } object? value = s_placementGhostField.GetValue(__instance); GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val == (Object)null) { return; } ItemData equippedHoeItem = GetEquippedHoeItem(__instance); string customHoePrefabName = GetCustomHoePrefabName(equippedHoeItem); int customHoeClearTargetCap = GetCustomHoeClearTargetCap(equippedHoeItem); if (string.IsNullOrEmpty(customHoePrefabName) || customHoeClearTargetCap <= 0) { ClearPendingClearState(); return; } Vector3 position = val.transform.position; float terrainRadius = GetTerrainRadius(((Component)piece).gameObject); List list = CollectClearCandidates(position, terrainRadius, s_shrubMask); if (list.Count == 0) { ClearPendingClearState(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Clear preview at {position}: no candidate roots found."); } return; } LogClearCatalog(position, terrainRadius, equippedHoeItem, list); List allowedCandidatesOrdered = GetAllowedCandidatesOrdered(list, position, equippedHoeItem); int count = allowedCandidatesOrdered.Count; ClearCandidate clearCandidate = null; ClearCandidate clearCandidate2 = null; ClearCandidate clearCandidate3 = null; foreach (ClearCandidate item in allowedCandidatesOrdered) { if (clearCandidate3 == null) { clearCandidate3 = item; } if (clearCandidate == null && HasEffects(item.HitEffect)) { clearCandidate = item; } if (clearCandidate2 == null && HasEffects(item.DestroyEffect)) { clearCandidate2 = item; } } if (clearCandidate == null) { clearCandidate = clearCandidate2; } if (clearCandidate == null) { clearCandidate = clearCandidate3; } if (count == 0) { ClearPendingClearState(); return; } string pieceName = CleanPrefabName(((Object)piece).name); if (!IsConfirmedClearClick(pieceName, customHoePrefabName, position, terrainRadius)) { StorePendingClearState(pieceName, customHoePrefabName, position, terrainRadius); if (clearCandidate != null) { PlayCandidateEffect(clearCandidate, preferDestroyEffect: false, position); } return; } float customHoeExtraClearStaminaCost = GetCustomHoeExtraClearStaminaCost(equippedHoeItem); if (customHoeExtraClearStaminaCost > 0f && !((Character)__instance).HaveStamina(customHoeExtraClearStaminaCost)) { StorePendingClearState(pieceName, customHoePrefabName, position, terrainRadius); ((Character)__instance).Message((MessageType)2, "Not enough stamina to clear brush.", 0, (Sprite)null); if (clearCandidate != null) { PlayCandidateEffect(clearCandidate, preferDestroyEffect: false, position); } return; } int num = 0; int num2 = Mathf.Min(customHoeClearTargetCap, allowedCandidatesOrdered.Count); for (int i = 0; i < num2; i++) { ClearCandidate candidate = allowedCandidatesOrdered[i]; if (DestroyCandidate(__instance, equippedHoeItem, candidate)) { num++; } } if (allowedCandidatesOrdered.Count > num) { StorePendingClearState(pieceName, customHoePrefabName, position, terrainRadius); } else { ClearPendingClearState(); } if (num > 0) { ApplyExtraClearStamina(__instance, equippedHoeItem); ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[ValheimBB] Cleared {num} brush object(s) at {position} (radius {terrainRadius:F1}) using {customHoePrefabName}."); } } } } private static bool IsPathPiece(Piece piece) { if ((Object)(object)piece == (Object)null) { return false; } string text = CleanPrefabName(((Object)piece).name); if (!text.EndsWith("_clear", StringComparison.OrdinalIgnoreCase)) { return false; } if (!text.StartsWith("path", StringComparison.OrdinalIgnoreCase)) { return text.StartsWith("mud_road", StringComparison.OrdinalIgnoreCase); } return true; } } private sealed class CustomItemDefinition { public string ItemId; public int PersistenceVersion; public string RuntimePrefabName; public string SurrogatePrefabName; public string DisplayName; public string LegacyPrefabMarkerValue; public string LegacyPieceTableName; public string[] SpawnAliases; public Func GetRuntimePrefab; public Action EnsureRuntimePrefabAvailable; } private sealed class SavedCustomItemDropPrefabState { public ItemData Item; public GameObject OriginalDropPrefab; } [HarmonyPatch(typeof(ZNetScene), "GetPrefab", new Type[] { typeof(string) })] public static class ZNetScene_GetPrefab_CustomItemAlias_Patch { private static bool Prefix(ZNetScene __instance, string name, ref GameObject __result) { if (!TryResolveCustomItemPrefab(null, __instance, name, out var prefab)) { return true; } __result = prefab; return false; } } [HarmonyPatch(typeof(ZNetScene), "GetPrefabNames")] public static class ZNetScene_GetPrefabNames_CustomItemAlias_Patch { private static void Postfix(List __result) { if (!IsFeatureEnabled(EnableCustomHoes) || __result == null) { return; } CustomItemDefinition[] s_customItemDefinitions = ValheimBBPlugin.s_customItemDefinitions; foreach (CustomItemDefinition customItemDefinition in s_customItemDefinitions) { if (customItemDefinition == null) { continue; } if (!__result.Contains(customItemDefinition.RuntimePrefabName)) { __result.Add(customItemDefinition.RuntimePrefabName); } if (customItemDefinition.SpawnAliases == null) { continue; } string[] spawnAliases = customItemDefinition.SpawnAliases; foreach (string item in spawnAliases) { if (!__result.Contains(item)) { __result.Add(item); } } } } } [HarmonyPatch(typeof(ItemDrop), "DropItem", new Type[] { typeof(ItemData), typeof(int), typeof(Vector3), typeof(Quaternion) })] public static class ItemDrop_DropItem_CustomItemSurrogate_Patch { private static void Prefix(ref ItemData item) { ItemData val = CreateSurrogateWorldDropItem(item); if (val != null) { item = val; } } } [HarmonyPatch] public static class ItemDrop_LoadFromZDO_CustomItemRepair_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(ItemDrop), "LoadFromZDO", new Type[2] { typeof(ItemData), typeof(ZDO) }, (Type[])null); } private static void Postfix(ItemData itemData) { NormalizeCustomItem(itemData); } } [HarmonyPatch] public static class ItemDrop_LoadIndexedFromZDO_CustomItemRepair_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(ItemDrop), "LoadFromZDO", new Type[3] { typeof(int), typeof(ItemData), typeof(ZDO) }, (Type[])null); } private static void Postfix(ItemData itemData) { NormalizeCustomItem(itemData); } } [HarmonyPatch(typeof(Plant), "GetHoverText")] public static class ValheimBB_PlantHoverTimerPatch { [HarmonyPostfix] private static void AddGrowthTimer(Plant __instance, ref string __result) { if (!EnablePlantGrowthTimer.Value) { return; } try { if (!((Object)(object)__instance == (Object)null)) { string plantGrowthLabel = GetPlantGrowthLabel(__instance); if (!string.IsNullOrEmpty(plantGrowthLabel)) { string text = "(" + plantGrowthLabel + ")"; __result = (string.IsNullOrEmpty(__result) ? text : (__result + "\n" + text)); } } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error adding plant growth timer: {arg}"); } } } } [HarmonyPatch(typeof(Pickable), "GetHoverText")] public static class ValheimBB_PickableBerryBushHoverPatch { [HarmonyPostfix] private static void ShowBerryBushRegrowthState(Pickable __instance, ref string __result) { if (!EnablePlantGrowthTimer.Value || !string.IsNullOrEmpty(__result)) { return; } try { if (!((Object)(object)__instance == (Object)null) && IsRegrowingPickable(__instance) && !(s_pickablePickedField == null) && (bool)s_pickablePickedField.GetValue(__instance)) { string hoverName = __instance.GetHoverName(); if (!string.IsNullOrEmpty(hoverName)) { string text = hoverName + " (" + GetPickableRegrowthLabel(__instance) + ")"; __result = LocalizeHoverText(text); } } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error adding berry bush hover text: {arg}"); } } } } [HarmonyPatch(typeof(ZNetScene), "RemoveObjects")] private static class ZNetScene_RemoveObjects_InvalidInstanceGuard_Patch { private static void Prefix(ZNetScene __instance) { SanitizeTrackedSceneInstances(__instance); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] public static class ZNetScene_Awake_Patch { private const string LegacyPathPrefabName = "path"; private const string LegacyLevelGroundPrefabName = "mud_road"; private const string PathPrefabName = "path_v2"; private const string LevelGroundPrefabName = "mud_road_v2"; private const string LegacyPathClearPrefabName = "path_clear"; private const string LegacyLevelGroundClearPrefabName = "mud_road_clear"; private const string PathClearPrefabName = "path_v2_clear"; private const string LevelGroundClearPrefabName = "mud_road_v2_clear"; private static readonly FieldInfo s_prefabsField = AccessTools.Field(typeof(ZNetScene), "m_prefabs"); private static readonly FieldInfo s_namedPrefabsField = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs"); private static readonly Dictionary s_hoeIconFiles = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "path", "clear_path.png" }, { "path_v2", "clear_path.png" }, { "mud_road", "clear_ground.png" }, { "mud_road_v2", "clear_ground.png" } }; private const string HiddenPrefabContainerName = "_ValheimBB_HiddenPrefabs"; private static readonly Dictionary s_hoeIconCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary s_clearPieceVariantPrefabs = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary s_hoeTablePiecePrefabs = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary s_baseTorchSecPerFuel = new Dictionary(StringComparer.OrdinalIgnoreCase); private static GameObject s_hiddenPrefabContainer; private static void Postfix(ZNetScene __instance) { try { ConfigureTorchFuelSettings(__instance); } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error while tweaking torch fuel settings: {arg}"); } } try { ConfigureStoneOvenDoorPrefab(__instance); } catch (Exception arg2) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"[ValheimBB] Error while tweaking stone oven door behavior: {arg2}"); } } if (IsFeatureEnabled(EnableStandingTorchAndKilnTweaks)) { try { GameObject prefab = __instance.GetPrefab("piece_groundtorch_wood"); if ((Object)(object)prefab == (Object)null) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)"[ValheimBB] Could not find 'piece_groundtorch_wood' in ZNetScene."); } } else { Piece component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)"[ValheimBB] Prefab 'piece_groundtorch_wood' has no Piece component."); } } else { if ((Object)(object)component.m_craftingStation != (Object)null) { ManualLogSource log5 = Log; if (log5 != null) { log5.LogInfo((object)("[ValheimBB] Standing Wood Torch originally required crafting station: " + component.m_craftingStation.m_name + ". Removing requirement.")); } } else { ManualLogSource log6 = Log; if (log6 != null) { log6.LogInfo((object)"[ValheimBB] Standing Wood Torch already had no crafting station set."); } } component.m_craftingStation = null; } } } catch (Exception arg3) { ManualLogSource log7 = Log; if (log7 != null) { log7.LogError((object)$"[ValheimBB] Error while tweaking torch build requirements: {arg3}"); } } } if (IsFeatureEnabled(EnableCustomHoes)) { try { if (!TryMatchTerrainRadius(__instance, "mud_road_v2", "path_v2")) { TryMatchTerrainRadius(__instance, "mud_road", "path"); } } catch (Exception arg4) { ManualLogSource log8 = Log; if (log8 != null) { log8.LogError((object)$"[ValheimBB] Error while widening path radius: {arg4}"); } } try { EnsureFlintHoePrefab(__instance); EnsureBronzeHoePrefab(__instance); ConfigureFlintHoePreviewEffect(__instance); ObjectDB_Awake_Patch.EnsureCustomHoeRegistrations(ObjectDB.instance); } catch (Exception arg5) { ManualLogSource log9 = Log; if (log9 != null) { log9.LogError((object)$"[ValheimBB] Error while creating custom hoe prefabs: {arg5}"); } } } if (!IsFeatureEnabled(EnableStandingTorchAndKilnTweaks)) { return; } try { GameObject prefab2 = __instance.GetPrefab("charcoal_kiln"); if ((Object)(object)prefab2 == (Object)null) { ManualLogSource log10 = Log; if (log10 != null) { log10.LogWarning((object)"[ValheimBB] charcoal_kiln prefab not found."); } return; } Smelter component2 = prefab2.GetComponent(); if ((Object)(object)component2 == (Object)null) { ManualLogSource log11 = Log; if (log11 != null) { log11.LogWarning((object)"[ValheimBB] Smelter component missing on charcoal_kiln prefab."); } return; } if (component2.m_conversion == null) { ManualLogSource log12 = Log; if (log12 != null) { log12.LogWarning((object)"[ValheimBB] charcoal_kiln Smelter has no conversion list."); } return; } int count = component2.m_conversion.Count; component2.m_conversion.RemoveAll(delegate(ItemConversion conv) { if (conv == null || (Object)(object)conv.m_from == (Object)null) { return false; } GameObject gameObject = ((Component)conv.m_from).gameObject; string obj = (((Object)(object)gameObject != (Object)null) ? ((Object)gameObject).name : string.Empty); string text = conv.m_from.m_itemData?.m_shared?.m_name ?? string.Empty; bool num2 = obj.Equals("FineWood", StringComparison.OrdinalIgnoreCase); bool flag = text.Equals("$item_finewood", StringComparison.OrdinalIgnoreCase); return num2 || flag; }); int count2 = component2.m_conversion.Count; int num = count - count2; ManualLogSource log13 = Log; if (log13 != null) { log13.LogInfo((object)$"[ValheimBB] charcoal_kiln conversion entries: {count} -> {count2} (removed {num} FineWood entries)."); } } catch (Exception arg6) { ManualLogSource log14 = Log; if (log14 != null) { log14.LogError((object)$"[ValheimBB] Error while tweaking charcoal_kiln conversions: {arg6}"); } } } private static void ConfigureTorchFuelSettings(ZNetScene scene) { if ((Object)(object)scene == (Object)null) { return; } if (!(s_prefabsField?.GetValue(scene) is List list) || list.Count == 0) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Could not read ZNetScene prefab list while syncing torch and brazier fuel settings."); } return; } int num = 0; foreach (GameObject item in list) { if (!TryGetTorchOrBrazierFireplace(item, out var fireplace)) { continue; } string text = CleanPrefabName(((Object)item).name); if (!s_baseTorchSecPerFuel.TryGetValue(text, out var value)) { value = fireplace.m_secPerFuel; s_baseTorchSecPerFuel[text] = value; } float secPerFuel = value * 2f; float secPerFuel2 = fireplace.m_secPerFuel; float maxFuel = fireplace.m_maxFuel; fireplace.m_secPerFuel = secPerFuel; if (string.Equals(text, "piece_groundtorch_wood", StringComparison.OrdinalIgnoreCase) && IsFeatureEnabled(EnableStandingTorchAndKilnTweaks)) { fireplace.m_maxFuel = 5f; } if (!Mathf.Approximately(secPerFuel2, fireplace.m_secPerFuel) || !Mathf.Approximately(maxFuel, fireplace.m_maxFuel)) { num++; ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[ValheimBB] Fuel updated for '{text}': max fuel {maxFuel:0.#} -> {fireplace.m_maxFuel:0.#}, sec/fuel {secPerFuel2:0.#} -> {fireplace.m_secPerFuel:0.#}."); } } } ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)$"[ValheimBB] Updated burn time for {num} torch/brazier prefabs."); } } private static bool TryGetTorchOrBrazierFireplace(GameObject prefab, out Fireplace fireplace) { fireplace = ((prefab != null) ? prefab.GetComponent() : null); if ((Object)(object)fireplace == (Object)null || (Object)(object)prefab.GetComponent() == (Object)null) { return false; } string text = CleanPrefabName(((Object)prefab).name); if (string.IsNullOrWhiteSpace(text)) { return false; } if (text.IndexOf("torch", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("brazier", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("sconce", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static void EnsureFlintHoePrefab(ZNetScene scene) { GameObject prefab = scene.GetPrefab("Hoe"); if ((Object)(object)prefab == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Hoe prefab not found."); } return; } ItemDrop component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)"[ValheimBB] ItemDrop missing on Hoe prefab."); } return; } component.m_itemData.m_shared.m_name = "Stone Hoe"; if ((Object)(object)FlintHoePrefab == (Object)null) { FlintHoePrefab = CreateCustomHoePrefab(prefab, "HoeFlint"); } ParkCloneAsHiddenPrefab(FlintHoePrefab); RegisterPrefabWithScene(scene, FlintHoePrefab); ConfigureFlintHoePrefab(component, FlintHoePrefab, scene); } internal static void EnsureBronzeHoePrefab(ZNetScene scene) { GameObject prefab = scene.GetPrefab("Hoe"); if ((Object)(object)prefab == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Hoe prefab not found."); } return; } ItemDrop component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)"[ValheimBB] ItemDrop missing on Hoe prefab."); } return; } component.m_itemData.m_shared.m_name = "Stone Hoe"; if ((Object)(object)BronzeHoePrefab == (Object)null) { BronzeHoePrefab = CreateCustomHoePrefab(prefab, "HoeBronze"); } ParkCloneAsHiddenPrefab(BronzeHoePrefab); RegisterPrefabWithScene(scene, BronzeHoePrefab); ConfigureBronzeHoePrefab(component, BronzeHoePrefab, scene); } private static GameObject CreateCustomHoePrefab(GameObject originalHoePrefab, string prefabName) { bool activeSelf = originalHoePrefab.activeSelf; originalHoePrefab.SetActive(false); GameObject obj = Object.Instantiate(originalHoePrefab); originalHoePrefab.SetActive(activeSelf); ((Object)obj).name = prefabName; obj.SetActive(false); Object.DontDestroyOnLoad((Object)(object)obj); return obj; } private static void ConfigureFlintHoePreviewEffect(ZNetScene scene) { if (!((Object)(object)scene == (Object)null) && !TrySetFlintHoePreviewEffect(scene, "Beech1") && !TrySetFlintHoePreviewEffect(scene, "FirTree_oldLog") && !TrySetFlintHoePreviewEffect(scene, "Beech_small1") && !TrySetFlintHoePreviewEffect(scene, "Beech_small2")) { FlintHoePreviewEffect = null; FlintHoePreviewEffectSource = null; ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Could not resolve a fixed Flint Hoe preview effect."); } } } private static bool TrySetFlintHoePreviewEffect(ZNetScene scene, string prefabName) { GameObject prefab = scene.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { return false; } EffectList val = null; TreeBase component = prefab.GetComponent(); if ((Object)(object)component != (Object)null && HasEffects(component.m_hitEffect)) { val = component.m_hitEffect; } if (!HasEffects(val)) { TreeLog component2 = prefab.GetComponent(); if ((Object)(object)component2 != (Object)null && HasEffects(component2.m_hitEffect)) { val = component2.m_hitEffect; } } if (!HasEffects(val)) { Destructible component3 = prefab.GetComponent(); if ((Object)(object)component3 != (Object)null && HasEffects(component3.m_hitEffect)) { val = component3.m_hitEffect; } } if (!HasEffects(val)) { WearNTear component4 = prefab.GetComponent(); if ((Object)(object)component4 != (Object)null && HasEffects(component4.m_hitEffect)) { val = component4.m_hitEffect; } } if (!HasEffects(val)) { return false; } FlintHoePreviewEffect = val; FlintHoePreviewEffectSource = prefabName; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ValheimBB] Flint Hoe preview effect source: " + prefabName)); } return true; } private static void ConfigureFlintHoePrefab(ItemDrop originalHoeItem, GameObject flintHoePrefab, ZNetScene scene) { ConfigureCustomHoePrefab(originalHoeItem, flintHoePrefab, scene, "Flint Hoe", "A flint-edged hoe for wider roadwork and brush clearing.", "HoeFlint_PieceTable", ref FlintHoeBaseStaminaCost, ref FlintHoeExtraClearStaminaCost); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Flint Hoe ready. Terrain cost={FlintHoeBaseStaminaCost:F1}, clear surcharge={FlintHoeExtraClearStaminaCost:F1}."); } } private static void ConfigureBronzeHoePrefab(ItemDrop originalHoeItem, GameObject bronzeHoePrefab, ZNetScene scene) { ConfigureCustomHoePrefab(originalHoeItem, bronzeHoePrefab, scene, "Bronze Hoe", "A bronze hoe that clears more brush with each swing. Can also clear dead tree logs and stumps.", "HoeBronze_PieceTable", ref BronzeHoeBaseStaminaCost, ref BronzeHoeExtraClearStaminaCost); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Bronze Hoe ready. Terrain cost={BronzeHoeBaseStaminaCost:F1}, clear surcharge={BronzeHoeExtraClearStaminaCost:F1}."); } } private static void ConfigureCustomHoePrefab(ItemDrop originalHoeItem, GameObject customHoePrefab, ZNetScene scene, string displayName, string description, string pieceTableName, ref float baseStaminaCost, ref float extraClearStaminaCost) { ItemDrop component = customHoePrefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] ItemDrop missing on " + displayName + " prefab.")); } return; } component.m_itemData.m_dropPrefab = customHoePrefab; component.m_itemData.m_shared.m_name = displayName; component.m_itemData.m_shared.m_description = description; float num = originalHoeItem.m_itemData.m_shared.m_attack?.m_attackStamina ?? 5f; baseStaminaCost = num * 0.2f; extraClearStaminaCost = Mathf.Max(0f, num * 0.5f - baseStaminaCost); if (component.m_itemData.m_shared.m_attack != null) { component.m_itemData.m_shared.m_attack.m_attackStamina = baseStaminaCost; } PieceTable val = EnsureClonedPieceTable(originalHoeItem, component, pieceTableName); if ((Object)(object)val == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] " + displayName + " has no build piece table.")); } return; } MatchTerrainRadius(val, scene, "mud_road_v2", "path_v2"); MatchTerrainRadius(val, scene, "mud_road", "path"); EnsureLegacyClearPieceCompatibility(scene); RemovePiecesFromTable(val, "mud_road_clear", "path_clear", "mud_road_v2_clear", "path_v2_clear"); ClonePieceVariant(scene, val, "mud_road_v2_clear", "Level Ground (Clear Brush)", "mud_road_v2", "mud_road"); ClonePieceVariant(scene, val, "path_v2_clear", "Pathen (Clear Brush)", "path_v2", "path"); if (IsFeatureEnabled(EnableStandingTorchAndKilnTweaks)) { AddExistingPieceToTable(scene, val, "piece_groundtorch_wood"); } } private static PieceTable EnsureClonedPieceTable(ItemDrop originalHoeItem, ItemDrop customHoeItem, string pieceTableName) { PieceTable val = customHoeItem.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null && ((Object)((Component)val).gameObject).name == pieceTableName) { return val; } PieceTable val2 = originalHoeItem.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null) { return null; } GameObject obj = Object.Instantiate(((Component)val2).gameObject); ((Object)obj).name = pieceTableName; Object.DontDestroyOnLoad((Object)(object)obj); PieceTable component = obj.GetComponent(); customHoeItem.m_itemData.m_shared.m_buildPieces = component; return component; } private static Sprite LoadHoeIconOverride(string originalName) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(originalName)) { return null; } if (s_hoeIconCache.TryGetValue(originalName, out var value)) { return value; } if (!s_hoeIconFiles.TryGetValue(originalName, out var value2)) { return null; } string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (string.IsNullOrEmpty(directoryName)) { return null; } string text = Path.Combine(directoryName, "Icons", "hoe", value2); if (!File.Exists(text)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Hoe icon override not found: " + text)); } return null; } try { Texture2D val = LoadTextureFromPng(text); if ((Object)(object)val == (Object)null) { return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = ((Object)val).name; s_hoeIconCache[originalName] = val2; ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)("[ValheimBB] Loaded hoe icon override '" + value2 + "'.")); } return val2; } catch (Exception arg) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogError((object)$"[ValheimBB] Error loading hoe icon override '{text}': {arg}"); } return null; } } private static Texture2D LoadTextureFromPng(string iconPath) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) Bitmap val = new Bitmap(iconPath); try { Texture2D val2 = new Texture2D(((Image)val).Width, ((Image)val).Height, (TextureFormat)5, false); Color32[] array = (Color32[])(object)new Color32[((Image)val).Width * ((Image)val).Height]; for (int i = 0; i < ((Image)val).Height; i++) { for (int j = 0; j < ((Image)val).Width; j++) { Color pixel = val.GetPixel(j, ((Image)val).Height - 1 - i); array[i * ((Image)val).Width + j] = new Color32(pixel.R, pixel.G, pixel.B, pixel.A); } } val2.SetPixels32(array); val2.Apply(false, false); ((Object)val2).name = Path.GetFileNameWithoutExtension(iconPath); return val2; } finally { ((IDisposable)val)?.Dispose(); } } private static void ClonePieceVariant(ZNetScene scene, PieceTable table, string newName, string displayToken, params string[] sourcePrefabNames) { foreach (GameObject piece in table.m_pieces) { if ((Object)(object)piece != (Object)null && ((Object)piece).name == newName) { return; } } GameObject originalPrefab = ResolveTerrainPiecePrefab(table, scene, sourcePrefabNames); GameObject orCreateClearPieceVariant = GetOrCreateClearPieceVariant(scene, originalPrefab, newName, displayToken); if (!((Object)(object)orCreateClearPieceVariant == (Object)null)) { table.m_pieces.Add(orCreateClearPieceVariant); } } private static void EnsureLegacyClearPieceCompatibility(ZNetScene scene) { GameObject originalPrefab = ResolveTerrainPiecePrefab(null, scene, "mud_road"); GameObject originalPrefab2 = ResolveTerrainPiecePrefab(null, scene, "path"); GetOrCreateClearPieceVariant(scene, originalPrefab, "mud_road_clear", "Level Ground (Clear Brush)"); GetOrCreateClearPieceVariant(scene, originalPrefab2, "path_clear", "Pathen (Clear Brush)"); } private static void RemovePiecesFromTable(PieceTable table, params string[] prefabNames) { if (table?.m_pieces != null && prefabNames != null && prefabNames.Length != 0) { HashSet names = new HashSet(prefabNames, StringComparer.OrdinalIgnoreCase); table.m_pieces.RemoveAll((GameObject piece) => (Object)(object)piece != (Object)null && names.Contains(CleanPrefabName(((Object)piece).name))); } } private static GameObject ResolveTerrainPiecePrefab(PieceTable table, ZNetScene scene, params string[] prefabNames) { if (prefabNames == null || prefabNames.Length == 0) { return null; } if (table?.m_pieces != null) { string[] array = prefabNames; foreach (string text in array) { if (string.IsNullOrWhiteSpace(text)) { continue; } foreach (GameObject piece in table.m_pieces) { if (!((Object)(object)piece == (Object)null) && string.Equals(CleanPrefabName(((Object)piece).name), text, StringComparison.OrdinalIgnoreCase)) { return piece; } } } } if ((Object)(object)scene != (Object)null) { string[] array = prefabNames; foreach (string text2 in array) { if (!string.IsNullOrWhiteSpace(text2)) { GameObject prefab = scene.GetPrefab(text2); if ((Object)(object)prefab != (Object)null) { return prefab; } } } } return null; } private static GameObject GetOrCreateClearPieceVariant(ZNetScene scene, GameObject originalPrefab, string newName, string displayToken) { if ((Object)(object)scene == (Object)null || (Object)(object)originalPrefab == (Object)null || string.IsNullOrWhiteSpace(newName)) { return null; } if (s_clearPieceVariantPrefabs.TryGetValue(newName, out var value) && (Object)(object)value != (Object)null) { RegisterPrefabWithScene(scene, value); return value; } string text = CleanPrefabName(((Object)originalPrefab).name); bool activeSelf = originalPrefab.activeSelf; originalPrefab.SetActive(false); GameObject val = Object.Instantiate(originalPrefab); originalPrefab.SetActive(activeSelf); ((Object)val).name = newName; ParkCloneAsHiddenPrefab(val); Piece component = val.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_name = displayToken; component.m_description = "First click previews brush clearing. Click again to clear."; Sprite val2 = LoadHoeIconOverride(text); if ((Object)(object)val2 != (Object)null) { component.m_icon = val2; } } RegisterPrefabWithScene(scene, val); s_clearPieceVariantPrefabs[newName] = val; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ValheimBB] Cloned '" + text + "' -> '" + newName + "'")); } return val; } private static bool TryMatchTerrainRadius(ZNetScene scene, string levelGroundPrefabName, string pathPrefabName) { GameObject val = ((scene != null) ? scene.GetPrefab(levelGroundPrefabName) : null); GameObject val2 = ((scene != null) ? scene.GetPrefab(pathPrefabName) : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return false; } float terrainRadius = GetTerrainRadius(val); float terrainRadius2 = GetTerrainRadius(val2); SetTerrainRadius(val2, terrainRadius); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Widened '{pathPrefabName}' radius from {terrainRadius2:F1} to {terrainRadius:F1} to match '{levelGroundPrefabName}'."); } return true; } private static bool MatchTerrainRadius(PieceTable table, ZNetScene scene, string levelGroundPrefabName, string pathPrefabName) { GameObject val = ResolveTerrainPiecePrefab(table, scene, levelGroundPrefabName); GameObject val2 = ResolveTerrainPiecePrefab(table, scene, pathPrefabName); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return false; } float terrainRadius = GetTerrainRadius(val); float terrainRadius2 = GetTerrainRadius(val2); SetTerrainRadius(val2, terrainRadius); if (!Mathf.Approximately(terrainRadius2, terrainRadius)) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Widened '{CleanPrefabName(((Object)val2).name)}' radius from {terrainRadius2:F1} to {terrainRadius:F1} to match '{CleanPrefabName(((Object)val).name)}'."); } } return true; } private static void RegisterPrefabWithScene(ZNetScene scene, GameObject prefab) { if (!((Object)(object)scene == (Object)null) && !((Object)(object)prefab == (Object)null)) { if (s_prefabsField?.GetValue(scene) is List list && !list.Contains(prefab)) { list.Add(prefab); } if (s_namedPrefabsField?.GetValue(scene) is Dictionary dictionary) { dictionary[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = prefab; } } } private static void AddExistingPieceToTable(ZNetScene scene, PieceTable table, string prefabName) { //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)scene == (Object)null || (Object)(object)table == (Object)null || string.IsNullOrWhiteSpace(prefabName)) { return; } string text = (string.Equals(prefabName, "piece_groundtorch_wood", StringComparison.OrdinalIgnoreCase) ? (prefabName + "_hoe") : prefabName); foreach (GameObject piece in table.m_pieces) { if (!((Object)(object)piece == (Object)null) && string.Equals(CleanPrefabName(((Object)piece).name), text, StringComparison.OrdinalIgnoreCase)) { return; } } GameObject val = scene.GetPrefab(prefabName); if ((Object)(object)val == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not find '" + prefabName + "' in ZNetScene")); } return; } if (string.Equals(prefabName, "piece_groundtorch_wood", StringComparison.OrdinalIgnoreCase)) { val = GetOrCreateHoeTablePiecePrefab(scene, val, text); if ((Object)(object)val == (Object)null) { return; } } Piece component = val.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Prefab '" + prefabName + "' has no Piece component.")); } return; } if (string.Equals(prefabName, "piece_groundtorch_wood", StringComparison.OrdinalIgnoreCase)) { PieceCategory category = ResolvePrimaryPieceCategory(table); component.m_craftingStation = null; component.m_category = category; } table.m_pieces.Add(val); ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)("[ValheimBB] Added '" + prefabName + "' to piece table '" + ((Object)table).name + "'.")); } } private static GameObject GetOrCreateHoeTablePiecePrefab(ZNetScene scene, GameObject originalPrefab, string cloneName) { if ((Object)(object)scene == (Object)null || (Object)(object)originalPrefab == (Object)null || string.IsNullOrWhiteSpace(cloneName)) { return null; } if (s_hoeTablePiecePrefabs.TryGetValue(cloneName, out var value) && (Object)(object)value != (Object)null) { RegisterPrefabWithScene(scene, value); return value; } bool activeSelf = originalPrefab.activeSelf; originalPrefab.SetActive(false); GameObject val = Object.Instantiate(originalPrefab); originalPrefab.SetActive(activeSelf); ((Object)val).name = cloneName; ParkCloneAsHiddenPrefab(val); RegisterPrefabWithScene(scene, val); s_hoeTablePiecePrefabs[cloneName] = val; return val; } private static void ParkCloneAsHiddenPrefab(GameObject clone) { if ((Object)(object)clone == (Object)null) { return; } GameObject orCreateHiddenPrefabContainer = GetOrCreateHiddenPrefabContainer(); if (!((Object)(object)orCreateHiddenPrefabContainer == (Object)null)) { ((Object)clone).hideFlags = (HideFlags)1; if ((Object)(object)clone.transform.parent != (Object)(object)orCreateHiddenPrefabContainer.transform) { clone.transform.SetParent(orCreateHiddenPrefabContainer.transform, false); } if (!clone.activeSelf) { clone.SetActive(true); } } } private static GameObject GetOrCreateHiddenPrefabContainer() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)s_hiddenPrefabContainer != (Object)null) { return s_hiddenPrefabContainer; } s_hiddenPrefabContainer = new GameObject("_ValheimBB_HiddenPrefabs"); s_hiddenPrefabContainer.SetActive(false); ((Object)s_hiddenPrefabContainer).hideFlags = (HideFlags)1; Object.DontDestroyOnLoad((Object)(object)s_hiddenPrefabContainer); return s_hiddenPrefabContainer; } private static PieceCategory ResolvePrimaryPieceCategory(PieceTable table) { //IL_003d: 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_0060: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)table != (Object)null && table.m_pieces != null) { foreach (GameObject piece in table.m_pieces) { Piece val = ((piece != null) ? piece.GetComponent() : null); if ((Object)(object)val != (Object)null) { return val.m_category; } } } return (PieceCategory)0; } } [HarmonyPatch(typeof(FejdStartup), "Start")] private static class FejdStartup_Start_MainMenuVersionLabel_Patch { private static void Postfix(FejdStartup __instance) { AppendMainMenuVersion(__instance); } } private sealed class ModMenuSection { public string Header; public List Options; } private sealed class ModMenuOption { public string Label; public bool RequiresReload; public Func GetValueText; public Action CycleValue; } [HarmonyPatch(typeof(FejdStartup), "Start")] private static class FejdStartup_Start_ModOptionsMenu_Patch { private static void Postfix(FejdStartup __instance) { EnsureMainMenuOptionsButton(__instance); } } [HarmonyPatch(typeof(Menu), "Show")] private static class Menu_Show_ModOptionsMenu_Patch { private static void Postfix(Menu __instance) { EnsureInGameOptionsButton(__instance); } } [HarmonyPatch(typeof(Menu), "Start")] private static class Menu_Start_ModOptionsMenu_Patch { private static void Postfix(Menu __instance) { EnsureInGameOptionsButton(__instance); } } [HarmonyPatch(typeof(Menu), "OnClose")] private static class Menu_OnClose_ModOptionsMenu_Patch { private static bool Prefix() { return !CloseActiveModOptionsPanel(); } } [HarmonyPatch(typeof(Menu), "Update")] private static class Menu_Update_ModOptionsMenu_Patch { private static bool Prefix() { if (!HasActiveModOptionsPanel()) { return true; } if (Input.GetKeyDown((KeyCode)27)) { CloseActiveModOptionsPanel(); return false; } return true; } } [HarmonyPatch(typeof(Menu), "Hide")] private static class Menu_Hide_ModOptionsMenu_Patch { private static bool Prefix() { return !CloseActiveModOptionsPanel(); } } private struct SectionHeaderControls { public Button Button; public TMP_Text Label; } [HarmonyPatch(typeof(ZoneSystem), "GetLocationIcons")] public static class ZoneSystem_GetLocationIcons_Log_Patch { private static readonly HashSet s_loggedLocationIconPositions = new HashSet(); private static void Postfix(Dictionary icons) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) try { if (!IsFeatureEnabled(EnableHaldorSense)) { return; } EnsureHaldorSenseWorldState(); if (icons == null) { return; } foreach (KeyValuePair icon in icons) { if (s_loggedLocationIconPositions.Add(icon.Key)) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] GetLocationIcons: pos={icon.Key} name='{icon.Value}'"); } } if (!(icon.Value == "Vendor_BlackForest")) { continue; } bool num = !s_haldorSpawned; Vector3 key = icon.Key; s_haldorSpawned = true; if (num) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)($"[ValheimBB] GetLocationIcons saw Vendor_BlackForest at {key} " + "(Haldor map icon present; firstThisSession=True).")); } } if ((Object)(object)Player.m_localPlayer == (Object)null) { continue; } if (!HasDiscoveredHaldorThisWorld()) { MarkHaldorDiscoveredThisWorld(); } if (!HasShownHaldorBannerThisWorld()) { Vector3 position = ((Component)Player.m_localPlayer).transform.position; position.y = 0f; bool flag = ShowHaldorBiomeBanner(position, key); ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)$"[ValheimBB] Haldor mystical reveal biome-style popup via GetLocationIcons shown={flag}."); } if (flag) { MarkHaldorBannerShown(); } } } } catch (Exception arg) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogError((object)$"[ValheimBB] Error in ZoneSystem.GetLocationIcons logging/trigger patch: {arg}"); } } } } private sealed class ClearCandidate { public GameObject Root; public string RootName; public string ComponentType; public EffectList HitEffect; public EffectList DestroyEffect; public int MinToolTier; } private struct PendingClearState { public bool Active; public Vector3 Center; public float Radius; public float Time; public string PieceName; public string HoePrefabName; } private sealed class NamedAudioClip { public string FileName; public AudioClip Clip; } private struct PickaxeBedrockCandidate { public bool Active; public Player Player; public Vector3 Position; public float ExpiresAt; } [CompilerGenerated] private sealed class <>c__DisplayClass73_0 { public List destination; public Action <>9__0; internal void b__0(AudioClip clip) { destination.Add(clip); } } [CompilerGenerated] private sealed class d__365 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delay; public int key; public ProximityState state; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__365(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; s_stoneOvenDoorCloseCoroutines.Remove(key); CloseStoneOvenDoorIfStillEmpty(state); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__73 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public List destination; public IEnumerable fileNames; public ValheimBBPlugin <>4__this; private <>c__DisplayClass73_0 <>8__1; public string label; private IEnumerator <>7__wrap1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__73(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>8__1 = null; <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { int num = <>1__state; ValheimBBPlugin valheimBBPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass73_0(); <>8__1.destination = destination; <>7__wrap1 = fileNames.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { string current = <>7__wrap1.Current; <>2__current = valheimBBPlugin.LoadSingleAudioClip(current, delegate(AudioClip clip) { <>8__1.destination.Add(clip); }, label); <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__72 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ValheimBBPlugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__72(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; ValheimBBPlugin valheimBBPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; s_localPlayerDeathClip = null; s_maleFallDamageClips.Clear(); s_femaleFallDamageClips.Clear(); <>2__current = valheimBBPlugin.LoadSingleAudioClip(s_localPlayerDeathClipFileName, delegate(AudioClip clip) { s_localPlayerDeathClip = clip; }, "death"); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = valheimBBPlugin.LoadFallDamageAudioClipSet(s_maleFallDamageClipFileNames, s_maleFallDamageClips, "male"); <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = valheimBBPlugin.LoadFallDamageAudioClipSet(s_femaleFallDamageClipFileNames, s_femaleFallDamageClips, "female"); <>1__state = 3; return true; case 3: { <>1__state = -1; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[ValheimBB] Loaded death clip: {0}; {1} male and {2} female fall grunt clip(s).", ((Object)(object)s_localPlayerDeathClip != (Object)null) ? "yes" : "no", s_maleFallDamageClips.Count, s_femaleFallDamageClips.Count)); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__88 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string filePath; private UnityWebRequest 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__88(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Invalid comparison between Unknown and I4 bool result; try { switch (<>1__state) { default: result = false; break; case 0: <>1__state = -1; if (!File.Exists(filePath)) { result = false; break; } 5__2 = UnityWebRequestMultimedia.GetAudioClip(new Uri(filePath).AbsoluteUri, (AudioType)13); <>1__state = -3; <>2__current = 5__2.SendWebRequest(); <>1__state = 1; result = true; break; case 1: <>1__state = -3; if ((int)5__2.result != 1) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("[ValheimBB] Failed loading pickaxe bedrock clip '" + filePath + "': " + 5__2.error)); } result = false; } else { AudioClip content = DownloadHandlerAudioClip.GetContent(5__2); if (!((Object)(object)content == (Object)null)) { ((Object)content).name = Path.GetFileNameWithoutExtension(filePath); s_pickaxeBedrockClip = new NamedAudioClip { FileName = Path.GetFileName(filePath), Clip = content }; <>m__Finally1(); 5__2 = null; result = false; break; } ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Pickaxe bedrock clip was null: " + filePath)); } result = false; } <>m__Finally1(); break; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (5__2 != null) { ((IDisposable)5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__87 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ValheimBBPlugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__87(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; ValheimBBPlugin valheimBBPlugin = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; s_pickaxeBedrockClip = null; string directoryName = Path.GetDirectoryName(typeof(ValheimBBPlugin).Assembly.Location); if (string.IsNullOrEmpty(directoryName)) { return false; } string text = Path.Combine(directoryName, "Assets"); if (!Directory.Exists(text)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Missing pickaxe bedrock audio directory: " + text)); } return false; } string[] files = Directory.GetFiles(text, "metal-hit-55.mp3", SearchOption.AllDirectories); Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); if (files.Length == 0) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)"[ValheimBB] Missing pickaxe bedrock clip: metal-hit-55.mp3"); } return false; } <>2__current = valheimBBPlugin.LoadPickaxeBedrockAudioClip(files[0]); <>1__state = 1; return true; } case 1: { <>1__state = -1; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ValheimBB] Loaded pickaxe bedrock clip: " + ((s_pickaxeBedrockClip != null) ? s_pickaxeBedrockClip.FileName : "none") + ".")); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__74 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string fileName; public string label; public Action onLoaded; private string 5__2; private UnityWebRequest 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__74(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Invalid comparison between Unknown and I4 bool result; try { switch (<>1__state) { default: result = false; break; case 0: { <>1__state = -1; string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (string.IsNullOrEmpty(directoryName)) { result = false; break; } 5__2 = Path.Combine(directoryName, fileName); if (!File.Exists(5__2)) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)("[ValheimBB] Missing " + label + " audio clip: " + 5__2)); } result = false; } else { 5__3 = UnityWebRequestMultimedia.GetAudioClip(new Uri(5__2).AbsoluteUri, (AudioType)20); <>1__state = -3; <>2__current = 5__3.SendWebRequest(); <>1__state = 1; result = true; } break; } case 1: <>1__state = -3; if ((int)5__3.result != 1) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("[ValheimBB] Failed loading audio clip '" + 5__2 + "': " + 5__3.error)); } result = false; } else { AudioClip content = DownloadHandlerAudioClip.GetContent(5__3); if (!((Object)(object)content == (Object)null)) { ((Object)content).name = Path.GetFileNameWithoutExtension(fileName); onLoaded?.Invoke(content); <>m__Finally1(); 5__3 = null; result = false; break; } ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Loaded audio clip was null: " + 5__2)); } result = false; } <>m__Finally1(); break; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (5__3 != null) { ((IDisposable)5__3).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static ConfigEntry EnableStandingTorchAndKilnTweaks; internal static ConfigEntry EnableBuildAndRecipeUnlockMarkers; internal static ConfigEntry EnableNightPopup; internal static ConfigEntry EnableHaldorSense; internal static ConfigEntry EnableCrossbowLoadedPersistence; internal static ConfigEntry EnablePickaxeStaminaTweaks; internal static ConfigEntry EnablePickaxeDirtOnlyRefund; internal static ConfigEntry EnablePickaxeBedrockAudio; internal static ConfigEntry EnableCustomHoes; internal static ConfigEntry EnableCraftingSortToggle; internal static ConfigEntry EnablePlantGrowthTimer; internal static ConfigEntry EnableWisplightAutoRetract; internal static ConfigEntry EnableStoneOvenDoorTweaks; internal static ConfigEntry EnableMaceKnockbackTweaks; internal static ConfigEntry EnableCreaturePushbackScaling; internal static ConfigEntry EnableThrownSpearPickupProtection; internal static ConfigEntry EnableAmmoRecipeBatchTweaks; internal static ConfigEntry EnableBearHeaddressRecipeTweak; internal static ConfigEntry EnableMuckshakeFoodTweak; internal static ConfigEntry EnableNoMapBiomeLabel; internal static ConfigEntry EnableRestingTweaks; internal static ConfigEntry EnablePlayerDamageGruntSfx; internal static ConfigEntry EnablePlayerDeathSfx; internal static ConfigEntry WisplightOutsideMistlandsBehavior; internal static ConfigEntry HaldorSenseRadius; internal static ConfigEntry PickaxeBedrockAudioVolume; internal static ConfigEntry HoeClearConfirmWindowSeconds; internal static ConfigEntry FlintHoeClearTargetCapConfig; internal static ConfigEntry BronzeHoeClearTargetCapConfig; internal static ConfigEntry PickaxeDirtOnlyRefundFraction; internal static ConfigEntry StoneOvenDoorTriggerRadius; internal static ConfigEntry StoneOvenDoorCloseDelaySeconds; private static readonly FieldInfo s_attackWeaponField = AccessTools.Field(typeof(Attack), "m_weapon"); private static readonly MethodInfo s_effectListCreateMethod = AccessTools.Method(typeof(EffectList), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) }, (Type[])null); private static readonly MethodInfo s_componentTransformGetter = AccessTools.PropertyGetter(typeof(Component), "transform"); private static readonly Dictionary s_deerOriginalIdleSounds = new Dictionary(); [ThreadStatic] private static int s_localPickaxeVegetationAudioDepth; [ThreadStatic] private static HashSet s_localPickaxeVegetationAudioTargets; private bool _pinHelpShown; private const string TutorialTypeName = "Tutorial"; private const string TutorialTextTypeName = "TutorialText"; private const string TutorialTextsFieldName = "m_texts"; private const string TutorialKnownTextsFieldName = "m_knownTexts"; private const string CompendiumAssetsFolderName = "Assets"; private const string CompendiumMarkdownFolderName = "Compendium"; private const string ValheimBBCompendiumMarkdownFileName = "valheimbutbetter.md"; private static readonly BindingFlags s_nonPublicInstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly BindingFlags s_nonPublicTypeFlags = BindingFlags.Public | BindingFlags.NonPublic; private static readonly ValheimBBCompendiumEntry[] s_valheimBBCompendiumEntries = new ValheimBBCompendiumEntry[1] { new ValheimBBCompendiumEntry("valheimbutbetter", "ValheimButBetter", "ValheimButBetter", LoadValheimBBCompendiumText) }; private static bool s_loggedTutorialApiWarning; private static bool s_loggedCompendiumInjection; private const int AmmoRecipeBatchTargetAmount = 100; private const int AmmoRecipeBatchScaleFactor = 5; private static readonly AmmoRecipeBatchTweakDefinition[] s_ammoRecipeBatchTweakDefinitions = new AmmoRecipeBatchTweakDefinition[6] { CreateAmmoRecipeBatchTweakDefinition("Recipe_ArrowBronze", "ArrowBronze", new string[1] { "Bronze" }, new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Bronze", 1), new AmmoRequirementBaseline("Feathers", 2)), CreateAmmoRecipeBatchTweakDefinition("Recipe_ArrowIron", "ArrowIron", new string[1] { "Iron" }, new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Iron", 1), new AmmoRequirementBaseline("Feathers", 2)), CreateAmmoRecipeBatchTweakDefinition("Recipe_ArrowSilver", "ArrowSilver", new string[1] { "Silver" }, new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Silver", 1), new AmmoRequirementBaseline("Feathers", 2)), CreateAmmoRecipeBatchTweakDefinition("Recipe_BoltIron", "BoltIron", new string[1] { "Iron" }, new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Iron", 1), new AmmoRequirementBaseline("Feathers", 2)), CreateAmmoRecipeBatchTweakDefinition("Recipe_BoltBlackmetal", "BoltBlackmetal", new string[1] { "BlackMetal" }, new AmmoRequirementBaseline("BlackMetal", 2), new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Feathers", 2)), CreateAmmoRecipeBatchTweakDefinition("Recipe_ArrowFrost", "ArrowFrost", new string[1] { "FreezeGland" }, new AmmoRequirementBaseline("Wood", 8), new AmmoRequirementBaseline("Obsidian", 4), new AmmoRequirementBaseline("Feathers", 2), new AmmoRequirementBaseline("FreezeGland", 1)) }; private const string BearHeaddressRecipeName = "Recipe_HelmetBerserker"; private const string BearHeaddressOutputPrefabName = "HelmetBerserker"; private static readonly HashSet s_bearHeaddressNormalizedRequirementNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "TrophyBjorn", "BoneFragments", "LeatherScraps" }; private const float CreaturePushbackEffectiveMassMultiplier = 0.5f; private const string PersistentLoadedCrossbowDataKey = "ValheimBB.PersistentLoadedCrossbow"; [ThreadStatic] private static Player s_loadedWeaponPreservePlayer; [ThreadStatic] private static ItemData s_loadedWeaponPreserveItem; private static readonly FieldRef s_playerLoadedWeaponRef = AccessTools.FieldRefAccess("m_weaponLoaded"); private static readonly MethodInfo s_playerSetWeaponLoadedMethod = AccessTools.Method(typeof(Player), "SetWeaponLoaded", (Type[])null, (Type[])null); private static readonly MethodInfo s_playerQueueReloadActionMethod = AccessTools.Method(typeof(Player), "QueueReloadAction", (Type[])null, (Type[])null); private static readonly MethodInfo s_playerIsReloadActionQueuedMethod = AccessTools.Method(typeof(Player), "IsReloadActionQueued", (Type[])null, (Type[])null); private static readonly MethodInfo s_playerCancelReloadActionMethod = AccessTools.Method(typeof(Player), "CancelReloadAction", (Type[])null, (Type[])null); private const string MuckshakePrefabName = "ShocklateSmoothie"; private const string MuckshakeAlternatePrefabName = "ShockolateSmoothie"; private const string MuckshakeLegacyPrefabName = "Muckshake"; private const string MuckshakeNameKey = "$item_shocklatesmoothie"; private const float MuckshakeHealthRegen = 2f; private static readonly string[] s_muckshakePrefabAliases = new string[3] { "ShocklateSmoothie", "ShockolateSmoothie", "Muckshake" }; private const string NewUnlockMarkerObjectName = "BB_NewUnlockMarker"; private const string NewBuildPieceSeenKeyPrefix = "valheimbb_seen_piece_"; private const string NewCraftRecipeSeenKeyPrefix = "valheimbb_seen_recipe_"; private static readonly Type s_hudPieceIconDataType = AccessTools.Inner(typeof(Hud), "PieceIconData"); private static readonly FieldInfo s_hudPieceIconsField = AccessTools.Field(typeof(Hud), "m_pieceIcons"); private static readonly FieldInfo s_hudPieceIconGoField = AccessTools.Field(s_hudPieceIconDataType, "m_go"); private static readonly Type s_inventoryRecipeDataPairType = AccessTools.Inner(typeof(InventoryGui), "RecipeDataPair"); private static readonly FieldInfo s_inventoryAvailableRecipesField = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes"); private static readonly PropertyInfo s_inventoryRecipeProperty = AccessTools.Property(s_inventoryRecipeDataPairType, "Recipe"); private static readonly PropertyInfo s_inventoryRecipeItemDataProperty = AccessTools.Property(s_inventoryRecipeDataPairType, "ItemData"); private static readonly PropertyInfo s_inventoryRecipeElementProperty = AccessTools.Property(s_inventoryRecipeDataPairType, "InterfaceElement"); private const string NoMapBiomeLabelObjectName = "BB_NoMapBiomeLabel"; private static readonly Vector2 NoMapBiomeLabelAnchoredPosition = new Vector2(-24f, -24f); private static readonly Vector2 NoMapBiomeLabelSize = new Vector2(220f, 24f); private static readonly Color NoMapBiomeLabelColor = Color32.op_Implicit(new Color32((byte)224, (byte)178, (byte)74, byte.MaxValue)); private static readonly Color NoMapBiomeShadowColor = new Color(0f, 0f, 0f, 0.7f); private static TMP_Text s_noMapBiomeLabel; [ThreadStatic] private static Attack s_activePickaxeAttack; [ThreadStatic] private static int s_activePickaxeAttackDepth; [ThreadStatic] private static Stack s_suppressedBedrockPlacementEffects; private static readonly FieldRef s_attackPickaxeSpecialRef = AccessTools.FieldRefAccess("m_pickaxeSpecial"); private static readonly FieldRef s_attackHitTerrainEffectRef = AccessTools.FieldRefAccess("m_hitTerrainEffect"); private static readonly FieldRef s_attackWeaponRef = AccessTools.FieldRefAccess("m_weapon"); private static readonly FieldRef s_terrainModifierOnPlacedEffectRef = AccessTools.FieldRefAccess("m_onPlacedEffect"); private static readonly FieldRef s_terrainModifierLevelOffsetRef = AccessTools.FieldRefAccess("m_levelOffset"); private static readonly FieldRef s_terrainOpOnPlacedEffectRef = AccessTools.FieldRefAccess("m_onPlacedEffect"); private static readonly FieldRef s_terrainOpSettingsRef = AccessTools.FieldRefAccess("m_settings"); private const float AntlerPickaxeStaminaCost = 6f; private const float BronzePickaxeStaminaCost = 7f; private const float IronPickaxeStaminaCost = 8f; private const float BlackMetalPickaxeStaminaCost = 10f; private const float DirtOnlyRefundFraction = 0.5f; private static readonly FieldRef s_attackCharacterField = AccessTools.FieldRefAccess("m_character"); private static readonly MethodInfo s_attackGetAttackStaminaMethod = AccessTools.Method(typeof(Attack), "GetAttackStamina", (Type[])null, (Type[])null); private static ActivePickaxeSwingState s_activePickaxeSwing; private const string RestingCountdownObjectName = "BB_RestingCountdown"; private const float RestingDelaySeconds = 15f; private const float VanillaRestingDelaySeconds = 20f; private static readonly FieldInfo HudStatusEffectsField = AccessTools.Field(typeof(Hud), "m_statusEffects"); private const string ThrownSpearThrowerIdentityDataKey = "ValheimBB.ThrownSpearThrowerIdentity"; private static readonly FieldInfo s_projectileOwnerField = AccessTools.Field(typeof(Projectile), "m_owner"); private static readonly FieldInfo s_projectileWeaponField = AccessTools.Field(typeof(Projectile), "m_weapon"); private static readonly MethodInfo s_playerGetPlayerIdMethod = AccessTools.Method(typeof(Player), "GetPlayerID", (Type[])null, (Type[])null); private static readonly MethodInfo s_playerGetPlayerNameMethod = AccessTools.Method(typeof(Player), "GetPlayerName", (Type[])null, (Type[])null); private static readonly MethodInfo s_characterGetHoverNameMethod = AccessTools.Method(typeof(Character), "GetHoverName", (Type[])null, (Type[])null); private static PendingThrownSpearContext s_pendingThrownSpearContext; private static int s_autoPickupContextDepth; private const string StoneOvenPrefabName = "piece_oven"; private const string StoneOvenHatchProxyName = "HatchProxy"; private const string StoneOvenDoorsName = "doors"; private const float StoneOvenDoorTriggerRadiusDefault = 0.3f; private const float StoneOvenDoorCloseDelayDefaultSeconds = 5f; private static readonly FieldRef> s_proximityStateNearRef = AccessTools.FieldRefAccess>("m_near"); private static readonly Dictionary s_stoneOvenDoorCloseCoroutines = new Dictionary(); private const float NonDistantTeleportStuckLogSeconds = 5f; private const float NonDistantTeleportRecoverySeconds = 10f; private const float NonDistantTeleportTransientSfxCleanupSeconds = 2.25f; private const float TeleportTrackingPositionEpsilon = 0.1f; private const int MaxLoggedMissingTeleportObjects = 8; private static readonly FieldRef s_playerTeleportingRef = AccessTools.FieldRefAccess("m_teleporting"); private static readonly FieldRef s_playerDistantTeleportRef = AccessTools.FieldRefAccess("m_distantTeleport"); private static readonly FieldRef s_playerTeleportTimerRef = AccessTools.FieldRefAccess("m_teleportTimer"); private static readonly FieldRef s_playerTeleportTargetPosRef = AccessTools.FieldRefAccess("m_teleportTargetPos"); private static readonly MethodInfo s_playerResetClothMethod = AccessTools.Method(typeof(Player), "ResetCloth", (Type[])null, (Type[])null); private static readonly MethodInfo s_zNetSceneIsPrefabZdoValidMethod = AccessTools.Method(typeof(ZNetScene), "IsPrefabZDOValid", new Type[1] { typeof(ZDO) }, (Type[])null); private static readonly MethodInfo s_zNetSceneFindInstanceMethod = AccessTools.Method(typeof(ZNetScene), "FindInstance", new Type[1] { typeof(ZDO) }, (Type[])null); private static bool s_trackingTeleportState; private static bool s_attemptedTrackedTeleportTransientSfxCleanup; private static bool s_loggedStuckTeleport; private static bool s_recoveredStuckTeleport; private static bool s_lastTrackedDistantTeleport; private static float s_lastTrackedTeleportTimer; private static Vector3 s_lastTrackedTeleportTarget; private const float WeaponKnockbackBaseForce = 50f; private const float WeaponKnockbackSecondaryForceMultiplier = 6f; private static readonly string[] s_maceKnockbackPrefabNames = new string[4] { "MaceBronze", "MaceIron", "MaceSilver", "MaceNeedle" }; private const string WisplightPrefabName = "Demister"; private const string WisplightSharedName = "$item_demister"; private static readonly Vector3 WisplightRetractedChestOffset = new Vector3(0f, 1.15f, 0.45f); private static readonly FieldRef s_demisterBallInstanceRef = AccessTools.FieldRefAccess("m_ballInstance"); private static readonly FieldRef s_demisterBallVelocityRef = AccessTools.FieldRefAccess("m_ballVel"); private const string CustomHoePrefabDataKey = "ValheimBB.CustomHoePrefab"; private static int s_hoeClearDamageTextSuppressDepth; private static float s_hoeClearDamageTextSuppressUntil; private static int s_hoeClearDropSuppressDepth; private static readonly int s_hoeClearNoDropsStatusHash = StringExtensionMethods.GetStableHashCode("ValheimBB_HoeClearNoDrops"); private const string CustomItemIdDataKey = "ValheimBB.CustomItemId"; private const string CustomItemPersistenceVersionDataKey = "ValheimBB.CustomItemPersistenceVersion"; private const string FlintHoeItemId = "valheimbb:hoe_flint"; private const string BronzeHoeItemId = "valheimbb:hoe_bronze"; private static readonly CustomItemDefinition s_flintHoeCustomItem = new CustomItemDefinition { ItemId = "valheimbb:hoe_flint", PersistenceVersion = 1, RuntimePrefabName = "HoeFlint", SurrogatePrefabName = "Hoe", DisplayName = "Flint Hoe", LegacyPrefabMarkerValue = "HoeFlint", LegacyPieceTableName = "HoeFlint_PieceTable", SpawnAliases = new string[2] { "flinthoe", "flint_hoe" }, GetRuntimePrefab = () => FlintHoePrefab, EnsureRuntimePrefabAvailable = EnsureFlintHoeCustomItemPrefabAvailable }; private static readonly CustomItemDefinition s_bronzeHoeCustomItem = new CustomItemDefinition { ItemId = "valheimbb:hoe_bronze", PersistenceVersion = 1, RuntimePrefabName = "HoeBronze", SurrogatePrefabName = "Hoe", DisplayName = "Bronze Hoe", LegacyPrefabMarkerValue = "HoeBronze", LegacyPieceTableName = "HoeBronze_PieceTable", SpawnAliases = new string[2] { "bronzehoe", "bronze_hoe" }, GetRuntimePrefab = () => BronzeHoePrefab, EnsureRuntimePrefabAvailable = EnsureBronzeHoeCustomItemPrefabAvailable }; private static readonly CustomItemDefinition[] s_customItemDefinitions = new CustomItemDefinition[2] { s_flintHoeCustomItem, s_bronzeHoeCustomItem }; private static readonly Dictionary s_customItemsById = BuildCustomItemsById(); private static readonly FieldInfo s_pickablePickedField = AccessTools.Field(typeof(Pickable), "m_picked"); private static readonly FieldInfo s_pickablePickedTimeField = AccessTools.Field(typeof(Pickable), "m_pickedTime"); private static readonly FieldInfo s_pickableRespawnMinutesField = AccessTools.Field(typeof(Pickable), "m_respawnTimeMinutes"); private static readonly MethodInfo s_plantGetGrowTimeMethod = AccessTools.Method(typeof(Plant), "GetGrowTime", (Type[])null, (Type[])null); private static readonly Type s_localizationType = AccessTools.TypeByName("Localization"); private static readonly MethodInfo s_localizationInstanceGetter = ((s_localizationType != null) ? AccessTools.PropertyGetter(s_localizationType, "instance") : null); private static readonly MethodInfo s_localizeStringMethod = ((s_localizationType != null) ? AccessTools.Method(s_localizationType, "Localize", new Type[1] { typeof(string) }, (Type[])null) : null); private static readonly FieldRef> s_zNetSceneInstancesRef = AccessTools.FieldRefAccess>("m_instances"); private static int s_lastTrackedSceneInstanceSanitizeFrame = -1; private static float s_nextTrackedSceneInstanceSanitizeLogTime = -1f; private static readonly string MainMenuVersionText = "ValheimBB v0.2.2"; private const string ModOptionsButtonName = "BB_ModOptionsButton"; private const string ModOptionsPanelName = "BB_ModOptionsPanel"; private const string ModOptionsTitle = "ValheimBB Options"; private const float ModOptionsPanelWidth = 760f; private const float ModOptionsPanelHeight = 720f; private const float ModOptionsScrollSensitivity = 900f; private const float ModOptionsScrollbarWidth = 16f; private static readonly HashSet s_collapsedModOptionSections = new HashSet(StringComparer.Ordinal); private static GameObject s_activeModOptionsPanel; private static readonly List s_haldorCandidatePositions = new List(); private static string s_haldorSenseWorldKey; private static bool s_haldorSpawned = false; private bool _wasInsideHaldorPotentialRadius; public const string PluginGuid = "com.valheimbb"; public const string PluginName = "ValheimBB"; public const string PluginVersion = "0.2.2"; private const string VanillaHoePrefabName = "Hoe"; private const string FlintHoePrefabName = "HoeFlint"; private const string BronzeHoePrefabName = "HoeBronze"; private const string StandingWoodTorchPrefabName = "piece_groundtorch_wood"; private const int TorchTargetMaxFuel = 5; private const string StoneHoeDisplayName = "Stone Hoe"; private const string FlintHoeDisplayName = "Flint Hoe"; private const string BronzeHoeDisplayName = "Bronze Hoe"; private const float FlintHoeTerrainCostMultiplier = 0.2f; private const float FlintHoeClearTotalCostMultiplier = 0.5f; private const int FlintHoeClearTargetCap = 1; private const int BronzeHoeClearTargetCap = 2; private const float ClearConfirmWindowSeconds = 5f; private const float FallDamageGruntMinRepeatSeconds = 0.15f; private const float DeathSplatMinRepeatSeconds = 0.5f; private const string PlayerSfxRpcName = "ValheimBB_PlayerSfx"; private const int PlayerSfxDamageGrunt = 0; private const int PlayerSfxDeathSplat = 1; private const string PickaxeTerrainHitPrefabName = "digg_v3"; private const string PickaxeBedrockAudioFolderName = "Assets"; private const string PickaxeBedrockAudioFileName = "metal-hit-55.mp3"; private const string PickaxeBedrockAudioRpcName = "ValheimBB_PickaxeBedrockAudio"; private const float PickaxeBedrockCandidateLifetimeSeconds = 0.4f; private const float PickaxeBedrockCandidateMaxDistance = 1f; private const float PickaxeBedrockMinRepeatSeconds = 0.05f; private const string NightPopupText = "Darkness Falls"; private const int FemalePlayerModelIndex = 1; internal static ManualLogSource Log; internal static ValheimBBPlugin Instance; internal static GameObject FlintHoePrefab; internal static GameObject BronzeHoePrefab; internal static EffectList FlintHoePreviewEffect; internal static string FlintHoePreviewEffectSource; internal static float FlintHoeBaseStaminaCost; internal static float FlintHoeExtraClearStaminaCost; internal static float BronzeHoeBaseStaminaCost; internal static float BronzeHoeExtraClearStaminaCost; private static readonly HashSet s_flintHoeClearableRootNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "Beech_small1", "Beech_small2", "Bush01", "Bush02", "Bush01_heath", "shrub_1", "shrub_2", "shrub_2_heath" }; private static readonly HashSet s_bronzeHoeExtraClearableRootNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "FirTree_oldLog", "stubbe" }; private static readonly HashSet s_loggedClearCatalogEntries = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet s_loggedMissingClearEffectEntries = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly string s_localPlayerDeathClipFileName = "deathsplat.wav"; private static readonly string[] s_maleFallDamageClipFileNames = new string[2] { "grunt1.wav", "grunt2.wav" }; private static readonly string[] s_femaleFallDamageClipFileNames = new string[2] { "grunt1f.wav", "grunt2f.wav" }; private static AudioClip s_localPlayerDeathClip; private static readonly List s_maleFallDamageClips = new List(); private static readonly List s_femaleFallDamageClips = new List(); private static NamedAudioClip s_pickaxeBedrockClip; private static readonly MethodInfo s_getRightItemMethod = AccessTools.Method(typeof(Humanoid), "GetRightItem", (Type[])null, (Type[])null); private static PendingClearState s_pendingClearState; private static PickaxeBedrockCandidate s_pickaxeBedrockCandidate; private static bool s_hasNightPopupState; private static bool s_wasNightLastUpdate; private AudioSource _fallDamageVoiceSource; private AudioSource _pickaxeBedrockAudioSource; private bool _fallDamageAudioLoadStarted; private bool _pickaxeBedrockAudioLoadStarted; private float _lastFallDamageGruntTime = -10f; private float _lastDeathSplatTime = -10f; private float _lastPickaxeBedrockTime = -10f; internal static bool IsFeatureEnabled(ConfigEntry entry, bool defaultValue = true) { return entry?.Value ?? defaultValue; } internal static WisplightOutsideMistlandsMode GetWisplightOutsideMistlandsBehavior() { if (!IsFeatureEnabled(EnableWisplightAutoRetract)) { return WisplightOutsideMistlandsMode.VanillaOrbit; } if (WisplightOutsideMistlandsBehavior != null) { return WisplightOutsideMistlandsBehavior.Value; } return WisplightOutsideMistlandsMode.LockedInPlace; } internal static float GetHaldorSenseRadius() { if (HaldorSenseRadius != null) { return Mathf.Max(0f, HaldorSenseRadius.Value); } return 600f; } internal static float GetPickaxeBedrockAudioVolume() { if (PickaxeBedrockAudioVolume != null) { return Mathf.Clamp01(PickaxeBedrockAudioVolume.Value); } return 1f; } internal static float GetHoeClearConfirmWindowSeconds() { if (HoeClearConfirmWindowSeconds != null) { return Mathf.Max(0.1f, HoeClearConfirmWindowSeconds.Value); } return 5f; } internal static int GetFlintHoeClearTargetCap() { if (FlintHoeClearTargetCapConfig != null) { return Mathf.Max(0, FlintHoeClearTargetCapConfig.Value); } return 1; } internal static int GetBronzeHoeClearTargetCap() { if (BronzeHoeClearTargetCapConfig != null) { return Mathf.Max(0, BronzeHoeClearTargetCapConfig.Value); } return 2; } internal static float GetPickaxeDirtOnlyRefundFraction() { if (PickaxeDirtOnlyRefundFraction != null) { return Mathf.Clamp01(PickaxeDirtOnlyRefundFraction.Value); } return 0.5f; } internal static float GetStoneOvenDoorTriggerRadius() { if (StoneOvenDoorTriggerRadius != null) { return Mathf.Clamp(StoneOvenDoorTriggerRadius.Value, 0.1f, 5f); } return 0.3f; } internal static float GetStoneOvenDoorCloseDelaySeconds() { if (StoneOvenDoorCloseDelaySeconds != null) { return Mathf.Clamp(StoneOvenDoorCloseDelaySeconds.Value, 0f, 30f); } return 5f; } private void BindConfigEntries() { //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Expected O, but got Unknown //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Expected O, but got Unknown //IL_03a8: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Expected O, but got Unknown //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Expected O, but got Unknown //IL_040c: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Expected O, but got Unknown //IL_0449: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Expected O, but got Unknown //IL_0486: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Expected O, but got Unknown //IL_04c3: Unknown result type (might be due to invalid IL or missing references) //IL_04cd: Expected O, but got Unknown EnableStandingTorchAndKilnTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "StandingTorchAndKilnTweaks", true, "Apply standing wood torch max-fuel/no-station tweaks and prevent charcoal kilns from consuming Fine Wood."); EnableBuildAndRecipeUnlockMarkers = ((BaseUnityPlugin)this).Config.Bind("Features", "BuildAndRecipeUnlockMarkers", true, "Show ! markers on newly unlocked build pieces and crafting recipes until seen."); EnableNightPopup = ((BaseUnityPlugin)this).Config.Bind("Features", "NightPopup", true, "Show a center-screen message when night begins."); EnableHaldorSense = ((BaseUnityPlugin)this).Config.Bind("Features", "HaldorSense", true, "Show Haldor direction and discovery messages regardless of map mode."); EnableCrossbowLoadedPersistence = ((BaseUnityPlugin)this).Config.Bind("Features", "CrossbowLoadedPersistence", true, "Keep reload-required weapons loaded when swapping away and back."); EnablePickaxeStaminaTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "PickaxeStaminaTweaks", true, "Adjust pickaxe stamina costs for all pickaxe tiers."); EnablePickaxeDirtOnlyRefund = ((BaseUnityPlugin)this).Config.Bind("Features", "PickaxeDirtOnlyRefund", true, "Refund part of pickaxe stamina cost when a swing only hits dirt."); EnablePickaxeBedrockAudio = ((BaseUnityPlugin)this).Config.Bind("Features", "PickaxeBedrockAudio", true, "Play custom audio feedback when a pickaxe hits bedrock."); EnableCustomHoes = ((BaseUnityPlugin)this).Config.Bind("Features", "CustomHoes", true, "Enable Stone/Flint/Bronze hoe progression and brush-clearing hoe modes."); EnableCraftingSortToggle = ((BaseUnityPlugin)this).Config.Bind("Features", "CraftingSortToggle", true, "Show the crafting list sort toggle in the crafting UI."); EnablePlantGrowthTimer = ((BaseUnityPlugin)this).Config.Bind("Features", "PlantGrowthTimer", true, "Show qualitative growth and regrowth states for plants and picked regrowing objects."); EnableWisplightAutoRetract = ((BaseUnityPlugin)this).Config.Bind("Features", "WisplightAutoRetract", true, "Pin the Wisplight in front of the player outside the Mistlands without unequipping it."); WisplightOutsideMistlandsBehavior = ((BaseUnityPlugin)this).Config.Bind("Features", "WisplightOutsideMistlandsBehavior", WisplightOutsideMistlandsMode.LockedInPlace, "Wisplight behavior outside the Mistlands: VanillaOrbit, Off, or LockedInPlace."); EnableStoneOvenDoorTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "StoneOvenDoorTweaks", true, "Use a tighter stone oven door proximity trigger and delay closing to reduce repeated open/close chatter."); EnableMaceKnockbackTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "MaceKnockbackTweaks", true, "Apply lower primary and stronger secondary knockback to bronze, iron, silver, and needle maces."); EnableCreaturePushbackScaling = ((BaseUnityPlugin)this).Config.Bind("Features", "CreaturePushbackScaling", true, "Make creature pushback treat creature mass as lower without changing actual rigidbody mass."); EnableThrownSpearPickupProtection = ((BaseUnityPlugin)this).Config.Bind("Features", "ThrownSpearPickupProtection", true, "Prevent thrown spears from being auto-picked up by the wrong player."); EnableAmmoRecipeBatchTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "AmmoRecipeBatchTweaks", true, "Craft selected arrows and bolts in larger batches while keeping scarce ingredient costs close to vanilla."); EnableBearHeaddressRecipeTweak = ((BaseUnityPlugin)this).Config.Bind("Features", "BearHeaddressRecipeTweak", true, "Remove the Bear Trophy requirement from Headdress of the Bear and replace it with common materials."); EnableMuckshakeFoodTweak = ((BaseUnityPlugin)this).Config.Bind("Features", "MuckshakeFoodTweak", true, "Increase Muckshake health regeneration."); EnableNoMapBiomeLabel = ((BaseUnityPlugin)this).Config.Bind("Features", "NoMapBiomeLabel", true, "Show the current biome label in no-map mode while the map is closed."); EnableRestingTweaks = ((BaseUnityPlugin)this).Config.Bind("Features", "RestingTweaks", true, "Reduce the resting delay and show a countdown on the Resting status icon."); EnablePlayerDamageGruntSfx = ((BaseUnityPlugin)this).Config.Bind("Features", "PlayerDamageGruntSfx", true, "Play player grunt SFX on fall and fire damage."); EnablePlayerDeathSfx = ((BaseUnityPlugin)this).Config.Bind("Features", "PlayerDeathSfx", true, "Play player death SFX."); HaldorSenseRadius = ((BaseUnityPlugin)this).Config.Bind("Tuning", "HaldorSenseRadius", 600f, new ConfigDescription("Distance in meters for the lighter Haldor direction sense message.", (AcceptableValueBase)(object)new AcceptableValueRange(50f, 2000f), Array.Empty())); PickaxeBedrockAudioVolume = ((BaseUnityPlugin)this).Config.Bind("Tuning", "PickaxeBedrockAudioVolume", 1f, new ConfigDescription("Volume multiplier for the custom pickaxe bedrock audio.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); HoeClearConfirmWindowSeconds = ((BaseUnityPlugin)this).Config.Bind("Tuning", "HoeClearConfirmWindowSeconds", 5f, new ConfigDescription("Seconds allowed between hoe clear preview click and confirm click.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 15f), Array.Empty())); FlintHoeClearTargetCapConfig = ((BaseUnityPlugin)this).Config.Bind("Tuning", "FlintHoeClearTargetCap", 1, new ConfigDescription("Maximum brush objects the Flint Hoe can clear per confirmed click.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); BronzeHoeClearTargetCapConfig = ((BaseUnityPlugin)this).Config.Bind("Tuning", "BronzeHoeClearTargetCap", 2, new ConfigDescription("Maximum brush objects the Bronze Hoe can clear per confirmed click.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); PickaxeDirtOnlyRefundFraction = ((BaseUnityPlugin)this).Config.Bind("Tuning", "PickaxeDirtOnlyRefundFraction", 0.5f, new ConfigDescription("Fraction of paid pickaxe stamina refunded when the swing only hits dirt.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); StoneOvenDoorTriggerRadius = ((BaseUnityPlugin)this).Config.Bind("Tuning", "StoneOvenDoorTriggerRadius", 0.3f, new ConfigDescription("Stone oven hatch proximity radius in meters. Vanilla is 2.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); StoneOvenDoorCloseDelaySeconds = ((BaseUnityPlugin)this).Config.Bind("Tuning", "StoneOvenDoorCloseDelaySeconds", 5f, new ConfigDescription("Seconds the stone oven hatch waits before closing after the last nearby player leaves.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); } private static int ForceAttachSoundEffects(EffectList effects) { if (!HasEffects(effects)) { return 0; } int num = 0; EffectData[] effectPrefabs = effects.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val != null && !((Object)(object)val.m_prefab == (Object)null) && val.m_enabled && ShouldAttachCreatureOriginSoundPrefab(CleanPrefabName(((Object)val.m_prefab).name)) && !val.m_attach) { val.m_attach = true; num++; } } return num; } private static bool ShouldAttachCreatureOriginSoundPrefab(string prefabName) { return prefabName.StartsWith("sfx_", StringComparison.OrdinalIgnoreCase); } private static ItemData GetAttackWeapon(Attack attack) { if (attack == null || !(s_attackWeaponField != null)) { return null; } object? value = s_attackWeaponField.GetValue(attack); return (ItemData)((value is ItemData) ? value : null); } private static void NormalizeAttackStartSoundEffects(Attack attack) { if (attack != null) { ForceAttachSoundEffects(GetAttackWeapon(attack)?.m_shared?.m_startEffect); ForceAttachSoundEffects(attack.m_startEffect); } } private static void NormalizeAttackTriggerSoundEffects(Attack attack) { if (attack != null) { ForceAttachSoundEffects(GetAttackWeapon(attack)?.m_shared?.m_triggerEffect); ForceAttachSoundEffects(attack.m_triggerEffect); } } private static void NormalizeAttackTrailSoundEffects(Attack attack) { if (attack != null) { ForceAttachSoundEffects(GetAttackWeapon(attack)?.m_shared?.m_trailStartEffect); ForceAttachSoundEffects(attack.m_trailStartEffect); } } private static bool IsDeerIdleSoundTarget(BaseAI ai) { if ((Object)(object)ai == (Object)null) { return false; } string a = CleanPrefabName(((Object)((Component)ai).gameObject).name); if (!string.Equals(a, "Deer", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "Deer_0", StringComparison.OrdinalIgnoreCase); } return true; } private static void SetDeerAlertedIdleSoundState(BaseAI ai, bool alerted) { if (!IsDeerIdleSoundTarget(ai)) { return; } int instanceID = ((Object)ai).GetInstanceID(); EffectList value; if (alerted) { if (HasEffects(ai.m_alertedEffects)) { if (!s_deerOriginalIdleSounds.ContainsKey(instanceID)) { s_deerOriginalIdleSounds[instanceID] = ai.m_idleSound; } ForceAttachSoundEffects(ai.m_alertedEffects); ai.m_idleSound = ai.m_alertedEffects; } } else if (s_deerOriginalIdleSounds.TryGetValue(instanceID, out value)) { ai.m_idleSound = value; s_deerOriginalIdleSounds.Remove(instanceID); } } private static void ClearDeerAlertedIdleSoundState(BaseAI ai) { if (!((Object)(object)ai == (Object)null)) { s_deerOriginalIdleSounds.Remove(((Object)ai).GetInstanceID()); } } private static IEnumerable ReplaceEffectListCreateNullParentWithSelfTransform(IEnumerable instructions) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown List list = new List(instructions); for (int i = 0; i <= list.Count - 4; i++) { if (!(list[i].opcode != OpCodes.Ldnull) && list[i + 3].operand is MethodInfo methodInfo && !(methodInfo != s_effectListCreateMethod)) { list[i] = new CodeInstruction(OpCodes.Ldarg_0, (object)null); list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)s_componentTransformGetter)); i++; } } return list; } private void EnsureFallDamageVoiceSource() { if (!((Object)(object)_fallDamageVoiceSource != (Object)null)) { _fallDamageVoiceSource = ((Component)this).gameObject.GetComponent(); if ((Object)(object)_fallDamageVoiceSource == (Object)null) { _fallDamageVoiceSource = ((Component)this).gameObject.AddComponent(); } _fallDamageVoiceSource.playOnAwake = false; _fallDamageVoiceSource.loop = false; _fallDamageVoiceSource.spatialBlend = 0f; _fallDamageVoiceSource.volume = 1f; _fallDamageVoiceSource.dopplerLevel = 0f; _fallDamageVoiceSource.bypassEffects = true; _fallDamageVoiceSource.bypassListenerEffects = true; _fallDamageVoiceSource.bypassReverbZones = true; _fallDamageVoiceSource.reverbZoneMix = 0f; _fallDamageVoiceSource.spatialize = false; } } private void BeginFallDamageAudioLoad() { if (!_fallDamageAudioLoadStarted) { _fallDamageAudioLoadStarted = true; ((MonoBehaviour)this).StartCoroutine(LoadFallDamageAudioClips()); } } [IteratorStateMachine(typeof(d__72))] private IEnumerator LoadFallDamageAudioClips() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__72(0) { <>4__this = this }; } [IteratorStateMachine(typeof(d__73))] private IEnumerator LoadFallDamageAudioClipSet(IEnumerable fileNames, List destination, string label) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__73(0) { <>4__this = this, fileNames = fileNames, destination = destination, label = label }; } [IteratorStateMachine(typeof(d__74))] private IEnumerator LoadSingleAudioClip(string fileName, Action onLoaded, string label) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__74(0) { fileName = fileName, onLoaded = onLoaded, label = label }; } private static bool IsFemalePlayer(Player player) { if ((Object)(object)player != (Object)null) { return player.GetPlayerModel() == 1; } return false; } private static int GetDamageGruntVariantCount(Player player) { int num = (IsFemalePlayer(player) ? s_femaleFallDamageClipFileNames.Length : s_maleFallDamageClipFileNames.Length); if (num == 0) { num = (IsFemalePlayer(player) ? s_maleFallDamageClipFileNames.Length : s_femaleFallDamageClipFileNames.Length); } return num; } private static List GetDamageGruntClips(Player player) { List list = (IsFemalePlayer(player) ? s_femaleFallDamageClips : s_maleFallDamageClips); if (list.Count == 0) { list = (IsFemalePlayer(player) ? s_maleFallDamageClips : s_femaleFallDamageClips); } return list; } internal static void RegisterPlayerSfxRpc(Player player) { ZNetView val = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent() : null); if (!((Object)(object)val == (Object)null) && val.IsValid()) { val.Register("ValheimBB_PlayerSfx", (Action)delegate(long sender, int sfxType, int variant) { RPC_PlayerSfx(player, sender, sfxType, variant); }); } } private static void RPC_PlayerSfx(Player player, long sender, int sfxType, int variant) { if (!((Object)(object)player == (Object)null)) { switch (sfxType) { case 0: TryPlayPlayerDamageGrunt(player, variant); break; case 1: TryPlayPlayerDeathSplat(player); break; } } } internal static void TryBroadcastPlayerDamageGrunt(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || (Object)(object)Instance == (Object)null || Time.unscaledTime - Instance._lastFallDamageGruntTime < 0.15f) { return; } int damageGruntVariantCount = GetDamageGruntVariantCount(player); if (damageGruntVariantCount != 0) { ZNetView component = ((Component)player).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner()) { Instance._lastFallDamageGruntTime = Time.unscaledTime; component.InvokeRPC(ZNetView.Everybody, "ValheimBB_PlayerSfx", new object[2] { 0, Random.Range(0, damageGruntVariantCount) }); } } } internal static void TryBroadcastPlayerDeathSplat(Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && !((Object)(object)Instance == (Object)null) && !(Time.unscaledTime - Instance._lastDeathSplatTime < 0.5f)) { ZNetView component = ((Component)player).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner()) { Instance._lastDeathSplatTime = Time.unscaledTime; component.InvokeRPC(ZNetView.Everybody, "ValheimBB_PlayerSfx", new object[2] { 1, 0 }); } } } private static void TryPlayPlayerDamageGrunt(Player player, int variant) { if (IsFeatureEnabled(EnablePlayerDamageGruntSfx)) { List damageGruntClips = GetDamageGruntClips(player); if (damageGruntClips.Count != 0) { int index = Mathf.Abs(variant) % damageGruntClips.Count; PlayPlayerVoiceClip(player, damageGruntClips[index]); } } } private static void TryPlayPlayerDeathSplat(Player player) { if (IsFeatureEnabled(EnablePlayerDeathSfx) && !((Object)(object)s_localPlayerDeathClip == (Object)null)) { PlayPlayerVoiceClip(player, s_localPlayerDeathClip); } } private static void PlayPlayerVoiceClip(Player player, AudioClip clip) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !((Object)(object)clip == (Object)null)) { if ((Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)Instance != (Object)null && (Object)(object)Instance._fallDamageVoiceSource != (Object)null) { Instance._fallDamageVoiceSource.Stop(); Instance._fallDamageVoiceSource.PlayOneShot(clip); } else { AudioSource.PlayClipAtPoint(clip, ((Component)player).transform.position, 1f); } } } private void EnsurePickaxeBedrockAudioSource() { if (IsFeatureEnabled(EnablePickaxeBedrockAudio) && !((Object)(object)_pickaxeBedrockAudioSource != (Object)null)) { _pickaxeBedrockAudioSource = ((Component)this).gameObject.AddComponent(); _pickaxeBedrockAudioSource.playOnAwake = false; _pickaxeBedrockAudioSource.loop = false; _pickaxeBedrockAudioSource.spatialBlend = 0f; _pickaxeBedrockAudioSource.volume = GetPickaxeBedrockAudioVolume(); _pickaxeBedrockAudioSource.dopplerLevel = 0f; _pickaxeBedrockAudioSource.bypassEffects = true; _pickaxeBedrockAudioSource.bypassListenerEffects = true; _pickaxeBedrockAudioSource.bypassReverbZones = true; _pickaxeBedrockAudioSource.reverbZoneMix = 0f; _pickaxeBedrockAudioSource.spatialize = false; } } private void BeginPickaxeBedrockAudioLoad() { if (IsFeatureEnabled(EnablePickaxeBedrockAudio) && !_pickaxeBedrockAudioLoadStarted) { _pickaxeBedrockAudioLoadStarted = true; ((MonoBehaviour)this).StartCoroutine(LoadPickaxeBedrockAudioClips()); } } [IteratorStateMachine(typeof(d__87))] private IEnumerator LoadPickaxeBedrockAudioClips() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__87(0) { <>4__this = this }; } [IteratorStateMachine(typeof(d__88))] private IEnumerator LoadPickaxeBedrockAudioClip(string filePath) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__88(0) { filePath = filePath }; } private static bool IsPickaxeWeapon(ItemData weapon) { if (weapon != null && weapon.m_shared != null && weapon.m_shared.m_attack != null) { return weapon.m_shared.m_attack.m_pickaxeSpecial; } return false; } private static bool IsPickaxeTerrainHitPrefab(GameObject prefab) { if ((Object)(object)prefab != (Object)null) { return string.Equals(CleanPrefabName(((Object)prefab).name), "digg_v3", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsPickaxeTerrainOp(TerrainOp terrainOp) { if ((Object)(object)terrainOp != (Object)null) { return string.Equals(CleanPrefabName(((Object)((Component)terrainOp).gameObject).name), "digg_v3", StringComparison.OrdinalIgnoreCase); } return false; } private static void RegisterLocalPickaxeTerrainHit(Vector3 hitPoint, GameObject prefab, Character character, ItemData weapon) { //IL_0057: 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) if (IsFeatureEnabled(EnablePickaxeBedrockAudio)) { Player val = (Player)(object)((character is Player) ? character : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && IsPickaxeTerrainHitPrefab(prefab) && IsPickaxeWeapon(weapon)) { PickaxeBedrockCandidate pickaxeBedrockCandidate = default(PickaxeBedrockCandidate); pickaxeBedrockCandidate.Active = true; pickaxeBedrockCandidate.Player = val; pickaxeBedrockCandidate.Position = hitPoint; pickaxeBedrockCandidate.ExpiresAt = Time.unscaledTime + 0.4f; s_pickaxeBedrockCandidate = pickaxeBedrockCandidate; } } } private static bool HasMatchingPickaxeBedrockCandidate(Vector3 position) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!s_pickaxeBedrockCandidate.Active) { return false; } if (Time.unscaledTime > s_pickaxeBedrockCandidate.ExpiresAt) { s_pickaxeBedrockCandidate.Active = false; return false; } return Vector3.Distance(position, s_pickaxeBedrockCandidate.Position) <= 1f; } private static void TryHandlePickaxeBedrockHit(TerrainOp terrainOp) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0052: 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) if (IsFeatureEnabled(EnablePickaxeBedrockAudio) && IsPickaxeTerrainOp(terrainOp) && terrainOp.m_settings != null && Heightmap.AtMaxLevelDepth(((Component)terrainOp).transform.position + Vector3.up * terrainOp.m_settings.m_levelOffset) && HasMatchingPickaxeBedrockCandidate(((Component)terrainOp).transform.position)) { Player player = s_pickaxeBedrockCandidate.Player; s_pickaxeBedrockCandidate.Active = false; TryBroadcastPickaxeBedrockClip(player, ((Component)terrainOp).transform.position); } } internal static void RegisterPickaxeBedrockAudioRpc(Player player) { ZNetView val = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent() : null); if (!((Object)(object)val == (Object)null) && val.IsValid()) { val.Register("ValheimBB_PickaxeBedrockAudio", (Action)delegate(long sender, Vector3 hitPosition) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) RPC_PickaxeBedrockAudio(player, sender, hitPosition); }); } } private static void RPC_PickaxeBedrockAudio(Player player, long sender, Vector3 hitPosition) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) TryPlayPickaxeBedrockClip(player, hitPosition); } private static void TryBroadcastPickaxeBedrockClip(Player player, Vector3 hitPosition) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (IsFeatureEnabled(EnablePickaxeBedrockAudio)) { ZNetView val = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent() : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { val.InvokeRPC(ZNetView.Everybody, "ValheimBB_PickaxeBedrockAudio", new object[1] { hitPosition }); } } } private static void TryPlayPickaxeBedrockClip(Player player, Vector3 hitPosition) { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) if (!IsFeatureEnabled(EnablePickaxeBedrockAudio)) { return; } if ((Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)Instance != (Object)null && (Object)(object)Instance._pickaxeBedrockAudioSource == (Object)null) { Instance.EnsurePickaxeBedrockAudioSource(); } if (!((Object)(object)Instance == (Object)null) && s_pickaxeBedrockClip != null && !((Object)(object)s_pickaxeBedrockClip.Clip == (Object)null) && !(Time.unscaledTime - Instance._lastPickaxeBedrockTime < 0.05f)) { Instance._lastPickaxeBedrockTime = Time.unscaledTime; if ((Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)Instance._pickaxeBedrockAudioSource != (Object)null) { Instance._pickaxeBedrockAudioSource.Stop(); Instance._pickaxeBedrockAudioSource.volume = GetPickaxeBedrockAudioVolume(); Instance._pickaxeBedrockAudioSource.PlayOneShot(s_pickaxeBedrockClip.Clip); } else { AudioSource.PlayClipAtPoint(s_pickaxeBedrockClip.Clip, hitPosition, GetPickaxeBedrockAudioVolume()); } ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ValheimBB] Pickaxe bedrock clip: " + s_pickaxeBedrockClip.FileName)); } } } private static HashSet GetLocalPickaxeVegetationAudioTargets() { return s_localPickaxeVegetationAudioTargets ?? (s_localPickaxeVegetationAudioTargets = new HashSet()); } private static bool BeginLocalPickaxeVegetationAudio(Attack attack) { if (!TryGetLocalPickaxeSwingPlayer(attack, out var _)) { return false; } if (s_localPickaxeVegetationAudioDepth == 0) { GetLocalPickaxeVegetationAudioTargets().Clear(); } s_localPickaxeVegetationAudioDepth++; return true; } private static void EndLocalPickaxeVegetationAudio(bool beganTracking) { if (beganTracking && s_localPickaxeVegetationAudioDepth > 0) { s_localPickaxeVegetationAudioDepth--; if (s_localPickaxeVegetationAudioDepth == 0) { GetLocalPickaxeVegetationAudioTargets().Clear(); } } } private static bool IsLocalPickaxeVegetationAudioActive() { return s_localPickaxeVegetationAudioDepth > 0; } private static bool TryRegisterLocalPickaxeVegetationTarget(Component component) { if (!IsLocalPickaxeVegetationAudioActive() || (Object)(object)component == (Object)null) { return false; } return GetLocalPickaxeVegetationAudioTargets().Add(((Object)component.gameObject).GetInstanceID()); } private static bool IsBushRustleHitEffect(EffectList effects) { if (!HasEffects(effects)) { return false; } EffectData[] effectPrefabs = effects.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (NormalizeName(((Object)(object)val.m_prefab != (Object)null) ? ((Object)val.m_prefab).name : null).Contains("sfxbushhit")) { return true; } } return false; } private static void TryReplayPickaxeVegetationHitEffect(Component target, EffectList hitEffect, HitData hit) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0049: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null) && IsBushRustleHitEffect(hitEffect) && TryRegisterLocalPickaxeVegetationTarget(target)) { Vector3 val = hit?.m_point ?? target.transform.position; if (val == Vector3.zero) { val = target.transform.position; } hitEffect.Create(val, Quaternion.identity, (Transform)null, 1f, -1); } } private void ShowPinHelpOnce() { if (!_pinHelpShown && !((Object)(object)Chat.instance == (Object)null)) { _pinHelpShown = true; Chat_InputText_Patch.ShowHelp(); } } private static float GetTerrainRadius(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return 2f; } float num = 0f; TerrainModifier component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { num = Mathf.Max(num, component.m_levelRadius); num = Mathf.Max(num, component.m_smoothRadius); num = Mathf.Max(num, component.m_paintRadius); } TerrainOp component2 = prefab.GetComponent(); if ((Object)(object)component2 != (Object)null && component2.m_settings != null) { num = Mathf.Max(num, component2.m_settings.m_levelRadius); num = Mathf.Max(num, component2.m_settings.m_raiseRadius); num = Mathf.Max(num, component2.m_settings.m_smoothRadius); num = Mathf.Max(num, component2.m_settings.m_paintRadius); } if (!(num > 0f)) { return 2f; } return num; } private static void SetTerrainRadius(GameObject prefab, float radius) { if (!((Object)(object)prefab == (Object)null) && !(radius <= 0f)) { TerrainModifier component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_levelRadius = radius; component.m_smoothRadius = radius; component.m_paintRadius = radius; } TerrainOp component2 = prefab.GetComponent(); if ((Object)(object)component2 != (Object)null && component2.m_settings != null) { component2.m_settings.m_levelRadius = radius; component2.m_settings.m_raiseRadius = radius; component2.m_settings.m_smoothRadius = radius; component2.m_settings.m_paintRadius = radius; } } } private static string CleanPrefabName(string name) { if (!string.IsNullOrEmpty(name)) { return name.Replace("(Clone)", ""); } return string.Empty; } private static IEnumerable GetAllPreviewClearableRootNames() { HashSet hashSet = new HashSet(s_flintHoeClearableRootNames, StringComparer.OrdinalIgnoreCase); hashSet.UnionWith(s_bronzeHoeExtraClearableRootNames); return hashSet; } private static bool IsRootClearableForHoe(ItemData hoeItem, string rootName) { if (string.IsNullOrWhiteSpace(rootName)) { return false; } if (IsBronzeHoeItem(hoeItem)) { if (!s_flintHoeClearableRootNames.Contains(rootName)) { return s_bronzeHoeExtraClearableRootNames.Contains(rootName); } return true; } if (IsFlintHoeItem(hoeItem)) { return s_flintHoeClearableRootNames.Contains(rootName); } return false; } private static bool HasEffects(EffectList effects) { if (effects != null && effects.m_effectPrefabs != null) { return effects.m_effectPrefabs.Length != 0; } return false; } private static void EnsureValheimBBCompendiumEntries(object tutorialInstance) { try { if (tutorialInstance == null) { return; } Type type = tutorialInstance.GetType(); if (!string.Equals(type.Name, "Tutorial", StringComparison.Ordinal)) { return; } Type nestedType = type.GetNestedType("TutorialText", s_nonPublicTypeFlags); FieldInfo fieldInfo = AccessTools.Field(type, "m_texts"); FieldInfo fieldInfo2 = AccessTools.Field(type, "m_knownTexts"); if (nestedType == null || fieldInfo == null) { LogTutorialApiWarning(type, nestedType, fieldInfo, fieldInfo2); return; } bool flag = false; flag |= EnsureEntriesInCollectionField(tutorialInstance, fieldInfo, nestedType, addNamesOnly: false); if (fieldInfo2 != null) { flag |= EnsureEntriesInCollectionField(tutorialInstance, fieldInfo2, nestedType, ShouldKnownTextFieldUseNamesOnly(fieldInfo2, nestedType)); } if (flag && !s_loggedCompendiumInjection) { s_loggedCompendiumInjection = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[ValheimBB] Injected ValheimBB entries into the in-game compendium."); } } } catch (Exception arg) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"[ValheimBB] Error injecting compendium entries: {arg}"); } } } private static string LoadValheimBBCompendiumText() { string defaultValheimBBCompendiumText = GetDefaultValheimBBCompendiumText(); try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (string.IsNullOrEmpty(directoryName)) { return defaultValheimBBCompendiumText; } string text = Path.Combine(directoryName, "Assets", "Compendium", "valheimbutbetter.md"); if (!File.Exists(text)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Compendium markdown file not found: " + text + ". Falling back to built-in text.")); } return defaultValheimBBCompendiumText; } string text2 = File.ReadAllText(text); if (string.IsNullOrWhiteSpace(text2)) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Compendium markdown file was empty: " + text + ". Falling back to built-in text.")); } return defaultValheimBBCompendiumText; } return NormalizeCompendiumText(text2); } catch (Exception arg) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)$"[ValheimBB] Failed to load compendium markdown file. Falling back to built-in text. {arg}"); } return defaultValheimBBCompendiumText; } } private static string NormalizeCompendiumText(string text) { if (!string.IsNullOrWhiteSpace(text)) { return text.Replace("\r\n", "\n").Trim(); } return string.Empty; } private static string GetDefaultValheimBBCompendiumText() { return "Overview\nValheimButBetter bundles several quality-of-life tweaks into one mod.\n\nCustom Hoes\nThe vanilla Hoe is relabeled as Stone Hoe.\nFlint Hoe lowers terrain stamina cost and can clear one nearby shrub or root after a confirm click.\nBronze Hoe clears more brush and can remove up to two nearby targets after a confirm click.\n\nWorld Tweaks\nFireplaces can burn core wood or ancient bark when normal wood is missing.\nModer lasts 10 minutes and changes ship wind to a side wind while active.\nHeaddress of the Bear no longer requires a bear trophy.\n\nCombat and Gear\nCrossbows stay loaded when you swap away from them and back.\nWisplight stays equipped, but rests in front of you outside Mistlands.\nMuckshake health regen increased to 2.\nMetal arrows and bolts craft in batches of 100 while keeping their metal bar costs at vanilla values.\nFrost arrows also craft in batches of 100 while keeping their freeze gland cost at the vanilla value.\nBronze, iron, silver, and needle maces have lower primary knockback and much stronger secondary knockback.\n\nFeedback\nPlants show qualitative growth state on hover instead of exact timers.\nPicked regrowing plants show qualitative regrowth state on hover.\nNewly unlocked build pieces and crafting recipes show a ! marker until hovered or selected.\nDirt-only pickaxe swings refund part of the stamina cost.\nBedrock pickaxe hits get custom audio feedback.\n\nConfig\nMajor features can be toggled in BepInEx/config/com.valheimbb.cfg.\nTunables include Haldor sense radius, bedrock audio volume, hoe clear confirmation timing, hoe clear target caps, and dirt-only pickaxe stamina refund.\n\nCommands\n/addpin\n/removepin\n/cleardeaths"; } private static void LogTutorialApiWarning(Type tutorialType, Type tutorialTextType, FieldInfo textsField, FieldInfo knownTextsField) { if (s_loggedTutorialApiWarning) { return; } s_loggedTutorialApiWarning = true; string text = ((knownTextsField != null) ? knownTextsField.Name : "null"); if (tutorialTextType == null || textsField == null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not resolve the Tutorial compendium API. type=" + ((tutorialType != null) ? tutorialType.FullName : "null") + ", textType=" + ((tutorialTextType != null) ? tutorialTextType.FullName : "null") + ", textsField=" + ((textsField != null) ? textsField.Name : "null") + ", knownTextsField=" + text + ".")); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)("[ValheimBB] Tutorial compendium sync field is unavailable in this Valheim build; injecting entries into m_texts only. knownTextsField=" + text + ".")); } } } private static void EnsureValheimBBKnownTexts(List> knownTexts) { if (knownTexts == null) { return; } bool flag = false; ValheimBBCompendiumEntry[] array = s_valheimBBCompendiumEntries; foreach (ValheimBBCompendiumEntry valheimBBCompendiumEntry in array) { bool flag2 = false; foreach (KeyValuePair knownText in knownTexts) { if (string.Equals(knownText.Key, valheimBBCompendiumEntry.Topic, StringComparison.Ordinal) || string.Equals(knownText.Value, valheimBBCompendiumEntry.Text, StringComparison.Ordinal)) { flag2 = true; break; } } if (!flag2) { knownTexts.Add(new KeyValuePair(valheimBBCompendiumEntry.Topic, valheimBBCompendiumEntry.Text)); flag = true; } } if (flag && !s_loggedCompendiumInjection) { s_loggedCompendiumInjection = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[ValheimBB] Added ValheimBB entries to Player.GetKnownTexts for the compendium."); } } } private static bool ShouldKnownTextFieldUseNamesOnly(FieldInfo knownTextsField, Type tutorialTextType) { Type collectionElementType = GetCollectionElementType(knownTextsField?.FieldType); if (!(collectionElementType == typeof(string))) { if (collectionElementType != null && tutorialTextType != null) { return !collectionElementType.IsAssignableFrom(tutorialTextType); } return false; } return true; } private static bool EnsureEntriesInCollectionField(object instance, FieldInfo field, Type tutorialTextType, bool addNamesOnly) { if (instance == null || field == null) { return false; } if (field.FieldType.IsArray) { return EnsureEntriesInArrayField(instance, field, tutorialTextType, addNamesOnly); } IList list = field.GetValue(instance) as IList; if (list == null) { list = CreateCollectionList(field.FieldType, GetCollectionElementType(field.FieldType) ?? tutorialTextType); if (list == null) { return false; } field.SetValue(instance, list); } bool result = false; ValheimBBCompendiumEntry[] array = s_valheimBBCompendiumEntries; foreach (ValheimBBCompendiumEntry valheimBBCompendiumEntry in array) { if (!CollectionContainsEntry(list, valheimBBCompendiumEntry)) { object obj = (addNamesOnly ? valheimBBCompendiumEntry.Name : CreateTutorialText(tutorialTextType, valheimBBCompendiumEntry)); if (obj != null) { list.Add(obj); result = true; } } } return result; } private static bool EnsureEntriesInArrayField(object instance, FieldInfo field, Type tutorialTextType, bool addNamesOnly) { Type type = GetCollectionElementType(field.FieldType) ?? (addNamesOnly ? typeof(string) : tutorialTextType); if (type == null) { return false; } Array obj = (field.GetValue(instance) as Array) ?? Array.CreateInstance(type, 0); List list = new List(obj.Length); foreach (object item in obj) { list.Add(item); } bool flag = false; ValheimBBCompendiumEntry[] array = s_valheimBBCompendiumEntries; foreach (ValheimBBCompendiumEntry valheimBBCompendiumEntry in array) { if (!CollectionContainsEntry(list, valheimBBCompendiumEntry)) { object obj2 = (addNamesOnly ? valheimBBCompendiumEntry.Name : CreateTutorialText(tutorialTextType, valheimBBCompendiumEntry)); if (obj2 != null) { list.Add(obj2); flag = true; } } } if (!flag) { return false; } Array array2 = Array.CreateInstance(type, list.Count); for (int j = 0; j < list.Count; j++) { array2.SetValue(list[j], j); } field.SetValue(instance, array2); return true; } private static IList CreateCollectionList(Type collectionType, Type elementType) { try { if (collectionType != null && !collectionType.IsInterface && !collectionType.IsAbstract) { return Activator.CreateInstance(collectionType) as IList; } if (elementType != null) { return Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)) as IList; } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error creating tutorial collection list: {arg}"); } } return null; } private static Type GetCollectionElementType(Type collectionType) { if (collectionType == null) { return null; } if (collectionType.IsArray) { return collectionType.GetElementType(); } if (collectionType.IsGenericType) { Type[] genericArguments = collectionType.GetGenericArguments(); if (genericArguments.Length == 1) { return genericArguments[0]; } } return null; } private static bool CollectionContainsEntry(IEnumerable collection, ValheimBBCompendiumEntry entry) { if (collection == null || entry == null) { return false; } foreach (object item in collection) { if (DoesCollectionItemMatchEntry(item, entry)) { return true; } } return false; } private static bool DoesCollectionItemMatchEntry(object item, ValheimBBCompendiumEntry entry) { if (item == null || entry == null) { return false; } if (item is string a) { return string.Equals(a, entry.Name, StringComparison.Ordinal); } Type type = item.GetType(); if (string.Equals(AccessTools.Field(type, "m_name")?.GetValue(item) as string, entry.Name, StringComparison.Ordinal)) { return true; } return string.Equals(AccessTools.Field(type, "m_topic")?.GetValue(item) as string, entry.Topic, StringComparison.Ordinal); } private static object CreateTutorialText(Type tutorialTextType, ValheimBBCompendiumEntry entry) { if (tutorialTextType == null || entry == null) { return null; } try { object? obj = Activator.CreateInstance(tutorialTextType); SetFieldValue(obj, "m_name", entry.Name); SetFieldValue(obj, "m_globalKeyTrigger", string.Empty); SetFieldValue(obj, "m_tutorialTrigger", string.Empty); SetFieldValue(obj, "m_topic", entry.Topic); SetFieldValue(obj, "m_label", entry.Label); SetFieldValue(obj, "m_isMunin", false); SetFieldValue(obj, "m_text", entry.Text); return obj; } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"[ValheimBB] Error creating tutorial text '{entry.Name}': {arg}"); } return null; } } private static void SetFieldValue(object target, string fieldName, object value) { if (target == null || string.IsNullOrWhiteSpace(fieldName)) { return; } FieldInfo field = target.GetType().GetField(fieldName, s_nonPublicInstanceFlags); if (!(field == null)) { if (value == null || field.FieldType.IsInstanceOfType(value)) { field.SetValue(target, value); } else if (field.FieldType == typeof(bool) && value is int num) { field.SetValue(target, num != 0); } else if (field.FieldType == typeof(int) && value is bool flag) { field.SetValue(target, flag ? 1 : 0); } else { field.SetValue(target, Convert.ChangeType(value, field.FieldType)); } } } private static AmmoRecipeBatchTweakDefinition CreateAmmoRecipeBatchTweakDefinition(string recipeName, string outputPrefabName, string[] staticRequirementPrefabNames, params AmmoRequirementBaseline[] vanillaRequirements) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (vanillaRequirements != null) { for (int i = 0; i < vanillaRequirements.Length; i++) { AmmoRequirementBaseline ammoRequirementBaseline = vanillaRequirements[i]; if (!string.IsNullOrWhiteSpace(ammoRequirementBaseline.PrefabName)) { dictionary[ammoRequirementBaseline.PrefabName] = ammoRequirementBaseline.Amount; } } } return new AmmoRecipeBatchTweakDefinition { RecipeName = recipeName, OutputPrefabName = outputPrefabName, StaticRequirementPrefabNames = ((staticRequirementPrefabNames != null) ? new HashSet(staticRequirementPrefabNames, StringComparer.OrdinalIgnoreCase) : new HashSet(StringComparer.OrdinalIgnoreCase)), VanillaRequirementAmounts = dictionary }; } private static void ApplyAmmoRecipeBatchTweaks(ObjectDB objectDb) { try { if ((Object)(object)objectDb == (Object)null || objectDb.m_recipes == null || !IsFeatureEnabled(EnableAmmoRecipeBatchTweaks)) { return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (Recipe recipe in objectDb.m_recipes) { if (TryGetAmmoRecipeBatchTweakDefinition(recipe, out var definition)) { hashSet.Add(definition.RecipeName); if (ApplyAmmoRecipeBatchTweak(recipe, definition)) { num++; } } } AmmoRecipeBatchTweakDefinition[] array = s_ammoRecipeBatchTweakDefinitions; foreach (AmmoRecipeBatchTweakDefinition ammoRecipeBatchTweakDefinition in array) { if (!hashSet.Contains(ammoRecipeBatchTweakDefinition.RecipeName)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not find " + ammoRecipeBatchTweakDefinition.RecipeName + " while applying ammo recipe batch tweaks.")); } } } if (num > 0) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[ValheimBB] Updated {num} ammo recipes to craft {100} per batch."); } } } catch (Exception arg) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogError((object)$"[ValheimBB] Error while updating ammo recipe batches: {arg}"); } } } private static bool TryGetAmmoRecipeBatchTweakDefinition(Recipe recipe, out AmmoRecipeBatchTweakDefinition definition) { definition = null; if ((Object)(object)recipe == (Object)null) { return false; } string a = CleanPrefabName(((Object)recipe).name); string a2 = CleanPrefabName(((Object)(object)recipe.m_item != (Object)null) ? ((Object)recipe.m_item).name : null); AmmoRecipeBatchTweakDefinition[] array = s_ammoRecipeBatchTweakDefinitions; foreach (AmmoRecipeBatchTweakDefinition ammoRecipeBatchTweakDefinition in array) { if (ammoRecipeBatchTweakDefinition != null && (string.Equals(a, ammoRecipeBatchTweakDefinition.RecipeName, StringComparison.OrdinalIgnoreCase) || string.Equals(a2, ammoRecipeBatchTweakDefinition.OutputPrefabName, StringComparison.OrdinalIgnoreCase))) { definition = ammoRecipeBatchTweakDefinition; return true; } } return false; } private static bool ApplyAmmoRecipeBatchTweak(Recipe recipe, AmmoRecipeBatchTweakDefinition definition) { if ((Object)(object)recipe == (Object)null || definition == null || recipe.m_resources == null) { return false; } bool result = false; if (recipe.m_amount != 100) { recipe.m_amount = 100; result = true; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val?.m_resItem == (Object)null)) { string requirementPrefabName = CleanPrefabName(((Object)val.m_resItem).name); if (definition.TryGetTargetRequirementAmount(requirementPrefabName, out var amount) && val.m_amount != amount) { val.m_amount = amount; result = true; } } } return result; } private static void UpdateBearHeaddressRecipe(ObjectDB objectDb) { try { if ((Object)(object)objectDb == (Object)null || objectDb.m_recipes == null || !IsFeatureEnabled(EnableBearHeaddressRecipeTweak)) { return; } Recipe val = FindBearHeaddressRecipe(objectDb); if ((Object)(object)val == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[ValheimBB] Could not find Recipe_HelmetBerserker (Headdress of the Bear)."); } return; } if (val.m_resources == null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)"[ValheimBB] Headdress of the Bear recipe has no resources array."); } return; } List list = new List(); Requirement[] resources = val.m_resources; foreach (Requirement val2 in resources) { if (!((Object)(object)val2?.m_resItem == (Object)null)) { string item = CleanPrefabName(((Object)val2.m_resItem).name); if (!s_bearHeaddressNormalizedRequirementNames.Contains(item)) { list.Add(CopyRequirement(val2)); } } } AddBearHeaddressRequirement(objectDb, list, "BoneFragments", 3); AddBearHeaddressRequirement(objectDb, list, "LeatherScraps", 2); val.m_resources = list.ToArray(); ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)"[ValheimBB] Updated Headdress of the Bear recipe: removed Bear Trophy, added 3 BoneFragments and 2 LeatherScraps."); } } catch (Exception arg) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogError((object)$"[ValheimBB] Error while updating Headdress of the Bear recipe: {arg}"); } } } private static Recipe FindBearHeaddressRecipe(ObjectDB objectDb) { if ((Object)(object)objectDb == (Object)null || objectDb.m_recipes == null) { return null; } foreach (Recipe recipe in objectDb.m_recipes) { if (!((Object)(object)recipe == (Object)null)) { string a = CleanPrefabName(((Object)recipe).name); string a2 = CleanPrefabName(((Object)(object)recipe.m_item != (Object)null) ? ((Object)recipe.m_item).name : null); if (string.Equals(a, "Recipe_HelmetBerserker", StringComparison.OrdinalIgnoreCase) || string.Equals(a2, "HelmetBerserker", StringComparison.OrdinalIgnoreCase)) { return recipe; } } } return null; } private static Requirement CopyRequirement(Requirement source) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (source == null) { return null; } return new Requirement { m_resItem = source.m_resItem, m_amount = source.m_amount, m_amountPerLevel = source.m_amountPerLevel, m_recover = source.m_recover }; } private static void AddBearHeaddressRequirement(ObjectDB objectDb, List requirements, string itemPrefabName, int amount) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown GameObject itemPrefab = objectDb.GetItemPrefab(itemPrefabName); if ((Object)(object)itemPrefab == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not find item prefab '" + itemPrefabName + "' when updating Headdress of the Bear recipe.")); } return; } ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[ValheimBB] Item prefab '" + itemPrefabName + "' has no ItemDrop component.")); } } else { requirements.Add(new Requirement { m_resItem = component, m_amount = amount, m_amountPerLevel = 2, m_recover = true }); } } private static bool RequiresReload(ItemData item) { if (item != null && item.m_shared != null && item.m_shared.m_attack != null) { return item.m_shared.m_attack.m_requiresReload; } return false; } private static ItemData GetLoadedWeapon(Player player) { if (!((Object)(object)player != (Object)null)) { return null; } return s_playerLoadedWeaponRef.Invoke(player); } private static bool HasPersistentLoadedFlag(ItemData weapon) { if (weapon?.m_customData != null && weapon.m_customData.TryGetValue("ValheimBB.PersistentLoadedCrossbow", out var value)) { return value == "1"; } return false; } private static void SetPersistentLoadedFlag(ItemData weapon, bool isLoaded) { if (RequiresReload(weapon)) { if (weapon.m_customData == null) { weapon.m_customData = new Dictionary(); } if (isLoaded) { weapon.m_customData["ValheimBB.PersistentLoadedCrossbow"] = "1"; } else { weapon.m_customData.Remove("ValheimBB.PersistentLoadedCrossbow"); } } } private static bool CanPersistLoadedWeapon(Player player, ItemData weapon) { if ((Object)(object)player == (Object)null || !RequiresReload(weapon)) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || !inventory.ContainsItem(weapon)) { return false; } if (weapon.m_shared.m_useDurability && weapon.m_durability <= 0f) { return false; } return true; } private static void ClearLoadedWeapon(Player player) { if ((Object)(object)player != (Object)null) { s_playerLoadedWeaponRef.Invoke(player) = null; } } private static void SetLoadedWeapon(Player player, ItemData weapon) { if ((Object)(object)player != (Object)null && s_playerSetWeaponLoadedMethod != null) { s_playerSetWeaponLoadedMethod.Invoke(player, new object[1] { weapon }); } } private static bool IsReloadActionQueued(Player player) { if ((Object)(object)player == (Object)null || s_playerIsReloadActionQueuedMethod == null) { return false; } object obj = s_playerIsReloadActionQueuedMethod.Invoke(player, null); if (obj is bool) { return (bool)obj; } return false; } private static void QueueReloadAction(Player player) { if ((Object)(object)player != (Object)null && s_playerQueueReloadActionMethod != null) { s_playerQueueReloadActionMethod.Invoke(player, null); } } private static void CancelReloadAction(Player player) { if ((Object)(object)player != (Object)null && s_playerCancelReloadActionMethod != null) { s_playerCancelReloadActionMethod.Invoke(player, null); } } private static void ClearInvalidLoadedWeapon(Player player) { ItemData loadedWeapon = GetLoadedWeapon(player); if (loadedWeapon != null && !CanPersistLoadedWeapon(player, loadedWeapon)) { SetPersistentLoadedFlag(loadedWeapon, isLoaded: false); ClearLoadedWeapon(player); } } private static void ApplyMuckshakeFoodTweaks(ObjectDB objectDb) { try { if (!((Object)(object)objectDb == (Object)null) && IsFeatureEnabled(EnableMuckshakeFoodTweak) && TryApplyMuckshakeFoodTweak(objectDb, out var previousHealthRegen, out var resolvedPrefabName)) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[ValheimBB] Updated Muckshake health regen on '{resolvedPrefabName}' from {previousHealthRegen:0.#} to {2f:0.#}."); } } } catch (Exception arg) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"[ValheimBB] Error while updating Muckshake food stats: {arg}"); } } } private static bool TryApplyMuckshakeFoodTweak(ObjectDB objectDb, out float previousHealthRegen, out string resolvedPrefabName) { previousHealthRegen = 0f; resolvedPrefabName = string.Empty; if (!TryGetMuckshakeSharedData(objectDb, out var shared, out resolvedPrefabName)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[ValheimBB] Could not resolve the Muckshake prefab while applying food tweaks (tried " + string.Join(", ", s_muckshakePrefabAliases) + " and '$item_shocklatesmoothie').")); } return false; } return ApplyMuckshakeFoodTweak(shared, out previousHealthRegen); } private static bool TryGetMuckshakeSharedData(ObjectDB objectDb, out SharedData shared, out string resolvedPrefabName) { shared = null; resolvedPrefabName = string.Empty; if ((Object)(object)objectDb == (Object)null) { return false; } string[] array = s_muckshakePrefabAliases; foreach (string text in array) { GameObject itemPrefab = objectDb.GetItemPrefab(text); if (TryGetMuckshakeSharedData(itemPrefab, out shared)) { resolvedPrefabName = CleanPrefabName(((Object)itemPrefab).name); return true; } } if (objectDb.m_items == null) { return false; } foreach (GameObject item in objectDb.m_items) { if (TryGetMuckshakeSharedData(item, out shared)) { resolvedPrefabName = CleanPrefabName(((Object)item).name); return true; } } return false; } private static bool TryGetMuckshakeSharedData(GameObject prefab, out SharedData shared) { shared = ((!((Object)(object)prefab != (Object)null)) ? null : prefab.GetComponent()?.m_itemData?.m_shared); if (shared != null) { return IsMuckshakeSharedData(shared, prefab); } return false; } private static bool ApplyMuckshakeFoodTweak(SharedData shared, out float previousHealthRegen) { previousHealthRegen = 0f; if (shared == null) { return false; } previousHealthRegen = shared.m_foodRegen; if (Mathf.Approximately(previousHealthRegen, 2f)) { return false; } shared.m_foodRegen = 2f; return true; } private static void RepairMuckshakeInventory(Inventory inventory) { if (inventory == null || !IsFeatureEnabled(EnableMuckshakeFoodTweak)) { return; } foreach (ItemData allItem in inventory.GetAllItems()) { NormalizeMuckshakeItem(allItem); } } private static void NormalizeMuckshakeItem(ItemData item) { if (IsFeatureEnabled(EnableMuckshakeFoodTweak) && IsMuckshakeItem(item)) { ApplyMuckshakeFoodTweak(item.m_shared, out var _); } } private static bool IsMuckshakeItem(ItemData item) { if (item != null) { return IsMuckshakeSharedData(item.m_shared, item.m_dropPrefab); } return false; } private static bool IsMuckshakeSharedData(SharedData shared, GameObject prefab) { if (shared == null) { return false; } if (MatchesMuckshakePrefabName(CleanPrefabName(((Object)(object)prefab != (Object)null) ? ((Object)prefab).name : null))) { return true; } if (!string.Equals(shared.m_name, "$item_shocklatesmoothie", StringComparison.OrdinalIgnoreCase)) { return string.Equals(shared.m_name, "Muckshake", StringComparison.OrdinalIgnoreCase); } return true; } private static bool MatchesMuckshakePrefabName(string prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return false; } string[] array = s_muckshakePrefabAliases; foreach (string b in array) { if (string.Equals(prefabName, b, StringComparison.OrdinalIgnoreCase)) { return true; } } if (prefabName.IndexOf("shockl", StringComparison.OrdinalIgnoreCase) >= 0) { return prefabName.IndexOf("smoothie", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static void RefreshNewBuildPieceMarkers(Hud hud, Player player) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud == (Object)null || (Object)(object)player == (Object)null || !(s_hudPieceIconsField?.GetValue(hud) is IList list)) { return; } List buildPieces = player.GetBuildPieces(); if (buildPieces == null) { return; } for (int i = 0; i < list.Count; i++) { GameObject pieceIconObject = GetPieceIconObject(list[i]); if (!((Object)(object)pieceIconObject == (Object)null)) { bool visible = IsFeatureEnabled(EnableBuildAndRecipeUnlockMarkers) && i < buildPieces.Count && (Object)(object)buildPieces[i] != (Object)null && !HasSeenBuildPiece(player, buildPieces[i]); SetNewUnlockMarkerVisible(GetMarkerAnchor(pieceIconObject), hud.m_buildSelection, visible, new Vector2(-3f, -1f), new Vector2(18f, 18f)); } } } private static Piece GetBuildPieceForIcon(Hud hud, Player player, GameObject iconObject) { if ((Object)(object)hud == (Object)null || (Object)(object)player == (Object)null || (Object)(object)iconObject == (Object)null || !(s_hudPieceIconsField?.GetValue(hud) is IList list)) { return null; } List buildPieces = player.GetBuildPieces(); if (buildPieces == null) { return null; } for (int i = 0; i < list.Count; i++) { if (GetPieceIconObject(list[i]) == iconObject) { if (i >= buildPieces.Count) { return null; } return buildPieces[i]; } } return null; } private static void WireCraftRecipeHoverHandlers(InventoryGui inventoryGui) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown if ((Object)(object)inventoryGui == (Object)null || !(s_inventoryAvailableRecipesField?.GetValue(inventoryGui) is IList list)) { return; } for (int i = 0; i < list.Count; i++) { if (TryGetCraftRecipeData(list[i], out var _, out var _, out var element) && !((Object)(object)element == (Object)null)) { UIInputHandler val = element.GetComponent(); if ((Object)(object)val == (Object)null) { val = element.AddComponent(); } val.m_onPointerEnter = OnCraftRecipePointerEnter; CraftRecipeSeenClickHandler craftRecipeSeenClickHandler = element.GetComponent(); if ((Object)(object)craftRecipeSeenClickHandler == (Object)null) { craftRecipeSeenClickHandler = element.AddComponent(); } craftRecipeSeenClickHandler.InventoryGui = inventoryGui; craftRecipeSeenClickHandler.Element = element; Button component = element.GetComponent