Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EilifPaths v1.7.1
EilifPaths.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("EilifPaths")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.7.1.0")] [assembly: AssemblyInformationalVersion("1.7.1+8319365b0f1722a3a8c9a3d6d2bc1697b08ad97d")] [assembly: AssemblyProduct("EilifPaths")] [assembly: AssemblyTitle("EilifPaths")] [assembly: AssemblyVersion("1.7.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EilifPaths { internal static class BedFire { internal const float DefaultExtraRange = 8f; internal static ConfigEntry<float> ExtraRange; internal static void Bind(ConfigFile config) { ExtraRange = config.Bind<float>("Bed", "extraFireRange", 8f, "Extra metres of reach for the bed's \"needs a fire nearby\" check, added on top of the fireplace's own heat area. 0 = vanilla (the bed must sit inside the fire's heat collider, roughly touching it). 8 = the Eilif default, comfortably more than double the vanilla reach. Client-side; it only affects your own bed claims and sleeps."); } internal static string Describe() { float num = ((ExtraRange != null) ? ExtraRange.Value : 0f); if (!(num <= 0f)) { return "+" + num.ToString("0.##", CultureInfo.InvariantCulture) + "m"; } return "vanilla"; } } [HarmonyPatch(typeof(Bed), "CheckFire")] internal static class Patch_Bed_CheckFire { private static bool Prefix(Bed __instance, ref bool __result) { //IL_0039: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) try { ConfigEntry<float> extraRange = BedFire.ExtraRange; if (extraRange == null) { return true; } float value = extraRange.Value; if (!(value > 0f)) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } Vector3 position = ((Component)__instance).transform.position; EffectArea val = EffectArea.IsPointInsideArea(position, (Type)1, value); if (!Object.op_Implicit((Object)(object)val)) { return true; } LogHit(position, val, value); __result = true; return false; } catch (Exception ex) { try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] bed fire check failed, using vanilla: " + ex.Message)); } } catch { } return true; } } private static void LogHit(Vector3 bedPos, EffectArea area, float extra) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) try { if (EilifPathsPlugin.Log != null) { float num = Vector3.Distance(bedPos, ((Component)area).transform.position); float radius = area.GetRadius(); EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] bed fire check passed with +" + extra.ToString("0.##", CultureInfo.InvariantCulture) + "m: heat source '" + ((Object)area).name + "' at " + num.ToString("0.##", CultureInfo.InvariantCulture) + "m, its own heat radius " + radius.ToString("0.##", CultureInfo.InvariantCulture) + "m.")); } } catch { } } } [BepInPlugin("net.eilif.paths", "Eilif Paths", "1.7.1")] public class EilifPathsPlugin : BaseUnityPlugin { public enum PathType { None, Path, PavedRoad, Wood, Stone, Iron, HardWood } public const string PluginGuid = "net.eilif.paths"; public const string PluginName = "Eilif Paths"; public const string PluginVersion = "1.7.1"; private const string OldModGuid = "Menthus.bepinex.plugins.UsefulPaths"; internal static ManualLogSource Log; private const float GroundCheckRate = 0.4f; internal static readonly Dictionary<PathType, ConfigEntry<float>> Movement = new Dictionary<PathType, ConfigEntry<float>>(); internal static readonly Dictionary<PathType, ConfigEntry<float>> StaminaDrain = new Dictionary<PathType, ConfigEntry<float>>(); internal static readonly Dictionary<PathType, ConfigEntry<float>> ActionStamina = new Dictionary<PathType, ConfigEntry<float>>(); private static readonly string[] ExpectedCoreClasses = new string[8] { "Patch_GetJogSpeedFactor", "Patch_GetRunSpeedFactor", "Patch_UseStamina", "Patch_UpdateWalking", "Patch_Bed_CheckFire", "Patch_StationExtension_Awake", "Patch_Minimap_UpdateExplore", "Patch_Player_UpdateStats" }; private static readonly string[] ExpectedFallbackClasses = new string[17] { "Patch_VPF_Fireplace_Awake", "Patch_VPF_CookingStation_UpdateCooking", "Patch_VPF_Smelter_UpdateSmelter", "Patch_VPF_ShieldGenerator_Start", "Patch_VPF_ShieldGenerator_OnProjectileHit", "Patch_VPF_ShieldGenerator_RPC_Attack", "Patch_VPF_CraftingStation_Start", "Patch_VPF_CraftingStation_CheckUsable", "Patch_VPF_StationExtension_Awake", "Patch_VPF_DropTable_GetDropList", "Patch_VPF_Pickable_RPC_Pick", "Patch_VPF_CharacterDrop_GenerateDropList", "Patch_VPF_Talker_Awake", "Patch_VPF_WearNTear_UpdateWear", "Patch_VPF_Player_Repair", "Patch_VPF_ItemDrop_Awake", "Patch_VPF_GameCamera_Awake" }; private const double GroundFaultCooldownSeconds = 60.0; private DateTime _groundFaultLastUtc = DateTime.MinValue; private int _groundFaultSuppressed; internal static PathType Current { get; private set; } = PathType.None; internal static bool OldModPresent { get; private set; } private void BindSurface(PathType type, float move, float stam, float action) { string text = type.ToString(); Movement[type] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "movement", move, "Speed multiplier while on " + text + " (1.0 = vanilla, >1 = faster)."); StaminaDrain[type] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "staminadrain", stam, "Stamina-cost multiplier for ordinary movement while on " + text + ": running, jumping, swimming, dodging, being encumbered (1.0 = vanilla, <1 = drains less)."); ActionStamina[type] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "actionstamina", action, "Stamina-cost multiplier for tools and weapons while on " + text + ": attacks, blocking, bow draw, building, hoe and cultivator terrain work, repairs, fishing. 1 = vanilla cost, 0 = free."); } private void Awake() { //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindSurface(PathType.Path, 1.4f, 0.25f, 1f); BindSurface(PathType.PavedRoad, 1.4f, 0.25f, 1f); BindSurface(PathType.Wood, 1.4f, 0.25f, 0f); BindSurface(PathType.Stone, 1.4f, 0.25f, 0f); BindSurface(PathType.Iron, 1.4f, 0.25f, 0f); BindSurface(PathType.HardWood, 1.4f, 0.25f, 0f); BedFire.Bind(((BaseUnityPlugin)this).Config); StationRange.Bind(((BaseUnityPlugin)this).Config); Exploration.Bind(((BaseUnityPlugin)this).Config); SwimRegen.Bind(((BaseUnityPlugin)this).Config); VPlusFallback.Bind(((BaseUnityPlugin)this).Config); VPlusFallback.Refuse(); OldModPresent = Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("Menthus.bepinex.plugins.UsefulPaths"); if (OldModPresent) { Log.LogWarning((object)"========================================================================"); Log.LogWarning((object)"[EilifPaths] The OLD 'Useful_Paths' (Menthus.bepinex.plugins.UsefulPaths)"); Log.LogWarning((object)"[EilifPaths] mod is ALSO loaded. It is superseded by EilifPaths and its"); Log.LogWarning((object)"[EilifPaths] path/road detection is broken on current Valheim. To avoid"); Log.LogWarning((object)"[EilifPaths] DOUBLE-APPLYING bonuses on built floors, EilifPaths is ceding"); Log.LogWarning((object)"[EilifPaths] Wood/Stone/Iron/HardWood floors to the old mod and handling"); Log.LogWarning((object)"[EilifPaths] ONLY dirt Path + PavedRoad here. >>> Please DISABLE Useful_Paths"); Log.LogWarning((object)"[EilifPaths] in the r2modman UI (and re-export the pack) for correct values."); Log.LogWarning((object)"========================================================================"); } ((MonoBehaviour)this).InvokeRepeating("UpdateGround", 0f, 0.4f); Harmony val = new Harmony("net.eilif.paths"); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); bool active = VPlusFallback.Active; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(EilifPathsPlugin).Assembly); foreach (Type type in typesFromAssembly) { string text = "?"; try { if (!(type == null)) { text = type.Name; if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0 && (!text.StartsWith("Patch_VPF_", StringComparison.Ordinal) || active)) { val.CreateClassProcessor(type).Patch(); hashSet.Add(text); } } } catch (Exception ex) { Log.LogError((object)("[EilifPaths] could not apply patch class " + text + ": " + ex.Message + " -> " + FeatureOf(text) + ".")); } } if (active) { Log.LogInfo((object)("[EilifPaths] VPlusFallback patch classes: " + CountApplied(hashSet, ExpectedFallbackClasses) + "/" + ExpectedFallbackClasses.Length + " applied.")); ReportMissing(hashSet, ExpectedFallbackClasses); } ToolStamina.Apply(val); VPlusFallback.Report(); Log.LogInfo((object)("[EilifPaths] Eilif Paths v1.7.1 loaded. Surfaces (speed / movement stamina / tool stamina): Path x" + F(Movement[PathType.Path]) + "/x" + F(StaminaDrain[PathType.Path]) + "/x" + F(ActionStamina[PathType.Path]) + ", PavedRoad x" + F(Movement[PathType.PavedRoad]) + "/x" + F(StaminaDrain[PathType.PavedRoad]) + "/x" + F(ActionStamina[PathType.PavedRoad]) + ", floors x" + F(Movement[PathType.Wood]) + "/x" + F(StaminaDrain[PathType.Wood]) + "/x" + F(ActionStamina[PathType.Wood]) + ". Polling every " + 0.4f.ToString("0.0", CultureInfo.InvariantCulture) + "s. Bed fire range: " + BedFire.Describe() + ". Workstation attachment range: " + StationRange.Describe() + ". Map discovery: " + Exploration.Describe() + ". Swim stamina regen: " + SwimRegen.Describe() + ". Core patch classes: " + CountApplied(hashSet, ExpectedCoreClasses) + "/" + ExpectedCoreClasses.Length + " applied.")); ReportMissing(hashSet, ExpectedCoreClasses); } private static string FeatureOf(string patchClass) { switch (patchClass) { case "Patch_GetJogSpeedFactor": return "the jog-speed bonus on paths, roads and floors"; case "Patch_GetRunSpeedFactor": return "the run-speed bonus on paths, roads and floors"; case "Patch_UseStamina": return "the ENTIRE stamina discount (movement and tools alike)"; case "Patch_UpdateWalking": return "the walking-speed bonus (jog and run are unaffected)"; case "Patch_Bed_CheckFire": return "the widened bed 'needs a fire nearby' reach"; case "Patch_StationExtension_Awake": return "the extra crafting-station attachment reach"; case "Patch_Minimap_UpdateExplore": return "the wider map-discovery radius on foot and while sailing"; case "Patch_Player_UpdateStats": return "stamina recovery while swimming and treading water"; case "Patch_VPF_Fireplace_Awake": return "infinite fireplace and torch fuel"; case "Patch_VPF_CookingStation_UpdateCooking": return "infinite oven fuel"; case "Patch_VPF_Smelter_UpdateSmelter": return "infinite hot tub fuel"; case "Patch_VPF_ShieldGenerator_Start": case "Patch_VPF_ShieldGenerator_OnProjectileHit": case "Patch_VPF_ShieldGenerator_RPC_Attack": return "one of the three shield-generator refuel points"; case "Patch_VPF_CraftingStation_Start": return "the 30m station build range and its no-spawn bubble"; case "Patch_VPF_CraftingStation_CheckUsable": return "no-roof crafting"; case "Patch_VPF_StationExtension_Awake": return "the V+ station attachment range (the [Workstation] bonus still applies)"; case "Patch_VPF_DropTable_GetDropList": return "the gathering bonus on trees, rocks and ore"; case "Patch_VPF_Pickable_RPC_Pick": return "the picking bonus on berries, mushrooms and cores"; case "Patch_VPF_CharacterDrop_GenerateDropList": return "the creature loot-amount bonus"; case "Patch_VPF_Talker_Awake": return "the lifted shout range (1.0 still routes /s to everyone)"; case "Patch_VPF_WearNTear_UpdateWear": return "no rain or water erosion damage on buildings"; case "Patch_VPF_Player_Repair": return "area repair (single-piece hammer repair still works)"; case "Patch_VPF_ItemDrop_Awake": return "floating dropped items"; case "Patch_VPF_GameCamera_Awake": return "the wider camera zoom-out and field of view"; default: return "an unnamed feature"; } } private static int CountApplied(HashSet<string> applied, string[] roster) { int num = 0; for (int i = 0; i < roster.Length; i++) { if (applied.Contains(roster[i])) { num++; } } return num; } private static void ReportMissing(HashSet<string> applied, string[] roster) { for (int i = 0; i < roster.Length; i++) { if (!applied.Contains(roster[i])) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("[EilifPaths] MISSING patch class " + roster[i] + " - " + FeatureOf(roster[i]) + " is not active. Re-check that method against this game build with ilspycmd and rebuild.")); } } } } private static string F(ConfigEntry<float> c) { return c.Value.ToString("0.##", CultureInfo.InvariantCulture); } private void UpdateGround() { try { UpdateGroundCore(); } catch (Exception ex) { try { SetCurrent(PathType.None); DateTime utcNow = DateTime.UtcNow; if ((utcNow - _groundFaultLastUtc).TotalSeconds < 60.0) { _groundFaultSuppressed++; return; } int groundFaultSuppressed = _groundFaultSuppressed; _groundFaultSuppressed = 0; _groundFaultLastUtc = utcNow; if (Log != null) { Log.LogWarning((object)("[EilifPaths] ground poll failed: " + ex.Message + ((groundFaultSuppressed > 0) ? (" (+" + groundFaultSuppressed + " more in the last minute)") : "") + " (surface reset to None, i.e. vanilla speed and stamina).")); } } catch { } } } private void UpdateGroundCore() { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: 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_00c4: 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected I4, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) ToolStamina.SanityReset(); VPlusFallback.Tick(0.4f); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { SetCurrent(PathType.None); return; } PathType pathType = PathType.None; if (!OldModPresent) { try { Collider lastGroundCollider = ((Character)localPlayer).GetLastGroundCollider(); if ((Object)(object)lastGroundCollider != (Object)null) { WearNTear componentInParent = ((Component)lastGroundCollider).GetComponentInParent<WearNTear>(); if ((Object)(object)componentInParent != (Object)null) { MaterialType materialType = componentInParent.m_materialType; switch ((int)materialType) { case 0: pathType = PathType.Wood; break; case 1: pathType = PathType.Stone; break; case 2: pathType = PathType.Iron; break; case 3: pathType = PathType.HardWood; break; } } } } catch (Exception ex) { Log.LogWarning((object)("[EilifPaths] floor check failed: " + ex.Message)); } } if (pathType == PathType.None) { try { Vector3 position = ((Component)localPlayer).transform.position; Heightmap val = Heightmap.FindHeightmap(position); if ((Object)(object)val != (Object)null) { Vector3 val2 = position; val2.x -= 0.5f; val2.z -= 0.5f; int num = default(int); int num2 = default(int); val.WorldToVertexMask(val2, ref num, ref num2); Color paintMask = val.GetPaintMask(num, num2); if (paintMask.b > 0.5f) { pathType = PathType.PavedRoad; } else if (paintMask.r > 0.5f) { pathType = PathType.Path; } } } catch (Exception ex2) { Log.LogWarning((object)("[EilifPaths] paint check failed: " + ex2.Message)); } } SetCurrent(pathType); } private static void SetCurrent(PathType t) { if (t != Current) { Current = t; if (t == PathType.None) { Log.LogInfo((object)"[EilifPaths] terrain: None (vanilla speed/stamina)"); return; } float num = Mult(Movement, t); float num2 = Mult(StaminaDrain, t); float num3 = Mult(ActionStamina, t); Log.LogInfo((object)(string.Format("[EilifPaths] terrain: {0} (x{1} speed, ", t, num.ToString("0.##", CultureInfo.InvariantCulture)) + "x" + num2.ToString("0.##", CultureInfo.InvariantCulture) + " movement stamina, x" + num3.ToString("0.##", CultureInfo.InvariantCulture) + " tool stamina)")); } } private static float Mult(Dictionary<PathType, ConfigEntry<float>> table, PathType surface) { if (surface == PathType.None || table == null) { return 1f; } if (!table.TryGetValue(surface, out var value) || value == null) { return 1f; } float value2 = value.Value; if (float.IsNaN(value2) || float.IsInfinity(value2) || value2 < 0f) { return 1f; } return value2; } internal static float MoveMult() { return Mult(Movement, Current); } internal static float StamMult() { return Mult(StaminaDrain, Current); } internal static float ActionMult() { return Mult(ActionStamina, Current); } internal static float CostMult() { if (Current == PathType.None) { return 1f; } if (ToolStamina.Active) { return ActionMult(); } if (ToolStamina.Degraded) { return Mathf.Max(ActionMult(), StamMult()); } return StamMult(); } } [HarmonyPatch(typeof(Player), "GetJogSpeedFactor")] internal static class Patch_GetJogSpeedFactor { private static void Postfix(ref float __result) { try { if (EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __result *= EilifPathsPlugin.MoveMult(); } } catch { } } } [HarmonyPatch(typeof(Player), "GetRunSpeedFactor")] internal static class Patch_GetRunSpeedFactor { private static void Postfix(ref float __result) { try { if (EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __result *= EilifPathsPlugin.MoveMult(); } } catch { } } } [HarmonyPatch(typeof(Player), "UseStamina")] internal static class Patch_UseStamina { private static void Prefix(Player __instance, ref float v) { try { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { v *= EilifPathsPlugin.CostMult(); } } catch { } } } [HarmonyPatch(typeof(Character), "UpdateWalking")] internal static class Patch_UpdateWalking { private static void Prefix(Character __instance, out float __state) { __state = float.NaN; try { if (!((Object)(object)__instance == (Object)null)) { __state = __instance.m_walkSpeed; if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __instance.m_walkSpeed *= EilifPathsPlugin.MoveMult(); } } } catch { } } private static void Finalizer(Character __instance, float __state) { try { if (!((Object)(object)__instance == (Object)null) && !float.IsNaN(__state) && !(__state <= 0f)) { __instance.m_walkSpeed = __state; } } catch { } } } internal static class Exploration { internal const string Section = "Exploration"; internal const float DefaultOnFoot = 2f; internal const float DefaultSailing = 5f; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> OnFootMultiplier; internal static ConfigEntry<float> SailingMultiplier; private static bool _lastSailing; private static bool _lastSailingKnown; internal static bool Active { get { try { return Enabled != null && Enabled.Value; } catch { return false; } } } internal static void Bind(ConfigFile config) { Enabled = config.Bind<bool>("Exploration", "enabled", true, "Widen the circle of map the game uncovers around you as you travel. false = vanilla discovery radius, and no hook is applied to it at all. Client-side: it changes your own map, nobody else's."); OnFootMultiplier = config.Bind<float>("Exploration", "onFootMultiplier", 2f, "How much wider the discovery circle is while you are NOT on a ship: walking, running, riding, swimming. 1 = vanilla (100 m in this build), 2 = the Eilif default, i.e. twice as far. This also sets how much map the [VPlusFallback] ShareExploration pass reveals around each other viking, so a crew mate's circle matches your own. THIS KNOB MOVES IN STEPS, NOT SMOOTHLY: the game uncovers whole 64 m fog pixels (ceil(radius / 64)), so vanilla 100 m is 2 pixels, 2 is 4 and 5 is 8. Every value from 1 to 1.28 rounds to the same 2 pixels and changes nothing at all. If a small increase looks like it did nothing, it did nothing: go up a whole step."); SailingMultiplier = config.Bind<float>("Exploration", "sailingMultiplier", 5f, "How much wider the discovery circle is while you are on a ship, at the helm or standing on the deck. 1 = vanilla, 5 = the Eilif default, i.e. five times as far, so one coastal run charts a wide band of sea. Set it equal to onFootMultiplier to stop treating sailing specially. Steps in whole 64 m fog pixels, same as onFootMultiplier: see the note there before you retune it by a tenth."); } private static float Safe(ConfigEntry<float> entry) { if (entry == null) { return 1f; } float value = entry.Value; if (float.IsNaN(value) || float.IsInfinity(value) || !(value > 0f)) { return 1f; } return value; } internal static bool IsSailing(Player player) { try { if ((Object)(object)player == (Object)null) { return false; } if (((Character)player).IsAttachedToShip()) { return true; } return (Object)(object)((Character)player).GetStandingOnShip() != (Object)null; } catch { return false; } } internal static float MultiplierFor(Player player) { if (!Active) { return 1f; } if (!IsSailing(player)) { return Safe(OnFootMultiplier); } return Safe(SailingMultiplier); } internal static float SharedMultiplier() { if (!Active) { return 1f; } return Safe(OnFootMultiplier); } internal static string Describe() { if (!Active) { return "vanilla"; } return "x" + F(Safe(OnFootMultiplier)) + " on foot, x" + F(Safe(SailingMultiplier)) + " sailing"; } private static string F(float v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } internal static void NoteState(bool sailing, float radiusBefore, float radiusAfter) { try { if (!_lastSailingKnown || _lastSailing != sailing) { _lastSailingKnown = true; _lastSailing = sailing; if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] map discovery: " + (sailing ? "sailing" : "on foot") + " (" + F(radiusBefore) + "m -> " + F(radiusAfter) + "m).")); } } } catch { } } } [HarmonyPatch(typeof(Minimap), "UpdateExplore", new Type[] { typeof(float), typeof(Player) })] internal static class Patch_Minimap_UpdateExplore { private static void Prefix(Minimap __instance, out float __state) { __state = float.NaN; try { if ((Object)(object)__instance == (Object)null || !Exploration.Active) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float exploreRadius = __instance.m_exploreRadius; bool sailing = Exploration.IsSailing(localPlayer); float num = Exploration.MultiplierFor(localPlayer); if (num == 1f) { Exploration.NoteState(sailing, exploreRadius, exploreRadius); return; } float num2 = exploreRadius * num; if (!float.IsNaN(num2) && !float.IsInfinity(num2) && num2 > 0f) { __state = exploreRadius; __instance.m_exploreRadius = num2; Exploration.NoteState(sailing, exploreRadius, num2); } } catch { } } private static void Finalizer(Minimap __instance, float __state) { try { if (!((Object)(object)__instance == (Object)null) && !float.IsNaN(__state) && !(__state <= 0f)) { __instance.m_exploreRadius = __state; } } catch { } } } internal static class StationRange { internal const float DefaultExtraRange = 10f; internal static ConfigEntry<float> ExtraRange; private static readonly HashSet<string> Reported = new HashSet<string>(); internal static void Bind(ConfigFile config) { ExtraRange = config.Bind<float>("Workstation", "extraAttachmentRange", 10f, "Extra metres of reach between a crafting station and its upgrades/attachments, for EVERY station (workbench, forge, black forge, galdr table, artisan table, ...). Added on top of each attachment piece's own built-in distance, which is 5 m for most of them, so the default 10 gives about 15 m. Applies to both halves of the rule at once: where the game lets you place the attachment, and whether the station counts it towards its level. 0 = vanilla. Client-side; it only affects your own placement and your own crafting UI."); } internal static string Describe() { float v = ((ExtraRange != null) ? ExtraRange.Value : 0f); if (!IsActive(v)) { return "vanilla"; } return "+" + v.ToString("0.##", CultureInfo.InvariantCulture) + "m"; } internal static bool IsActive(float v) { if (v > 0f && !float.IsNaN(v)) { return !float.IsInfinity(v); } return false; } internal static void LogOnce(string prefabName, float baseRange, float newRange) { try { if (EilifPathsPlugin.Log != null) { string text = prefabName ?? "?"; if (!Reported.Contains(text)) { Reported.Add(text); EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] workstation attachment '" + text + "': reach " + baseRange.ToString("0.##", CultureInfo.InvariantCulture) + "m -> " + newRange.ToString("0.##", CultureInfo.InvariantCulture) + "m.")); } } } catch { } } } [HarmonyPatch(typeof(StationExtension), "Awake")] internal static class Patch_StationExtension_Awake { private static void Postfix(StationExtension __instance) { try { ConfigEntry<float> extraRange = StationRange.ExtraRange; if (extraRange == null) { return; } float value = extraRange.Value; if (StationRange.IsActive(value) && !((Object)(object)__instance == (Object)null)) { float maxStationDistance = __instance.m_maxStationDistance; if (!float.IsNaN(maxStationDistance) && !float.IsInfinity(maxStationDistance)) { StationRange.LogOnce(newRange: __instance.m_maxStationDistance = maxStationDistance + value, prefabName: CleanName(((Object)__instance).name), baseRange: maxStationDistance); } } } catch (Exception ex) { try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] workstation attachment range failed, using vanilla: " + ex.Message)); } } catch { } } } private static string CleanName(string name) { if (string.IsNullOrEmpty(name)) { return "?"; } int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num <= 0) { return name; } return name.Substring(0, num); } } internal static class SwimRegen { internal const string Section = "Swim"; internal const float DefaultTreading = 1f; internal const float DefaultSwimming = 0.5f; internal const float DefaultThreshold = 0.5f; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> RegenWhileTreading; internal static ConfigEntry<float> RegenWhileSwimming; internal static ConfigEntry<float> TreadingSpeedThreshold; private static bool _timerRefTried; private static FieldRef<Player, float> _regenTimerRef; private static bool _warned; internal static bool Active { get { try { return Enabled != null && Enabled.Value; } catch { return false; } } } internal static void Bind(ConfigFile config) { Enabled = config.Bind<bool>("Swim", "enabled", true, "Regenerate stamina in deep water, which vanilla refuses to do at all. false = vanilla, and no hook is applied. This never changes the cost of swimming, only the recovery; the drain stays whatever ValheimPlus [Stamina] swimStaminaDrain says."); RegenWhileTreading = config.Bind<float>("Swim", "regenWhileTreading", 1f, "Stamina regeneration while you are in the water and NOT moving, as a multiple of the normal on-land rate. 1 = the Eilif default, i.e. treading water recovers exactly as fast as standing still on a beach. 0 = vanilla (no recovery at all). The usual one-second pause after spending stamina still applies here, exactly as it does on land."); RegenWhileSwimming = config.Bind<float>("Swim", "regenWhileSwimming", 0.5f, "Stamina regeneration while you are actively swimming, as a multiple of the normal rate. 0.5 = the Eilif default, i.e. half rate against the swim drain, which is left alone. 0 = vanilla (no recovery while stroking). NOTE this half deliberately ignores the one-second pause after spending: the swim drain re-arms that pause every single tick, so honouring it would mean this setting could never do anything."); TreadingSpeedThreshold = config.Bind<float>("Swim", "treadingSpeedThreshold", 0.5f, "Horizontal speed in metres per second below which you count as treading water rather than swimming. Vertical bobbing is ignored. Swim speed is about 2 m/s, so the default 0.5 leaves plenty of room either side; raise it if wave push makes you read as swimming while you float, lower it if a gentle drift reads as treading."); } private static float Safe(ConfigEntry<float> entry, float fallback) { if (entry == null) { return fallback; } float value = entry.Value; if (float.IsNaN(value) || float.IsInfinity(value) || value < 0f) { return fallback; } return value; } internal static string Describe() { if (!Active) { return "vanilla"; } return "x" + F(Safe(RegenWhileTreading, 0f)) + " treading, x" + F(Safe(RegenWhileSwimming, 0f)) + " swimming"; } private static string F(float v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } private static bool TryGetRegenTimer(Player player, out float timer) { timer = 0f; if (!_timerRefTried) { _timerRefTried = true; try { _regenTimerRef = AccessTools.FieldRefAccess<Player, float>("m_staminaRegenTimer"); } catch (Exception ex) { _regenTimerRef = null; Warn("Player.m_staminaRegenTimer not reachable (" + ex.Message + "); stamina recovery while TREADING water is off for this session. Recovery while actively swimming is unaffected."); } } if (_regenTimerRef == null) { return false; } timer = _regenTimerRef.Invoke(player); return true; } private static void Warn(string line) { try { if (!_warned) { _warned = true; if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] swim stamina: " + line)); } } } catch { } } private static bool IsStroking(Player player) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) try { Vector3 velocity = ((Character)player).GetVelocity(); float num = velocity.x * velocity.x + velocity.z * velocity.z; float num2 = Safe(TreadingSpeedThreshold, 0.5f); return num > num2 * num2; } catch { return true; } } internal static void Regenerate(Player player, float dt) { if (!Active || (Object)(object)player == (Object)null || !(dt > 0f) || float.IsNaN(dt) || float.IsInfinity(dt) || ((Character)player).InIntro() || ((Character)player).IsTeleporting() || !((Character)player).IsSwimming() || ((Character)player).IsOnGround() || ((Character)player).InAttack() || ((Character)player).InDodge() || ((Character)player).IsEncumbered()) { return; } float maxStamina = ((Character)player).GetMaxStamina(); if (!(maxStamina > 0f) || float.IsNaN(maxStamina) || float.IsInfinity(maxStamina)) { return; } float stamina = player.GetStamina(); if (!(stamina < maxStamina)) { return; } bool flag = IsStroking(player); float num = (flag ? Safe(RegenWhileSwimming, 0f) : Safe(RegenWhileTreading, 0f)); if (num > 0f && (flag || (TryGetRegenTimer(player, out var timer) && !(timer > 0f)))) { float num2 = (((Character)player).IsBlocking() ? 0.8f : 1f); float num3 = (player.m_staminaRegen + (1f - stamina / maxStamina) * player.m_staminaRegen * player.m_staminaRegenTimeMultiplier) * num2; float num4 = 1f; SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan != null) { sEMan.ModifyStaminaRegen(ref num4); } float num5 = num3 * num4 * dt * Game.m_staminaRegenRate * num; if (!float.IsNaN(num5) && !float.IsInfinity(num5) && num5 > 0f) { ((Character)player).AddStamina(num5); } } } } [HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })] internal static class Patch_Player_UpdateStats { private static void Postfix(Player __instance, float dt) { try { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { SwimRegen.Regenerate(__instance, dt); } } catch { } } } internal static class ToolStamina { private sealed class Site { internal readonly Type Owner; internal readonly string Method; internal readonly Type[] Args; internal readonly string What; internal Site(Type owner, string method, Type[] args, string what) { Owner = owner; Method = method; Args = args; What = what; } internal string Label() { return ((Owner != null) ? Owner.Name : "?") + "." + Method; } } private static int s_depth; private static bool s_leakReported; private static readonly Func<Site>[] SiteFactories = new Func<Site>[9] { () => new Site(typeof(Attack), "Update", new Type[1] { typeof(float) }, "melee and ranged swings"), () => new Site(typeof(Attack), "FireProjectileBurst", new Type[0], "projectile bursts"), () => new Site(typeof(Humanoid), "BlockAttack", new Type[2] { typeof(HitData), typeof(Character) }, "blocking and parry"), () => new Site(typeof(Player), "UpdatePlacement", new Type[2] { typeof(bool), typeof(float) }, "building, hoe and cultivator, piece removal"), () => new Site(typeof(Player), "Repair", new Type[2] { typeof(ItemData), typeof(Piece) }, "repairs"), () => new Site(typeof(Player), "UpdateAttackBowDraw", new Type[2] { typeof(ItemData), typeof(float) }, "bow and crossbow draw"), () => new Site(typeof(Player), "UpdateActionQueue", new Type[1] { typeof(float) }, "crossbow reload"), () => new Site(typeof(FishingFloat), "FixedUpdate", new Type[0], "fishing"), () => new Site(typeof(SE_Harpooned), "UpdateStatusEffect", new Type[1] { typeof(float) }, "harpoon drag") }; private static readonly string[] SiteLabels = new string[9] { "Attack.Update", "Attack.FireProjectileBurst", "Humanoid.BlockAttack", "Player.UpdatePlacement", "Player.Repair", "Player.UpdateAttackBowDraw", "Player.UpdateActionQueue", "FishingFloat.FixedUpdate", "SE_Harpooned.UpdateStatusEffect" }; internal static bool Active => s_depth > 0; internal static bool Degraded { get; private set; } private static void ScopePrefix() { try { if (s_depth < int.MaxValue) { s_depth++; } } catch { } } private static void ScopeFinalizer() { try { if (s_depth > 0) { s_depth--; } } catch { } } internal static void SanityReset() { if (s_depth == 0) { return; } int num = s_depth; s_depth = 0; if (s_leakReported) { return; } s_leakReported = true; try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] tool/weapon stamina context was left open (depth " + num + ") and has been reset. Stamina costs are correct again; this notice is logged once.")); } } catch { } } internal static void Apply(Harmony harmony) { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown try { if (harmony == null) { MarkDegraded("Harmony instance was null"); return; } MethodInfo methodInfo = AccessTools.Method(typeof(ToolStamina), "ScopePrefix", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ToolStamina), "ScopeFinalizer", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { MarkDegraded("the patch bodies themselves could not be resolved"); return; } int num = 0; int num2 = SiteFactories.Length; for (int i = 0; i < num2; i++) { Site site = null; try { site = SiteFactories[i](); MethodBase methodBase = AccessTools.Method(site.Owner, site.Method, site.Args, (Type[])null); if (methodBase == null) { MarkDegraded("could not find " + site.Label() + " (" + site.What + ")"); } else { harmony.CreateProcessor(methodBase).AddPrefix(new HarmonyMethod(methodInfo)).AddFinalizer(new HarmonyMethod(methodInfo2)) .Patch(); num++; } } catch (Exception ex) { string text = ((site != null) ? (site.Label() + " (" + site.What + ")") : (SiteLabels[i] + " (its game type could not be loaded)")); MarkDegraded("could not hook " + text + ": " + ex.Message); } } try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] tool/weapon stamina hooks: " + num + "/" + num2 + " applied" + (Degraded ? " (DEGRADED - see the errors above)" : "") + ".")); } } catch { } } catch (Exception ex2) { MarkDegraded("hook setup failed: " + ex2.Message); } } private static void MarkDegraded(string reason) { Degraded = true; try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogError((object)("[EilifPaths] tool/weapon stamina hook problem: " + reason + ". Falling back to the safer of the two multipliers for charges that can no longer be classified (vanilla cost on paths and roads, 0.25 on built floors).")); } } catch { } } } internal static class VPlusFallback { internal const string Section = "VPlusFallback"; private const string ValheimPlusGuid = "org.bepinex.plugins.valheim_plus"; private const string ValheimPlusDllPrefix = "valheimplus"; internal const float VanillaAttachmentRange = 5f; internal const float ShoutDistance = 100000f; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> InfiniteFireplaceFuel; internal static ConfigEntry<bool> InfiniteOvenFuel; internal static ConfigEntry<bool> InfiniteHotTubFuel; internal static ConfigEntry<bool> InfiniteShieldGeneratorFuel; internal static ConfigEntry<float> StationBuildRange; internal static ConfigEntry<float> StationAttachmentRange; internal static ConfigEntry<bool> DisableStationRoofCheck; internal static ConfigEntry<float> GatheringBonusPercent; internal static ConfigEntry<float> PickableBonusPercent; internal static ConfigEntry<float> LootDropBonusPercent; internal static ConfigEntry<bool> ShareExploration; internal static ConfigEntry<float> ShareExplorationRadius; internal static ConfigEntry<bool> ServerWideShouts; internal static ConfigEntry<bool> NoWeatherDamage; internal static ConfigEntry<bool> AreaRepair; internal static ConfigEntry<float> AreaRepairRadius; internal static ConfigEntry<bool> ItemsFloat; internal static ConfigEntry<float> CameraMaxZoom; internal static ConfigEntry<float> CameraMaxZoomBoat; internal static ConfigEntry<float> CameraFov; private const float RecheckDelaySeconds = 8f; private static float _recheckAccum; private static bool _recheckDone; private static readonly List<string> Applied = new List<string>(); private static readonly HashSet<string> ReportedOnce = new HashSet<string>(); private static bool _nviewRefsTried; internal static FieldRef<CookingStation, ZNetView> CookingStationNView; internal static FieldRef<Smelter, ZNetView> SmelterNView; internal static FieldRef<ShieldGenerator, ZNetView> ShieldGeneratorNView; private static float _exploreAccum; private static bool _exploreTried; private static MethodInfo _exploreMethod; private static readonly object[] _exploreArgs = new object[2]; private static FieldRef<ZNet, List<PlayerInfo>> _playersRef; internal static bool ValheimPlusPresent { get; private set; } internal static string ValheimPlusEvidence { get; private set; } internal static bool Active { get { try { return Enabled != null && Enabled.Value && !ValheimPlusPresent; } catch { return false; } } } internal static void Bind(ConfigFile config) { Enabled = config.Bind<bool>("VPlusFallback", "Enabled", false, "Master switch for the ValheimPlus stand-in features. Ships OFF. Turn it on ONLY when ValheimPlus is not in the pack: with V+ installed these would stack on top of it and double every multiplier. Nothing in this section does anything while it is false."); InfiniteFireplaceFuel = config.Bind<bool>("VPlusFallback", "InfiniteFireplaceFuel", true, "Campfires, hearths, bonfires, standing and wall torches, braziers never burn out. Mirrors ValheimPlus [FireSource] fires = true / torches = true. Needs Enabled = true."); InfiniteOvenFuel = config.Bind<bool>("VPlusFallback", "InfiniteOvenFuel", true, "The stone oven never burns through its coal. Mirrors ValheimPlus [Oven] infiniteFuel. Needs Enabled = true."); InfiniteHotTubFuel = config.Bind<bool>("VPlusFallback", "InfiniteHotTubFuel", true, "The hot tub stays hot without wood. Mirrors ValheimPlus [HotTub] infiniteFuel. Needs Enabled = true."); InfiniteShieldGeneratorFuel = config.Bind<bool>("VPlusFallback", "InfiniteShieldGeneratorFuel", true, "The Ashlands shield generator never runs down. Mirrors ValheimPlus [ShieldGenerator] infiniteFuel. Needs Enabled = true."); StationBuildRange = config.Bind<float>("VPlusFallback", "StationBuildRange", 30f, "How far from a crafting station you may build, in metres, for every station type. Vanilla is 10. Mirrors ValheimPlus [Workbench] workbenchRange = 30. This also moves the station's no-monsters-spawn area to match, exactly as V+ did. 0 = leave vanilla alone. Needs Enabled = true."); StationAttachmentRange = config.Bind<float>("VPlusFallback", "StationAttachmentRange", 20f, "How far a station upgrade may sit from its station BEFORE the [Workstation] extraAttachmentRange bonus is added on top. Vanilla prefabs carry 5. Mirrors ValheimPlus [Workbench] workbenchAttachmentRange = 20, so with the shipped extraAttachmentRange of 10 the total is 30 m, the same reach the crew has today with V+ installed. 0 = leave the prefab value alone and let extraAttachmentRange do all the work. Needs Enabled = true."); DisableStationRoofCheck = config.Bind<bool>("VPlusFallback", "DisableStationRoofCheck", true, "Crafting stations work in the open, no roof needed. Mirrors ValheimPlus [Workbench] disableRoofCheck. Needs Enabled = true."); GatheringBonusPercent = config.Bind<float>("VPlusFallback", "GatheringBonusPercent", 30f, "Extra percent of wood, stone, ore, scrap and the other gathered materials from trees, rocks and ore veins. 30 = 30% more, and a fractional bonus is rolled per item, so 30% means each item has a 30% chance of a second one. Mirrors ValheimPlus [Gathering] with every material at 30, and covers the SAME material list V+ covered, nothing wider. 0 = vanilla. Needs Enabled = true."); PickableBonusPercent = config.Bind<float>("VPlusFallback", "PickableBonusPercent", 30f, "Extra percent from anything you pick by hand: berries, mushrooms, carrots, thistle, flint, stone, surtling cores, amber. Mirrors ValheimPlus [Pickable] with every category at 30. Quest items (dragon eggs, withered bones, goblin totems) are excluded, as they were in V+. 0 = vanilla. Needs Enabled = true."); LootDropBonusPercent = config.Bind<float>("VPlusFallback", "LootDropBonusPercent", 30f, "Extra percent on the amount of loot a killed creature drops. Mirrors ValheimPlus [LootDrop] lootDropAmountMultiplier = 30. NOTE this does very little on small drops: V+ truncates, so a 1-to-2 drop stays 1-to-2. Kept identical on purpose. 0 = vanilla. Needs Enabled = true."); ShareExploration = config.Bind<bool>("VPlusFallback", "ShareExploration", true, "Your map fills in around every other viking who is online and showing their position, not just around you. Mirrors the half of ValheimPlus [Map] shareMapProgression that actually runs on a dedicated server. It does NOT restore V+'s stored server-side map: that half needs a Minimap on the server, which a headless box does not have. Needs Enabled = true."); ShareExplorationRadius = config.Bind<float>("VPlusFallback", "ShareExplorationRadius", 0f, "Metres of map revealed around each other player. 0 = use the game's own radius, which is 100 in 0.221.12 and identical to what ValheimPlus [Map] exploreRadius was set to. CHANGED IN 1.6.0: whatever this settles on is then scaled by [Exploration] onFootMultiplier, so a crew mate's circle keeps matching your own instead of staying at 100 m while yours grew. That applies to an explicit number here too, not just to the 0 default: if you set 150 here and leave onFootMultiplier at 2 you get 300. Set [Exploration] onFootMultiplier to 1 if you want this number taken literally."); ServerWideShouts = config.Bind<bool>("VPlusFallback", "ServerWideShouts", true, "Shouts carry across the whole world instead of stopping at 70 metres. Mirrors ValheimPlus [Chat] defaultShoutDistance = 100000. READ THIS BEFORE GRADING IT: Valheim 1.0 already sends every /s shout to every player on the server through a routed RPC, so the chat window is server-wide with or without this. What the patch still covers is the older per-character Talker range gate, which anything speaking through Talker.Say is still subject to. Normal and whispered chat are left exactly as vanilla has them. Needs Enabled = true."); NoWeatherDamage = config.Bind<bool>("VPlusFallback", "NoWeatherDamage", true, "Buildings stop taking rain and water erosion damage. Combat damage, structural collapse from missing supports, Deep North snow load and event damage are all untouched. Mirrors ValheimPlus [Building] noWeatherDamage. Runs on whichever client owns the piece, so it protects a building only while somebody who has this on is the nearest viking to it. Needs Enabled = true."); AreaRepair = config.Bind<bool>("VPlusFallback", "AreaRepair", true, "One hammer repair click repairs every damaged piece within AreaRepairRadius instead of only the one under the crosshair. Mirrors ValheimPlus [Building] enableAreaRepair. Each extra piece still pays its own stamina and hammer durability and still passes the same ward and crafting-station checks a single repair does, and the sweep stops the moment you run out of either. Needs Enabled = true."); AreaRepairRadius = config.Bind<float>("VPlusFallback", "AreaRepairRadius", 7.5f, "Radius in metres for AreaRepair, measured from the piece under the crosshair. Mirrors ValheimPlus [Building] areaRepairRadius = 7.5, the value the crew ran. 0 = no area sweep, i.e. vanilla single-piece repair. Needs Enabled = true."); ItemsFloat = config.Bind<bool>("VPlusFallback", "ItemsFloat", true, "Dropped items float on water instead of sinking out of reach. Mirrors ValheimPlus [Items] itemsFloatInWater. Applies to items that spawn while you are the nearest viking; an item already resting on a lake bed stays there. Needs Enabled = true."); CameraMaxZoom = config.Bind<float>("VPlusFallback", "CameraMaxZoom", 100f, "How far the third-person camera may be scrolled back on foot, in metres. Vanilla is 6. Mirrors ValheimPlus [Camera] cameraMaximumZoomDistance = 100, the value the crew ran. Accepted range 1 to 100, the same window V+ enforced; a number outside it is refused and this one knob stays vanilla. 0 = leave vanilla alone. Needs Enabled = true."); CameraMaxZoomBoat = config.Bind<float>("VPlusFallback", "CameraMaxZoomBoat", 100f, "The same, for while you are steering a ship. Vanilla is 6. Mirrors ValheimPlus [Camera] cameraBoatMaximumZoomDistance = 100. Accepted range 1 to 100. 0 = leave vanilla alone. Needs Enabled = true."); CameraFov = config.Bind<float>("VPlusFallback", "CameraFov", 75f, "Field of view in degrees. Vanilla is 65, higher is wider. Mirrors ValheimPlus [Camera] cameraFOV = 75. Accepted range 1 to 140. 0 = leave vanilla alone, which is the setting to use if a wider view makes you queasy. Needs Enabled = true."); } internal static bool Refuse() { ValheimPlusEvidence = DetectValheimPlus(); ValheimPlusPresent = ValheimPlusEvidence != null; if (!ValheimPlusPresent) { return false; } ManualLogSource log = EilifPathsPlugin.Log; if (log == null) { return true; } if (Enabled == null || !Enabled.Value) { log.LogInfo((object)("[EilifPaths] VPlusFallback: ValheimPlus detected (" + ValheimPlusEvidence + "); the section is off, which is correct while V+ provides these itself.")); return true; } log.LogWarning((object)"========================================================================"); log.LogWarning((object)"[EilifPaths] [VPlusFallback] Enabled=true but ValheimPlus is STILL installed"); log.LogWarning((object)("[EilifPaths] in this profile (" + ValheimPlusEvidence + "). These features")); log.LogWarning((object)"[EilifPaths] exist only to replace V+ when it is ABSENT. Running both would"); log.LogWarning((object)"[EilifPaths] stack fuel, range and gathering bonuses on top of each other."); log.LogWarning((object)"[EilifPaths] REFUSING to apply. Either remove ValheimPlus from the pack, or"); log.LogWarning((object)"[EilifPaths] set [VPlusFallback] Enabled=false."); log.LogWarning((object)"========================================================================"); return true; } private static string DetectValheimPlus() { string text = DetectInPluginRegistry(); if (text != null) { return text; } try { string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { return null; } string[] files = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories); if (files == null) { return null; } for (int i = 0; i < files.Length; i++) { if (Normalise(Path.GetFileNameWithoutExtension(files[i])).StartsWith("valheimplus", StringComparison.Ordinal)) { return Path.GetFileName(files[i]); } } return null; } catch (Exception ex) { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] VPlusFallback: could not scan the plugin folder for ValheimPlus (" + ex.Message + "); assuming absent.")); } return null; } } private static string DetectInPluginRegistry() { try { Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; if (pluginInfos == null) { return null; } if (pluginInfos.ContainsKey("org.bepinex.plugins.valheim_plus")) { return "plugin GUID org.bepinex.plugins.valheim_plus"; } return null; } catch { return null; } } private static string Normalise(string s) { if (string.IsNullOrEmpty(s)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { if (c >= 'A' && c <= 'Z') { stringBuilder.Append((char)(c + 32)); } else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } internal static void Recheck(float dt) { if (_recheckDone) { return; } if (ValheimPlusPresent) { _recheckDone = true; return; } _recheckAccum += dt; if (_recheckAccum < 8f) { return; } _recheckDone = true; string text = DetectInPluginRegistry(); if (text == null) { return; } ValheimPlusEvidence = text; ValheimPlusPresent = true; if (Enabled != null && Enabled.Value) { ManualLogSource log = EilifPathsPlugin.Log; if (log != null) { log.LogError((object)"========================================================================"); log.LogError((object)("[EilifPaths] ValheimPlus IS loaded after all (" + text + ") but its DLL")); log.LogError((object)"[EilifPaths] file name did not look like ValheimPlus, so [VPlusFallback] had"); log.LogError((object)"[EilifPaths] already applied. Every percentage bonus is switched back to"); log.LogError((object)"[EilifPaths] vanilla NOW so gathering, picking and loot cannot double up."); log.LogError((object)"[EilifPaths] Set [VPlusFallback] Enabled=false, or drop ValheimPlus."); log.LogError((object)"========================================================================"); } } } internal static bool On(ConfigEntry<bool> entry) { if (Active && entry != null) { return entry.Value; } return false; } internal static bool Pct(ConfigEntry<float> entry, out float percent) { percent = 0f; if (!Active || entry == null) { return false; } float value = entry.Value; if (value == 0f || float.IsNaN(value) || float.IsInfinity(value)) { return false; } percent = value; return true; } internal static bool Metres(ConfigEntry<float> entry, out float metres) { metres = 0f; if (!Active || entry == null) { return false; } float value = entry.Value; if (!(value > 0f) || float.IsNaN(value) || float.IsInfinity(value)) { return false; } metres = value; return true; } internal static float ApplyPercent(float target, float percent) { if (percent <= -100f) { return 0f; } return target + target / 100f * percent; } internal static int ApplyPercentWithChance(float target, float percent) { float num = ApplyPercent(target, percent); if (num <= 0f) { return 0; } int num2 = (int)Math.Floor(num); float num3 = num - (float)num2; if (num3 > 0f && Random.value < num3) { num2++; } return num2; } internal static void Note(string line) { Applied.Add(line); } internal static void Warn(string line) { try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogWarning((object)("[EilifPaths] VPlusFallback: " + line)); } } catch { } } internal static void InfoOnce(string key, string line) { try { if (EilifPathsPlugin.Log != null && !ReportedOnce.Contains(key)) { ReportedOnce.Add(key); EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] VPlusFallback: " + line)); } } catch { } } internal static string F(float v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } private static void NoteCamera() { float metres; bool flag = Metres(CameraMaxZoom, out metres); float metres2; bool flag2 = Metres(CameraMaxZoomBoat, out metres2); float metres3; bool flag3 = Metres(CameraFov, out metres3); if (!flag && !flag2 && !flag3) { return; } string text = "camera "; if (flag || flag2) { text = text + "zooms out to " + (flag ? (F(metres) + "m on foot") : "vanilla on foot") + " and " + (flag2 ? (F(metres2) + "m on a boat") : "vanilla on a boat") + " (vanilla 6m)"; if (flag3) { text += ", "; } } if (flag3) { text = text + "field of view " + F(metres3) + " (vanilla 65)"; } Note(text + "."); } internal static void Report() { ManualLogSource log = EilifPathsPlugin.Log; if (log == null) { return; } if (!Active) { if (ValheimPlusPresent) { log.LogInfo((object)"[EilifPaths] VPlusFallback: disabled (ValheimPlus present)."); } else { log.LogWarning((object)"[EilifPaths] VPlusFallback: OFF and no ValheimPlus installed. Infinite fuel, the 30m station build range, no-roof crafting and the +30% gathering, picking and loot bonuses are NOT active. Set [VPlusFallback] Enabled = true in net.eilif.paths.cfg to restore them."); } return; } if (On(InfiniteFireplaceFuel)) { Note("fires and torches never burn out."); } if (On(InfiniteOvenFuel)) { Note("oven fuel never runs down."); } if (On(InfiniteHotTubFuel)) { Note("hot tub fuel never runs down."); } if (On(InfiniteShieldGeneratorFuel)) { Note("shield generator fuel never runs down."); } if (Metres(StationBuildRange, out var metres)) { Note("station build range " + F(metres) + "m (vanilla 10)."); } if (Metres(StationAttachmentRange, out metres)) { Note("station attachment range set to " + F(metres) + "m before [Workstation] extraAttachmentRange of " + StationRange.Describe() + " is added on top."); } if (On(DisableStationRoofCheck)) { Note("stations work without a roof."); } if (Pct(GatheringBonusPercent, out var percent)) { Note("gathering +" + F(percent) + "%."); } if (Pct(PickableBonusPercent, out percent)) { Note("picking +" + F(percent) + "%."); } if (Pct(LootDropBonusPercent, out percent)) { Note("creature loot amount +" + F(percent) + "%."); } if (On(ShareExploration)) { Note("map fills in around every online viking."); } if (On(ServerWideShouts)) { Note("shout range lifted to " + F(100000f) + "m (1.0 already routes /s to everyone)."); } if (On(NoWeatherDamage)) { Note("no rain or water erosion damage on buildings."); } if (On(AreaRepair) && Metres(AreaRepairRadius, out metres)) { Note("hammer repairs everything damaged within " + F(metres) + "m."); } if (On(ItemsFloat)) { Note("dropped items float instead of sinking."); } NoteCamera(); if (Applied.Count == 0) { log.LogInfo((object)"[EilifPaths] VPlusFallback: on, but every individual feature is off."); return; } foreach (string item in Applied) { log.LogInfo((object)("[EilifPaths] VPlusFallback: " + item)); } } internal static void ResolveNViewRefs() { if (_nviewRefsTried) { return; } _nviewRefsTried = true; try { CookingStationNView = AccessTools.FieldRefAccess<CookingStation, ZNetView>("m_nview"); } catch (Exception ex) { Warn("CookingStation.m_nview not reachable (" + ex.Message + "); oven fuel stays vanilla."); } try { SmelterNView = AccessTools.FieldRefAccess<Smelter, ZNetView>("m_nview"); } catch (Exception ex2) { Warn("Smelter.m_nview not reachable (" + ex2.Message + "); hot tub fuel stays vanilla."); } try { ShieldGeneratorNView = AccessTools.FieldRefAccess<ShieldGenerator, ZNetView>("m_nview"); } catch (Exception ex3) { Warn("ShieldGenerator.m_nview not reachable (" + ex3.Message + "); shield fuel stays vanilla."); } } internal static void FillFuel(ZNetView nview, float maxFuel, float topUpBelow, string diagKey, string diagText) { if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { ZDO zDO = nview.GetZDO(); if (zDO != null && !(zDO.GetFloat(ZDOVars.s_fuel, 0f) >= topUpBelow)) { zDO.Set(ZDOVars.s_fuel, maxFuel); InfoOnce(diagKey, diagText); } } } internal static float PeriodicTopUpLevel(float maxFuel) { float num = maxFuel - 1f; float num2 = maxFuel * 0.5f; if (!(num < num2)) { return num; } return num2; } internal static void Tick(float dt) { //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) Recheck(dt); if (!On(ShareExploration)) { return; } _exploreAccum += dt; if (_exploreAccum < 2f) { return; } _exploreAccum = 0f; try { Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } ZNet instance2 = ZNet.instance; if ((Object)(object)instance2 == (Object)null) { return; } if (!_exploreTried) { _exploreTried = true; _exploreMethod = AccessTools.Method(typeof(Minimap), "Explore", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null); if (_exploreMethod == null) { Warn("Minimap.Explore(Vector3, float) not found; ShareExploration is inert."); } try { _playersRef = AccessTools.FieldRefAccess<ZNet, List<PlayerInfo>>("m_players"); } catch (Exception ex) { Warn("ZNet.m_players not reachable (" + ex.Message + "); ShareExploration is inert."); } } if (_exploreMethod == null || _playersRef == null) { return; } List<PlayerInfo> list = _playersRef.Invoke(instance2); if (list == null || list.Count == 0) { return; } float num = instance.m_exploreRadius; if (ShareExplorationRadius != null && ShareExplorationRadius.Value > 0f) { num = ShareExplorationRadius.Value; } num *= Exploration.SharedMultiplier(); if (!(num > 0f) || float.IsNaN(num) || float.IsInfinity(num)) { return; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; for (int i = 0; i < list.Count; i++) { if (list[i].m_publicPosition) { Vector3 position2 = list[i].m_position; Vector3 val = position2 - position; if (!(((Vector3)(ref val)).sqrMagnitude < 1f)) { _exploreArgs[0] = position2; _exploreArgs[1] = num; _exploreMethod.Invoke(instance, _exploreArgs); } } } } catch (Exception ex2) { Warn("ShareExploration pass failed (" + ex2.Message + "); turning it off for this session."); _exploreMethod = null; _playersRef = null; } } } [HarmonyPatch(typeof(Fireplace), "Awake")] internal static class Patch_VPF_Fireplace_Awake { private static void Postfix(Fireplace __instance) { try { if (VPlusFallback.On(VPlusFallback.InfiniteFireplaceFuel) && !((Object)(object)__instance == (Object)null)) { __instance.m_infiniteFuel = true; VPlusFallback.InfoOnce("fire", "fires and torches set to infinite fuel."); } } catch (Exception ex) { VPlusFallback.Warn("fireplace fuel: " + ex.Message); } } } [HarmonyPatch(typeof(CookingStation), "UpdateCooking")] internal static class Patch_VPF_CookingStation_UpdateCooking { private static void Prefix(CookingStation __instance) { try { if (VPlusFallback.On(VPlusFallback.InfiniteOvenFuel) && !((Object)(object)__instance == (Object)null) && __instance.m_useFuel) { VPlusFallback.ResolveNViewRefs(); FieldRef<CookingStation, ZNetView> cookingStationNView = VPlusFallback.CookingStationNView; if (cookingStationNView != null) { float maxFuel = __instance.m_maxFuel; VPlusFallback.FillFuel(cookingStationNView.Invoke(__instance), maxFuel, VPlusFallback.PeriodicTopUpLevel(maxFuel), "oven", "oven topped up to full fuel and held there."); } } } catch (Exception ex) { VPlusFallback.Warn("oven fuel: " + ex.Message); } } } [HarmonyPatch(typeof(Smelter), "UpdateSmelter")] internal static class Patch_VPF_Smelter_UpdateSmelter { internal const string HotTubName = "$piece_bathtub"; private static void Prefix(Smelter __instance) { try { if (VPlusFallback.On(VPlusFallback.InfiniteHotTubFuel) && !((Object)(object)__instance == (Object)null) && __instance.m_maxFuel > 0 && string.Equals(__instance.m_name, "$piece_bathtub", StringComparison.Ordinal)) { VPlusFallback.ResolveNViewRefs(); FieldRef<Smelter, ZNetView> smelterNView = VPlusFallback.SmelterNView; if (smelterNView != null) { float maxFuel = __instance.m_maxFuel; VPlusFallback.FillFuel(smelterNView.Invoke(__instance), maxFuel, VPlusFallback.PeriodicTopUpLevel(maxFuel), "hottub", "hot tub topped up to full fuel and held there."); } } } catch (Exception ex) { VPlusFallback.Warn("hot tub fuel: " + ex.Message); } } } [HarmonyPatch(typeof(ShieldGenerator), "Start")] internal static class Patch_VPF_ShieldGenerator_Start { private static void Postfix(ShieldGenerator __instance) { ShieldFuel.Refill(__instance); } } [HarmonyPatch(typeof(ShieldGenerator), "OnProjectileHit")] internal static class Patch_VPF_ShieldGenerator_OnProjectileHit { private static void Postfix(ShieldGenerator __instance) { ShieldFuel.Refill(__instance); } } [HarmonyPatch(typeof(ShieldGenerator), "RPC_Attack")] internal static class Patch_VPF_ShieldGenerator_RPC_Attack { private static void Postfix(ShieldGenerator __instance) { ShieldFuel.Refill(__instance); } } internal static class ShieldFuel { internal static void Refill(ShieldGenerator instance) { try { if (VPlusFallback.On(VPlusFallback.InfiniteShieldGeneratorFuel) && !((Object)(object)instance == (Object)null) && instance.m_maxFuel > 0) { VPlusFallback.ResolveNViewRefs(); FieldRef<ShieldGenerator, ZNetView> shieldGeneratorNView = VPlusFallback.ShieldGeneratorNView; if (shieldGeneratorNView != null) { float num = instance.m_maxFuel; VPlusFallback.FillFuel(shieldGeneratorNView.Invoke(instance), num, num, "shieldgen", "shield generator topped up to full fuel and held there."); } } } catch (Exception ex) { VPlusFallback.Warn("shield generator fuel: " + ex.Message); } } } [HarmonyPatch(typeof(CraftingStation), "Start")] internal static class Patch_VPF_CraftingStation_Start { private static void Prefix(CraftingStation __instance) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) try { if (!VPlusFallback.Metres(VPlusFallback.StationBuildRange, out var metres) || (Object)(object)__instance == (Object)null) { return; } float rangeBuild = __instance.m_rangeBuild; __instance.m_rangeBuild = metres; if ((Object)(object)__instance.m_areaMarker != (Object)null) { CircleProjector component = __instance.m_areaMarker.GetComponent<CircleProjector>(); if ((Object)(object)component != (Object)null) { component.m_radius = metres; } float num = metres / 20f; __instance.m_areaMarker.transform.localScale = new Vector3(num, 1f, num); } ResizePlayerBaseArea(__instance, metres); VPlusFallback.InfoOnce("station:" + (__instance.m_name ?? "?"), "station '" + (__instance.m_name ?? "?") + "' build range " + VPlusFallback.F(rangeBuild) + "m -> " + VPlusFallback.F(metres) + "m."); } catch (Exception ex) { VPlusFallback.Warn("station build range: " + ex.Message); } } private static void ResizePlayerBaseArea(CraftingStation station, float radius) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) try { EffectArea componentInChildren = ((Component)station).GetComponentInChildren<EffectArea>(); if (!((Object)(object)componentInChildren == (Object)null) && (componentInChildren.m_type & 4) != 0) { SphereCollider component = ((Component)componentInChildren).GetComponent<SphereCollider>(); if ((Object)(object)component != (Object)null) { component.radius = radius; } } } catch (Exception ex) { VPlusFallback.Warn("station no-spawn area: " + ex.Message); } } } [HarmonyPatch(typeof(CraftingStation), "CheckUsable")] internal static class Patch_VPF_CraftingStation_CheckUsable { private static void Prefix(CraftingStation __instance) { try { if (VPlusFallback.On(VPlusFallback.DisableStationRoofCheck) && !((Object)(object)__instance == (Object)null) && __instance.m_craftRequireRoof) { __instance.m_craftRequireRoof = false; VPlusFallback.InfoOnce("roof", "crafting stations no longer need a roof."); } } catch (Exception ex) { VPlusFallback.Warn("station roof check: " + ex.Message); } } } [HarmonyPatch(typeof(StationExtension), "Awake")] internal static class Patch_VPF_StationExtension_Awake { private static void Prefix(StationExtension __instance) { try { if (VPlusFallback.Metres(VPlusFallback.StationAttachmentRange, out var metres) && !((Object)(object)__instance == (Object)null)) { __instance.m_maxStationDistance = metres; } } catch (Exception ex) { VPlusFallback.Warn("station attachment range: " + ex.Message); } } } [HarmonyPatch(typeof(DropTable), "GetDropList", new Type[] { typeof(int) })] internal static class Patch_VPF_DropTable_GetDropList { private static readonly HashSet<string> Materials = new HashSet<string>(StringComparer.Ordinal) { "Wood", "FineWood", "RoundLog", "ElderBark", "YggdrasilWood", "Stone", "BlackMarble", "Grausten", "Blackwood", "TinOre", "CopperOre", "CopperScrap", "IronScrap", "SilverOre", "FlametalOreNew", "Chitin", "Feathers", "ProustitePowder" }; private static void Postfix(ref List<GameObject> __result) { try { if (!VPlusFallback.Pct(VPlusFallback.GatheringBonusPercent, out var percent) || __result == null || __result.Count == 0) { return; } List<GameObject> list = new List<GameObject>(__result.Count); for (int i = 0; i < __result.Count; i++) { GameObject val = __result[i]; if ((Object)(object)val == (Object)null) { continue; } if (!Materials.Contains(((Object)val).name)) { list.Add(val); continue; } int num = VPlusFallback.ApplyPercentWithChance(1f, percent); for (int j = 0; j < num; j++) { list.Add(val); } } __result = list; } catch (Exception ex) { VPlusFallback.Warn("gathering bonus: " + ex.Message); } } } [HarmonyPatch(typeof(Pickable), "RPC_Pick")] internal static class Patch_VPF_Pickable_RPC_Pick { private static readonly HashSet<string> Boosted = new HashSet<string>(StringComparer.Ordinal) { "Carrot", "Blueberries", "Cloudberry", "Raspberry", "Mushroom", "MushroomBlue", "MushroomYellow", "MushroomMagecap", "MushroomJotunPuffs", "MushroomSmokePuff", "Fiddleheadfern", "Vineberry", "Onion", "Barley", "CarrotSeeds", "Dandelion", "Flax", "Thistle", "TurnipSeeds", "Turnip", "OnionSeeds", "RoyalJelly", "VoltureEgg", "BoneFragments", "Flint", "Stone", "Wood", "Crystal", "Tar", "WolfHairBundle", "WolfClaw", "Amber", "AmberPearl", "Coins", "Ruby", "SurtlingCore", "BlackCore" }; private static void Prefix(Pickable __instance, out int __state) { __state = int.MinValue; try { if (!VPlusFallback.Pct(VPlusFallback.PickableBonusPercent, out var percent) || (Object)(object)__instance == (Object)null || (Object)(object)__instance.m_itemPrefab == (Object)null || !Boosted.Contains(((Object)__instance.m_itemPrefab).name)) { return; } int amount = __instance.m_amount; if (amount > 0) { int num = VPlusFallback.ApplyPercentWithChance(amount, percent); if (num != amount) { __state = amount; __instance.m_amount = num; } } } catch (Exception ex) { __state = int.MinValue; VPlusFallback.Warn("picking bonus: " + ex.Message); } } private static void Finalizer(Pickable __instance, int __state) { try { if (__state != int.MinValue && !((Object)(object)__instance == (Object)null)) { __instance.m_amount = __state; } } catch { } } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] internal static class Patch_VPF_CharacterDrop_GenerateDropList { private static void Prefix(CharacterDrop __instance, out List<Drop> __state) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown __state = null; try { if (!VPlusFallback.Pct(VPlusFallback.LootDropBonusPercent, out var percent) || (Object)(object)__instance == (Object)null || __instance.m_drops == null || __instance.m_drops.Count == 0) { return; } List<Drop> drops = __instance.m_drops; List<Drop> list = new List<Drop>(drops.Count); for (int i = 0; i < drops.Count; i++) { Drop val = drops[i]; if (val == null || (Object)(object)val.m_prefab == (Object)null) { list.Add(val); continue; } list.Add(new Drop { m_prefab = val.m_prefab, m_amountMin = (int)VPlusFallback.ApplyPercent(val.m_amountMin, percent), m_amountMax = (int)VPlusFallback.ApplyPercent(val.m_amountMax, percent), m_chance = val.m_chance, m_onePerPlayer = val.m_onePerPlayer, m_levelMultiplier = val.m_levelMultiplier, m_dontScale = val.m_dontScale }); } __state = drops; __instance.m_drops = list; } catch (Exception ex) { __state = null; VPlusFallback.Warn("loot amount bonus: " + ex.Message); } } private static void Finalizer(CharacterDrop __instance, List<Drop> __state) { try { if (__state != null && !((Object)(object)__instance == (Object)null)) { __instance.m_drops = __state; } } catch { } } } [HarmonyPatch(typeof(Talker), "Awake")] internal static class Patch_VPF_Talker_Awake { private static void Postfix(Talker __instance) { try { if (VPlusFallback.On(VPlusFallback.ServerWideShouts) && !((Object)(object)__instance == (Object)null) && !(__instance.m_shoutDistance >= 100000f)) { __instance.m_shoutDistance = 100000f; VPlusFallback.InfoOnce("shout", "shout range lifted to " + VPlusFallback.F(100000f) + "m."); } } catch (Exception ex) { VPlusFallback.Warn("shout range: " + ex.Message); } } } [HarmonyPatch(typeof(WearNTear), "UpdateWear", new Type[] { typeof(float) })] internal static class Patch_VPF_WearNTear_UpdateWear { private static void Prefix(float time, ref float ___m_rainTimer) { try { if (VPlusFallback.On(VPlusFallback.NoWeatherDamage)) { ___m_rainTimer = time; VPlusFallback.InfoOnce("weather", "rain and water erosion no longer damage buildings."); } } catch (Exception ex) { VPlusFallback.Warn("no weather damage: " + ex.Message); } } } [HarmonyPatch(typeof(Player), "Repair", new Type[] { typeof(ItemData), typeof(Piece) })] internal static class Patch_VPF_Player_Repair { private static void Postfix(Player __instance, ItemData toolItem, Piece repairPiece) { try { if (VPlusFallback.On(VPlusFallback.AreaRepair) && VPlusFallback.Metres(VPlusFallback.AreaRepairRadius, out var metres) && !((Object)(object)__instance == (Object)null) && toolItem != null && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { AreaRepairSweep.Run(__instance, toolItem, repairPiece, metres); } } catch (Exception ex) { VPlusFallback.Warn("area repair: " + ex.Message); } } } internal static class AreaRepairSweep { private static bool _busy; private static bool _tried; private static bool _usable; private static FieldRef<Player, Piece> _hoveringPiece; private static FieldInfo _allPieces; private static FieldInfo _ghostLayer; private static MethodInfo _repair; private static void Resolve() { if (_tried) { return; } _tried = true; try { _hoveringPiece = AccessTools.FieldRefAccess<Player, Piece>("m_hoveringPiece"); _allPieces = AccessTools.Field(typeof(Piece), "s_allPieces"); _ghostLayer = AccessTools.Field(typeof(Piece), "s_ghostLayer"); _repair = AccessTools.Method(typeof(Player), "Repair", new Type[2] { typeof(ItemData), typeof(Piece) }, (Type[])null); _usable = _hoveringPiece != null && _allPieces != null && _ghostLayer != null && _repair != null; if (!_usable) { VPlusFallback.Warn("area repair: Player.m_hoveringPiece, Piece.s_allPieces, Piece.s_ghostLayer or Player.Repair could not be resolved on this game build. Repairs are single-piece vanilla again."); } } catch (Exception ex) { _usable = false; VPlusFallback.Warn("area repair setup: " + ex.Message); } } internal static void Run(Player player, ItemData toolItem, Piece repairPiece, float radius) { //IL_0038: 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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (_busy) { return; } Resolve(); if (!_usable) { return; } Piece hoveringPiece = player.GetHoveringPiece(); Vector3 val = (((Object)(object)hoveringPiece != (Object)null) ? ((Component)hoveringPiece).transform.position : ((Component)player).transform.position); if (!(_allPieces.GetValue(null) is List<Piece> list)) { return; } int num = (int)_ghostLayer.GetValue(null); List<Piece> list2 = new List<Piece>(); for (int i = 0; i < list.Count; i++) { Piece val2 = list[i]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)hoveringPiece) && ((Component)val2).gameObject.layer != num && !(Vector3.Distance(val, ((Component)val2).transform.position) >= radius)) { WearNTear component = ((Component)val2).GetComponent<WearNTear>(); if (!((Object)(object)component == (Object)null) && !(component.GetHealthPercentage() >= 1f)) { list2.Add(val2); } } } if (list2.Count == 0) { return; } float num2 = ((toolItem.m_shared != null && toolItem.m_shared.m_attack != null) ? toolItem.m_shared.m_attack.m_attackStamina : 0f); bool flag = toolItem.m_shared != null && toolItem.m_shared.m_useDurability; Piece val3 = _hoveringPiece.Invoke(player); int num3 = 0; _busy = true; try { for (int j = 0; j < list2.Count; j++) { if (!((Character)player).HaveStamina(num2)) { break; } if (flag && !(toolItem.m_durability > 0f)) { break; } _hoveringPiece.Invoke(player) = list2[j]; _repair.Invoke(player, new object[2] { toolItem, repairPiece }); num3++; } } finally { _hoveringPiece.Invoke(player) = val3; _busy = false; } if (num3 > 0) { VPlusFallback.InfoOnce("arearepair", "hammer repairs everything damaged within " + VPlusFallback.F(radius) + "m."); ((Character)player).Message((MessageType)1, num3 + " more pieces repaired", 0, (Sprite)null, false); } } } [HarmonyPatch(typeof(ItemDrop), "Awake")] internal static class Patch_VPF_ItemDrop_Awake { private static void Prefix(ItemDrop __instance) { try { if (VPlusFallback.On(VPlusFallback.ItemsFloat) && !((Object)(object)__instance == (Object)null)) { GameObject gameObject = ((Component)__instance).gameObject; if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)gameObject.GetComponent<ZNetView>() == (Object)null) && !((Object)(object)gameObject.GetComponent<Floating>() != (Object)null)) { gameObject.AddComponent<Floating>().m_waterLevelOffset = 0.5f; VPlusFallback.InfoOnce("float", "dropped items float instead of sinking."); } } } catch (Exception ex) { VPlusFallback.Warn("floating items: " + ex.Message); } } } [HarmonyPatch(typeof(GameCamera), "Awake")] internal static class Patch_VPF_GameCamera_Awake { internal const float MinZoom = 1f; internal const float MaxZoom = 100f; internal const float MinFov = 1f; internal const float MaxFov = 140f; private static void Postfix(GameCamera __instance) { try { if (VPlusFallback.Active && !((Object)(object)__instance == (Object)null)) { if (Take(VPlusFallback.CameraMaxZoom, 1f, 100f, "CameraMaxZoom", out var value)) { __instance.m_maxDistance = value; } if (Take(VPlusFallback.CameraMaxZoomBoat, 1f, 100f, "CameraMaxZoomBoat", out value)) { __instance.m_maxDistanceBoat = value; } if (Take(VPlusFallback.CameraFov, 1f, 140f, "CameraFov", out value)) { __instance.m_fov = value; } VPlusFallback.InfoOnce("camera", "camera max zoom " + VPlusFallback.F(__instance.m_maxDistance) + "m on foot, " + VPlusFallback.F(__instance.m_maxDistanceBoat) + "m on a boat, field of view " + VPlusFallback.F(__instance.m_fov) + "."); } } catch (Exception ex) { VPlusFallback.Warn("camera: " + ex.Message); } } private static bool Take(ConfigEntry<float> entry, float min, float max, string key, out float value) { value = 0f; if (entry == null) { return false; } float value2 = entry.Value; if (value2 == 0f || float.IsNaN(value2) || float.IsInfinity(value2)) { return false; } if (value2 < min || value2 > max) { VPlusFallback.InfoOnce("camera:" + key, key + " = " + VPlusFallback.F(value2) + " is outside the accepted " + VPlusFallback.F(min) + " to " + VPlusFallback.F(max) + " range; that one value stays vanilla."); return false; } value = value2; return true; } } }