Decompiled source of Skaft v1.3.0

plugins/Skaft/Skaft.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Ezomic.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
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("Thijssen Software")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")]
[assembly: AssemblyDescription("Hammer repair reaches further the higher your Crafting skill.")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyInformationalVersion("1.3.0+6521da31872e3e40f6b490afb9417af7a8ae507c")]
[assembly: AssemblyProduct("Skaft")]
[assembly: AssemblyTitle("Skaft")]
[assembly: AssemblyVersion("1.3.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 Skaft
{
	internal static class Bench
	{
		private static readonly List<ItemData> Worn = new List<ItemData>();

		private static Func<InventoryGui, ItemData, bool> _canRepair;

		private static bool _bound;

		private static bool _bindFailed;

		internal static int Count(Player player)
		{
			float num = ((Character)player).GetSkillFactor((SkillType)107) * 100f;
			float num2 = Mathf.Max(1f, SkaftConfig.FullLevel.Value);
			float num3 = Mathf.Pow(Mathf.Clamp01(num / num2), Mathf.Max(0.01f, SkaftConfig.ItemCurve.Value));
			int num4 = Mathf.Max(1, SkaftConfig.MinItems.Value);
			int num5 = Mathf.Max(num4, SkaftConfig.MaxItems.Value);
			return Mathf.Clamp(Mathf.FloorToInt(Mathf.Lerp((float)num4, (float)num5, num3)), num4, num5);
		}

		internal static void Run(InventoryGui gui)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)gui == (Object)null)
			{
				return;
			}
			int num = Count(localPlayer) - 1;
			if (num <= 0 || !Bind())
			{
				return;
			}
			CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation();
			if (((Object)(object)currentCraftingStation == (Object)null && !localPlayer.NoCostCheat()) || ((Object)(object)currentCraftingStation != (Object)null && !currentCraftingStation.CheckUsable(localPlayer, false)))
			{
				return;
			}
			Worn.Clear();
			((Humanoid)localPlayer).GetInventory().GetWornItems(Worn);
			int num2 = 0;
			for (int i = 0; i < Worn.Count; i++)
			{
				if (num2 >= num)
				{
					break;
				}
				ItemData val = Worn[i];
				if (val != null && _canRepair(gui, val))
				{
					((Character)localPlayer).RaiseSkill((SkillType)107, 1f - val.m_durability / val.GetMaxDurability());
					val.m_durability = val.GetMaxDurability();
					num2++;
				}
			}
			Worn.Clear();
			if (SkaftConfig.Verbose.Value)
			{
				SkaftPlugin.Log.LogInfo((object)("Bench: crafting " + Sweep.Level(localPlayer) + ", allowance " + (num + 1) + " items, " + (num2 + 1) + " repaired."));
			}
			if (num2 > 0)
			{
				((Character)localPlayer).Message((MessageType)2, Localization.instance.Localize("$msg_repaired", new string[1] { num2 + 1 + " items" }), 0, (Sprite)null, false);
			}
		}

		private static bool Bind()
		{
			if (_bound)
			{
				return !_bindFailed;
			}
			_bound = true;
			try
			{
				_canRepair = AccessTools.MethodDelegate<Func<InventoryGui, ItemData, bool>>(AccessTools.Method(typeof(InventoryGui), "CanRepair", (Type[])null, (Type[])null), (object)null, true);
				if (_canRepair == null)
				{
					throw new MissingMemberException();
				}
			}
			catch (Exception ex)
			{
				_bindFailed = true;
				SkaftPlugin.Log.LogWarning((object)("Could not reach InventoryGui.CanRepair - the bench repairs one item a press, as vanilla does. The hammer sweep is unaffected. " + ex.Message));
			}
			return !_bindFailed;
		}
	}
	internal static class SkaftConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> MinRadius;

		public static ConfigEntry<float> MaxRadius;

		public static ConfigEntry<float> FullLevel;

		public static ConfigEntry<float> Curve;

		public static ConfigEntry<float> CostMultiplier;

		public static ConfigEntry<float> DurabilityFloor;

		public static ConfigEntry<int> MaxPieces;

		public static ConfigEntry<bool> RepairItems;

		public static ConfigEntry<int> MinItems;

		public static ConfigEntry<int> MaxItems;

		public static ConfigEntry<float> ItemCurve;

		public static ConfigEntry<bool> OwnBuildingsOnly;

		public static ConfigEntry<bool> ShowReachInBuildMenu;

		public static ConfigEntry<bool> ShowDamagedInReach;

		public static ConfigEntry<string> ReachEntries;

		public static ConfigEntry<bool> Verbose;

		private static string _reachRaw;

		private static HashSet<string> _reachSet;

		internal static bool IsReachEntry(string prefabName)
		{
			string text = ReachEntries.Value ?? string.Empty;
			if (text != _reachRaw || _reachSet == null)
			{
				_reachRaw = text;
				_reachSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				string[] array = text.Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text2 = array[i].Trim();
					if (text2.Length > 0)
					{
						_reachSet.Add(text2);
					}
				}
			}
			return _reachSet.Contains(prefabName);
		}

		public static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Skaft", "Enabled", true, "The whole mod, both halves of it: the hammer sweep on buildings and the Repair button at the bench. Off leaves vanilla single-piece repair untouched in both places - which is also exactly what this mod does at Crafting 0, so turning it off is only useful for telling the mod apart from a low skill level. To keep one half and drop the other, use RepairItems.");
			MinRadius = config.Bind<float>("Skaft", "MinRadius", 0f, "Sweep radius in metres at Crafting 0, measured from the piece under your cursor. Zero on purpose: a new character gets vanilla repair, one piece per swing, and the mod is invisible until the skill is worth something. At zero the sweep does not run at all, so a new character cannot meet a bug in it. Raise this only if you want the reach handed over rather than earned.");
			MaxRadius = config.Bind<float>("Skaft", "MaxRadius", 8f, "Sweep radius in metres once Crafting reaches FullLevel. Vanilla lets you touch a piece from 5 metres away, so 8 metres around what you are already touching is the outer edge of \"working on this building\". At 8 a mid-wall swing covers a 10x6 longhouse end to end. Larger numbers repair things you cannot see, and your stamina stops paying for them long before the radius runs out.");
			FullLevel = config.Bind<float>("Skaft", "FullLevel", 60f, "The Crafting level at which the radius reaches MaxRadius and a bench press reaches MaxItems. Shared by both halves on purpose: one skill, one level at which it has arrived. Above it nothing changes. 100 is the wrong number to design against: it costs roughly 20,300 crafts, and Crafting only rises by crafting or upgrading at a station and by repairing a worn item - repairing buildings trains nothing at all. 60 is about 5,700, which a long playthrough actually reaches, so the reward arrives instead of dangling.");
			Curve = config.Bind<float>("Skaft", "Curve", 0.8f, "Exponent on the skill fraction before it is lerped between MinRadius and MaxRadius: radius = min + (max-min) * (level/FullLevel)^Curve. 1.0 is a straight line. Below 1 opens the reach earlier, above 1 hoards it for the top levels. The default keeps the first ten levels indistinguishable from vanilla - 1.9m cannot reach a neighbour two metres away - then grows steadily, because the experience curve is already brutal at the top and stacking a second brake on it puts the whole mod somewhere nobody goes.");
			CostMultiplier = config.Bind<float>("Skaft", "CostMultiplier", 1f, "Multiplies the stamina, eitr and hammer durability charged for each piece the sweep actually repairs. 1 means every piece costs exactly what vanilla charges to repair one piece by hand. That per-piece price is what stops a wide radius being free: your stamina bar, not the radius, decides how many pieces a swing fixes. Above 1 makes a sweep dearer than doing it by hand. Below 1 is the setting that deletes the point of this mod. Pieces already at full health cost nothing either way.");
			DurabilityFloor = config.Bind<float>("Skaft", "DurabilityFloor", 1f, "The sweep stops before the hammer would drop to or below this. Repairing subtracts durability without checking zero, so a wide sweep can spend an entire hammer inside one click - the game tells you it broke, but it broke on a single press rather than over the swings that wore it down, and it unequips, which drops you out of build mode mid-job. Zero lets a sweep spend the hammer to its last point and break it on the next swing, the normal way.");
			MaxPieces = config.Bind<int>("Skaft", "MaxPieces", 200, "Hard ceiling on pieces repaired in one swing, whatever the radius says. This is a network guard, not a balance number - each repaired piece is one message to the piece's owner and one broadcast back, so 200 is 400 messages in a single frame. Balance is stamina and durability.");
			RepairItems = config.Bind<bool>("Skaft", "RepairItems", true, "Whether one press of the Repair button at a crafting station repairs more than one item. Off leaves the bench exactly as vanilla has it, one item a press, and the hammer sweep is unaffected either way. This is the only switch that separates the two halves of the mod.");
			MinItems = config.Bind<int>("Skaft", "MinItems", 1, "How many items one press of the Repair button fixes at Crafting 0. One is vanilla, and it is the floor for the same reason MinRadius is zero: a new character should get the game as it ships, and the mod should be something the character grows into rather than something the install hands over.");
			MaxItems = config.Bind<int>("Skaft", "MaxItems", 10, "How many items one press of the Repair button fixes once Crafting reaches FullLevel. Ten is a full kit - helmet, chest, legs, cape, weapon, shield, bow and the three tools - so at the top of the curve coming home is one press rather than ten. Nothing is made cheaper by this: vanilla charges no materials, no durability and no stamina to repair an item, so the presses were the whole price and the skill is what buys them down. Unlike the sweep there is no stamina bar to stop it, which is why the number itself is the constraint and why it starts at one.");
			ItemCurve = config.Bind<float>("Skaft", "ItemCurve", 1.5f, "Exponent on the skill fraction for the bench, the way Curve is for the radius: items = min + (max-min) * (level/FullLevel)^ItemCurve, floored. It is a separate number, and above 1 where Curve is below it, because a count is a much coarser dial than metres - one more metre of reach is nothing, one more item is the difference between pressing twice and pressing once. At the default the bench is vanilla until about Crafting 14, three items around 25, seven around 50 and ten at 60.");
			OwnBuildingsOnly = config.Bind<bool>("Skaft", "OwnBuildingsOnly", false, "Restrict the sweep to pieces you placed yourself. False matches vanilla, which happily repairs anyone's building and leaves permission entirely to wards. True is for shared bases where you would rather not top up a neighbour's walls with your own stamina. The piece under your cursor is always vanilla's business, not this setting's.");
			ShowReachInBuildMenu = config.Bind<bool>("Skaft", "ShowReachInBuildMenu", true, "Add a line to the Repair entry in the build menu showing your current reach in metres and the Crafting level it came from. This is the only place the number is shown; after a swing, the \"Repaired x12\" count tells you the rest.");
			ShowDamagedInReach = config.Bind<bool>("Skaft", "ShowDamagedInReach", true, "Show how many damaged pieces are in reach under the crosshair, while a repair entry is selected and you are pointing at a piece. It is there because a piece above 75% health looks perfect - the game only swaps in the worn model below three quarters and the broken one below a quarter, so the top of every health bar is damage you cannot see - and because a swing at an intact piece does nothing at all however much is broken around it. The line says which of those you are looking at. It counts damage and distance only: stamina, hammer durability, wards and a missing station can still cut the swing short.");
			ReachEntries = config.Bind<string>("Skaft", "ReachEntries", "piece_repair", "Comma separated prefab names of the build menu entries the reach line is written on. It is not simply \"whatever is flagged as a repair entry\", because that flag means \"this entry clicks on the world instead of placing into it\" and other mods use it for their own tools - Vaettir's Transplant entry on the cultivator wears it, and the sweep can never run there. Add a name here if some other mod's repair entry does fall through to the game's own repair; the sweep itself is unaffected either way.");
			Verbose = config.Bind<bool>("Diagnostics", "Verbose", false, "One line per swing to BepInEx/LogOutput.log: skill level, radius, candidates found, pieces repaired, and what stopped the sweep. For tuning the curve, noisy in normal play.");
		}
	}
	internal static class SkaftPatches
	{
		private const float CountInterval = 0.25f;

		private static float _nextCount;

		private static WearNTear _countedFor;

		private static string _countLine;

		private const float ReachInterval = 1f;

		private static float _nextReach;

		private static readonly HashSet<string> _unknownEntries = new HashSet<string>();

		private static Piece _described;

		private static string _originalDescription;

		private static string _writtenDescription;

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "Repair", new Type[]
		{
			typeof(ItemData),
			typeof(Piece)
		})]
		private static void Repair(Player __instance, ItemData toolItem)
		{
			if (SkaftConfig.Enabled.Value && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && toolItem != null)
			{
				Piece hoveringPiece = __instance.GetHoveringPiece();
				WearNTear val = default(WearNTear);
				if (!((Object)(object)hoveringPiece == (Object)null) && ((Component)hoveringPiece).TryGetComponent<WearNTear>(ref val) && Sweep.JustRepaired(val))
				{
					Sweep.Run(__instance, toolItem, hoveringPiece, val);
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(InventoryGui), "RepairOneItem")]
		private static void RepairOneItem(InventoryGui __instance)
		{
			if (SkaftConfig.Enabled.Value && SkaftConfig.RepairItems.Value)
			{
				Bench.Run(__instance);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Hud), "UpdateCrosshair")]
		private static void UpdateCrosshair(Hud __instance, Player player)
		{
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			if (!SkaftConfig.Enabled.Value || !SkaftConfig.ShowDamagedInReach.Value || (Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			TextMeshProUGUI hoverName = __instance.m_hoverName;
			if ((Object)(object)hoverName == (Object)null)
			{
				return;
			}
			Piece selectedPiece = player.GetSelectedPiece();
			if ((Object)(object)selectedPiece == (Object)null || !selectedPiece.m_repairPiece || !SkaftConfig.IsReachEntry(Utils.GetPrefabName(((Object)((Component)selectedPiece).gameObject).name)))
			{
				_countLine = null;
				_countedFor = null;
				return;
			}
			Piece hoveringPiece = player.GetHoveringPiece();
			WearNTear val = default(WearNTear);
			if ((Object)(object)hoveringPiece == (Object)null || !((Component)hoveringPiece).TryGetComponent<WearNTear>(ref val))
			{
				_countLine = null;
				_countedFor = null;
				return;
			}
			float num = Sweep.Radius(player);
			if (num <= 0f)
			{
				_countLine = null;
				_countedFor = null;
				return;
			}
			if ((Object)(object)val != (Object)(object)_countedFor || Time.time >= _nextCount)
			{
				_countedFor = val;
				_nextCount = Time.time + 0.25f;
				int num2 = Sweep.CountDamaged(((Component)hoveringPiece).transform.position, num, val);
				bool flag = val.GetHealthPercentage() >= 1f;
				int num3 = num2 + ((!flag) ? 1 : 0);
				_countLine = ((num3 <= 0) ? "Damaged in reach: none" : ("Damaged in reach: " + num3 + (flag ? " (aim at a damaged piece)" : "")));
			}
			if (!string.IsNullOrEmpty(_countLine))
			{
				((TMP_Text)hoverName).text = (string.IsNullOrEmpty(((TMP_Text)hoverName).text) ? _countLine : (((TMP_Text)hoverName).text + "\n" + _countLine));
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "UpdatePlacement", new Type[]
		{
			typeof(bool),
			typeof(float)
		})]
		private static void UpdatePlacement(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			Piece selectedPiece = __instance.GetSelectedPiece();
			if ((Object)(object)selectedPiece != (Object)(object)_described)
			{
				Restore();
			}
			if (!SkaftConfig.Enabled.Value || !SkaftConfig.ShowReachInBuildMenu.Value)
			{
				Restore();
			}
			else
			{
				if ((Object)(object)selectedPiece == (Object)null || !selectedPiece.m_repairPiece)
				{
					return;
				}
				string prefabName = Utils.GetPrefabName(((Object)((Component)selectedPiece).gameObject).name);
				if (!SkaftConfig.IsReachEntry(prefabName))
				{
					if (SkaftConfig.Verbose.Value && _unknownEntries.Add(prefabName))
					{
						SkaftPlugin.Log.LogInfo((object)("Repair entry '" + prefabName + "' is not in ReachEntries, so no reach line is written on it. That is correct for another mod's tool; add the name if it is a repair entry the sweep can actually serve."));
					}
				}
				else if (!(Time.time < _nextReach))
				{
					_nextReach = Time.time + 1f;
					if ((Object)(object)_described == (Object)null)
					{
						_described = selectedPiece;
						_originalDescription = selectedPiece.m_description;
					}
					if (selectedPiece.m_description != _writtenDescription && selectedPiece.m_description != null && selectedPiece.m_description != _originalDescription)
					{
						_originalDescription = selectedPiece.m_description;
					}
					string text = "Reach: " + Sweep.Radius(__instance).ToString("0.0") + "m (Crafting " + Sweep.Level(__instance) + ")";
					_writtenDescription = (string.IsNullOrEmpty(_originalDescription) ? text : (_originalDescription + "\n" + text));
					selectedPiece.m_description = _writtenDescription;
				}
			}
		}

		internal static void Restore()
		{
			if ((Object)(object)_described != (Object)null && _described.m_description == _writtenDescription)
			{
				_described.m_description = _originalDescription;
			}
			_described = null;
			_originalDescription = null;
			_writtenDescription = null;
		}
	}
	[BepInPlugin("ezomic.valheim.skaft", "Skaft", "1.3.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class SkaftPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ezomic.valheim.skaft";

		public const string PluginName = "Skaft";

		public const string PluginVersion = "1.3.0";

		public const string PluginAuthor = "Robbin Thijssen";

		private const string CoreGuid = "ezomic.valheim.core";

		internal static ManualLogSource Log;

		internal static bool CorePresent;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SkaftConfig.Bind(((BaseUnityPlugin)this).Config);
			TryRegisterWithCore();
			_harmony = new Harmony("ezomic.valheim.skaft");
			_harmony.PatchAll(typeof(SkaftPatches));
			Log.LogInfo((object)"Skaft 1.3.0 by Robbin Thijssen - ready.");
		}

		private void TryRegisterWithCore()
		{
			CorePresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core");
			if (!CorePresent)
			{
				Log.LogInfo((object)"Core not installed - running standalone, without the version gate.");
			}
			else
			{
				RegisterWithCore();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void RegisterWithCore()
		{
			Suite.Register("ezomic.valheim.skaft", "Skaft", "1.3.0", ((BaseUnityPlugin)this).Config, (Requirement)1, (Assembly)null);
			Suite.Sync((ConfigEntryBase[])(object)new ConfigEntryBase[10]
			{
				(ConfigEntryBase)SkaftConfig.Enabled,
				(ConfigEntryBase)SkaftConfig.MinRadius,
				(ConfigEntryBase)SkaftConfig.MaxRadius,
				(ConfigEntryBase)SkaftConfig.FullLevel,
				(ConfigEntryBase)SkaftConfig.Curve,
				(ConfigEntryBase)SkaftConfig.CostMultiplier,
				(ConfigEntryBase)SkaftConfig.RepairItems,
				(ConfigEntryBase)SkaftConfig.MinItems,
				(ConfigEntryBase)SkaftConfig.MaxItems,
				(ConfigEntryBase)SkaftConfig.ItemCurve
			});
			Suite.Local((ConfigEntryBase[])(object)new ConfigEntryBase[3]
			{
				(ConfigEntryBase)SkaftConfig.ShowReachInBuildMenu,
				(ConfigEntryBase)SkaftConfig.ShowDamagedInReach,
				(ConfigEntryBase)SkaftConfig.Verbose
			});
		}

		private void OnDestroy()
		{
			SkaftPatches.Restore();
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class Sweep
	{
		private static readonly List<WearNTear> Candidates = new List<WearNTear>();

		private static readonly List<WearNTear> Preview = new List<WearNTear>();

		private static Vector3 _center;

		private static readonly Comparison<WearNTear> ByDistance = delegate(WearNTear a, WearNTear b)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)a).transform.position - _center;
			float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
			val = ((Component)b).transform.position - _center;
			return sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
		};

		private static FieldRef<WearNTear, float> _lastRepair;

		private static FieldRef<Player, PieceTable> _buildPieces;

		private static Func<Player, float> _buildStamina;

		private static bool _bound;

		private static bool _bindFailed;

		private static bool _toolReported;

		internal static float Radius(Player player)
		{
			float num = ((Character)player).GetSkillFactor((SkillType)107) * 100f;
			float num2 = Mathf.Max(1f, SkaftConfig.FullLevel.Value);
			float num3 = Mathf.Pow(Mathf.Clamp01(num / num2), Mathf.Max(0.01f, SkaftConfig.Curve.Value));
			return Mathf.Lerp(SkaftConfig.MinRadius.Value, SkaftConfig.MaxRadius.Value, num3);
		}

		internal static int Level(Player player)
		{
			return Mathf.RoundToInt(((Character)player).GetSkillFactor((SkillType)107) * 100f);
		}

		internal static void Run(Player player, ItemData tool, Piece hovered, WearNTear hoveredWear)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			float num = Radius(player);
			if (num <= 0f || !Bind())
			{
				return;
			}
			Collect(Candidates, ((Component)hovered).transform.position, num, hoveredWear);
			Candidates.Sort(ByDistance);
			float num2 = Mathf.Max(0f, SkaftConfig.CostMultiplier.Value);
			float num3 = _buildStamina(player) * num2;
			float num4 = tool.m_shared.m_attack.m_attackEitr * num2;
			float num5 = tool.m_shared.m_useDurabilityDrain * num2;
			ReportTool(player, tool, num3);
			int num6 = 0;
			string text = "radius";
			Piece val2 = default(Piece);
			for (int i = 0; i < Candidates.Count; i++)
			{
				if (num6 >= SkaftConfig.MaxPieces.Value)
				{
					text = "MaxPieces";
					break;
				}
				if (!((Character)player).HaveStamina(num3 * Game.m_staminaRate))
				{
					text = "stamina";
					break;
				}
				if (tool.m_shared.m_useDurability && tool.m_durability - num5 <= SkaftConfig.DurabilityFloor.Value)
				{
					text = "durability";
					break;
				}
				WearNTear val = Candidates[i];
				if ((Object)(object)val == (Object)null || !((Component)val).TryGetComponent<Piece>(ref val2))
				{
					continue;
				}
				ZNetView component = ((Component)val).GetComponent<ZNetView>();
				if ((Object)(object)component == (Object)null || !component.IsValid() || (SkaftConfig.OwnBuildingsOnly.Value && component.GetZDO().GetLong(ZDOVars.s_creator, 0L) != player.GetPlayerID()) || (!player.NoCostCheat() && (Object)(object)val2.m_craftingStation != (Object)null && (Object)(object)CraftingStation.HaveBuildStationInRange(val2.m_craftingStation.m_name, ((Component)player).transform.position) == (Object)null && (!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGlobalKey(((object)(GlobalKeys)27/*cast due to .constrained prefix*/).ToString()))) || !PrivateArea.CheckAccess(((Component)val2).transform.position, 0f, false, false) || val.GetHealthPercentage() >= 1f)
				{
					continue;
				}
				if (!component.HasOwner())
				{
					component.ClaimOwnership();
				}
				if (val.Repair())
				{
					((Character)player).UseStamina(num3);
					((Character)player).UseEitr(num4);
					if (tool.m_shared.m_useDurability)
					{
						tool.m_durability -= num5;
					}
					num6++;
				}
			}
			if (SkaftConfig.Verbose.Value)
			{
				SkaftPlugin.Log.LogInfo((object)("Sweep: crafting " + Level(player) + ", radius " + num.ToString("0.0") + "m, " + Candidates.Count + " damaged in range, " + num6 + " repaired, stopped on " + text + "."));
			}
			Candidates.Clear();
			if (num6 > 0)
			{
				((Character)player).Message((MessageType)1, Localization.instance.Localize("$msg_repaired", new string[1] { hovered.m_name }), num6 + 1, (Sprite)null, false);
			}
		}

		private static void Collect(List<WearNTear> into, Vector3 center, float radius, WearNTear skip)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: 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)
			_center = center;
			float num = radius * radius;
			into.Clear();
			List<WearNTear> allInstances = WearNTear.GetAllInstances();
			Piece val3 = default(Piece);
			for (int i = 0; i < allInstances.Count; i++)
			{
				WearNTear val = allInstances[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)skip))
				{
					Vector3 val2 = ((Component)val).transform.position - center;
					if (!(((Vector3)(ref val2)).sqrMagnitude > num) && !(val.GetHealthPercentage() >= 1f) && ((Component)val).TryGetComponent<Piece>(ref val3))
					{
						into.Add(val);
					}
				}
			}
		}

		internal static int CountDamaged(Vector3 center, float radius, WearNTear skip)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (radius <= 0f)
			{
				return 0;
			}
			Collect(Preview, center, radius, skip);
			int count = Preview.Count;
			Preview.Clear();
			return count;
		}

		private static void ReportTool(Player player, ItemData tool, float stamina)
		{
			if (!_toolReported && SkaftConfig.Verbose.Value)
			{
				_toolReported = true;
				string text = "unknown (m_buildPieces unreachable)";
				if (_buildPieces != null)
				{
					PieceTable val = _buildPieces.Invoke(player);
					text = (((Object)(object)val == (Object)null) ? "no piece table" : ((object)Unsafe.As<SkillType, SkillType>(ref val.m_skill)/*cast due to .constrained prefix*/).ToString());
				}
				SkaftPlugin.Log.LogInfo((object)("Tool: " + tool.m_shared.m_name + ", attackStamina " + tool.m_shared.m_attack.m_attackStamina + ", attackEitr " + tool.m_shared.m_attack.m_attackEitr + ", useDurability " + tool.m_shared.m_useDurability + ", useDurabilityDrain " + tool.m_shared.m_useDurabilityDrain + ", durability " + tool.m_durability.ToString("0.0") + "/" + tool.GetMaxDurability().ToString("0.0") + ". Charging " + stamina.ToString("0.00") + " stamina a piece. Build table skill: " + text + " (if that is Crafting, it already discounts build stamina by up to half at level 100, on top of the reach this mod grants)."));
			}
		}

		internal static bool JustRepaired(WearNTear wear)
		{
			if (!Bind())
			{
				return false;
			}
			try
			{
				return _lastRepair.Invoke(wear) == Time.time;
			}
			catch
			{
				return false;
			}
		}

		private static bool Bind()
		{
			if (_bound)
			{
				return !_bindFailed;
			}
			_bound = true;
			try
			{
				_lastRepair = AccessTools.FieldRefAccess<WearNTear, float>("m_lastRepair");
				_buildStamina = AccessTools.MethodDelegate<Func<Player, float>>(AccessTools.Method(typeof(Player), "GetBuildStamina", (Type[])null, (Type[])null), (object)null, true);
				if (_lastRepair == null || _buildStamina == null)
				{
					throw new MissingMemberException();
				}
				try
				{
					_buildPieces = AccessTools.FieldRefAccess<Player, PieceTable>("m_buildPieces");
				}
				catch
				{
					_buildPieces = null;
				}
			}
			catch (Exception ex)
			{
				_bindFailed = true;
				SkaftPlugin.Log.LogWarning((object)("Could not reach Player.GetBuildStamina or WearNTear.m_lastRepair - the sweep is off and repair stays vanilla. " + ex.Message));
			}
			return !_bindFailed;
		}
	}
}