Decompiled source of EilifPaths v1.7.1

EilifPaths.dll

Decompiled a month ago
using 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;
		}
	}
}