using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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.4.0.0")] [assembly: AssemblyInformationalVersion("1.4.0+5a86245ed19a4e0bda8f09872dec10e534468f66")] [assembly: AssemblyProduct("EilifPaths")] [assembly: AssemblyTitle("EilifPaths")] [assembly: AssemblyVersion("1.4.0.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 ExtraRange; internal static void Bind(ConfigFile config) { ExtraRange = config.Bind("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 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.4.0")] 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.4.0"; private const string OldModGuid = "Menthus.bepinex.plugins.UsefulPaths"; internal static ManualLogSource Log; private const float GroundCheckRate = 0.4f; internal static readonly Dictionary> Movement = new Dictionary>(); internal static readonly Dictionary> StaminaDrain = new Dictionary>(); internal static readonly Dictionary> ActionStamina = new Dictionary>(); 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(text, "movement", move, "Speed multiplier while on " + text + " (1.0 = vanilla, >1 = faster)."); StaminaDrain[type] = ((BaseUnityPlugin)this).Config.Bind(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(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_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0174: 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); 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"); int num = 0; int num2 = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(EilifPathsPlugin).Assembly); foreach (Type type in typesFromAssembly) { try { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0) { num2++; val.CreateClassProcessor(type).Patch(); num++; } } catch (Exception ex) { Log.LogError((object)("[EilifPaths] could not apply patch class " + ((type != null) ? type.Name : "?") + ": " + ex.Message)); } } ToolStamina.Apply(val); Log.LogInfo((object)("[EilifPaths] Eilif Paths v1.4.0 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() + ". Core patch classes: " + num + "/" + num2 + " applied.")); } private static string F(ConfigEntry c) { return c.Value.ToString("0.##", CultureInfo.InvariantCulture); } private void UpdateGround() { //IL_00a0: 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) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected I4, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) ToolStamina.SanityReset(); 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(); 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 value = Movement[t].Value; float value2 = StaminaDrain[t].Value; float value3 = ActionStamina[t].Value; Log.LogInfo((object)(string.Format("[EilifPaths] terrain: {0} (x{1} speed, ", t, value.ToString("0.##", CultureInfo.InvariantCulture)) + "x" + value2.ToString("0.##", CultureInfo.InvariantCulture) + " movement stamina, x" + value3.ToString("0.##", CultureInfo.InvariantCulture) + " tool stamina)")); } } internal static float MoveMult() { if (Current != PathType.None) { return Movement[Current].Value; } return 1f; } internal static float StamMult() { if (Current != PathType.None) { return StaminaDrain[Current].Value; } return 1f; } internal static float ActionMult() { if (Current != PathType.None) { return ActionStamina[Current].Value; } return 1f; } 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) { if (EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __result *= EilifPathsPlugin.MoveMult(); } } } [HarmonyPatch(typeof(Player), "GetRunSpeedFactor")] internal static class Patch_GetRunSpeedFactor { private static void Postfix(ref float __result) { if (EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __result *= EilifPathsPlugin.MoveMult(); } } } [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 = (((Object)(object)__instance != (Object)null) ? __instance.m_walkSpeed : 0f); if ((Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && EilifPathsPlugin.Current != EilifPathsPlugin.PathType.None) { __instance.m_walkSpeed *= EilifPathsPlugin.MoveMult(); } } private static void Postfix(Character __instance, float __state) { if ((Object)(object)__instance != (Object)null) { __instance.m_walkSpeed = __state; } } } internal static class StationRange { internal const float DefaultExtraRange = 10f; internal static ConfigEntry ExtraRange; private static readonly HashSet Reported = new HashSet(); internal static void Bind(ConfigFile config) { ExtraRange = config.Bind("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 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 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; 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 { } } private static Site[] Sites() { return new 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") }; } internal static void Apply(Harmony harmony) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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; Site[] array = Sites(); Site[] array2 = array; foreach (Site site in array2) { try { 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) { MarkDegraded("could not hook " + site.Label() + " (" + site.What + "): " + ex.Message); } } try { if (EilifPathsPlugin.Log != null) { EilifPathsPlugin.Log.LogInfo((object)("[EilifPaths] tool/weapon stamina hooks: " + num + "/" + array.Length + " 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 { } } } }