Decompiled source of OhMyGrid v1.0.2

OhMyGrid.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
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.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("cgaggino")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Plant crops in circular/donut grids in Valheim.")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+de619cbe58f38b63dc81270f12792949d48ea926")]
[assembly: AssemblyProduct("OhMyGrid")]
[assembly: AssemblyTitle("OhMyGrid")]
[assembly: AssemblyVersion("1.0.2.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 OhMyGrid
{
	public readonly struct GridPoint
	{
		public readonly float X;

		public readonly float Z;

		public GridPoint(float x, float z)
		{
			X = x;
			Z = z;
		}

		public override string ToString()
		{
			return $"({X:0.##}, {Z:0.##})";
		}
	}
	public static class GridGenerator
	{
		public static IEnumerable<GridPoint> Donut(float centerX, float centerZ, float innerRadius, float outerRadius, float spacing)
		{
			if (spacing <= 0f)
			{
				throw new ArgumentOutOfRangeException("spacing");
			}
			if (innerRadius < 0f)
			{
				throw new ArgumentOutOfRangeException("innerRadius");
			}
			if (outerRadius < innerRadius)
			{
				throw new ArgumentOutOfRangeException("outerRadius");
			}
			for (float r = innerRadius; r <= outerRadius + 0.0001f; r += spacing)
			{
				if (r <= 0.0001f)
				{
					yield return new GridPoint(centerX, centerZ);
					continue;
				}
				int n = Math.Max(1, (int)Math.Round(Math.PI * 2.0 * (double)r / (double)spacing));
				for (int i = 0; i < n; i++)
				{
					double num = Math.PI * 2.0 * (double)i / (double)n;
					float x = centerX + (float)((double)r * Math.Cos(num));
					float z = centerZ + (float)((double)r * Math.Sin(num));
					yield return new GridPoint(x, z);
				}
			}
		}
	}
	[BepInPlugin("cgaggino.OhMyGrid", "OhMyGrid", "1.0.2")]
	public class OhMyGridPlugin : BaseUnityPlugin
	{
		public enum CenterMode
		{
			Player,
			Fixed,
			Cursor,
			CursorSnap
		}

		public enum GeometryMode
		{
			Off,
			Donut
		}

		[HarmonyPatch(typeof(Player), "TryPlacePiece")]
		private static class TryPlacePiecePatch
		{
			[HarmonyPrefix]
			private static bool Prefix(Player __instance, Piece piece, ref bool __result)
			{
				if (_inDonutPlace)
				{
					return true;
				}
				if ((Object)(object)Instance == (Object)null)
				{
					return true;
				}
				if (Instance._geometryMode.Value == GeometryMode.Off)
				{
					return true;
				}
				GameObject val = PlacementGhostRef.Invoke(__instance);
				if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<Plant>() == (Object)null)
				{
					return true;
				}
				__result = Instance.PlaceDonut(__instance, piece, val);
				return false;
			}
		}

		[HarmonyPatch(typeof(Player), "Interact")]
		private static class InteractPatch
		{
			[HarmonyPrefix]
			private static bool Prefix(Player __instance, GameObject go, bool hold, bool alt)
			{
				if ((Object)(object)Instance == (Object)null || !Instance._massInteractEnabled.Value)
				{
					return true;
				}
				if (hold || alt)
				{
					return true;
				}
				if (!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303))
				{
					return true;
				}
				Instance.MassInteract(__instance);
				return false;
			}
		}

		public const string PluginGuid = "cgaggino.OhMyGrid";

		public const string PluginName = "OhMyGrid";

		public const string PluginVersion = "1.0.2";

		private const float MinRadius = 0f;

		private const float MaxRadius = 32f;

		internal static ManualLogSource Log;

		private readonly Harmony _harmony = new Harmony("cgaggino.OhMyGrid");

		private ConfigEntry<float> _innerRadius;

		private ConfigEntry<float> _outerRadius;

		private ConfigEntry<float> _spacing;

		private ConfigEntry<float> _autoSnapRadius;

		private ConfigEntry<CenterMode> _centerMode;

		private ConfigEntry<GeometryMode> _geometryMode;

		private ConfigEntry<bool> _showCostOverlay;

		private ConfigEntry<bool> _massInteractEnabled;

		private ConfigEntry<float> _massInteractRadius;

		private ConfigEntry<KeyboardShortcut> _dumpGridHotkey;

		private ConfigEntry<KeyboardShortcut> _cycleModeHotkey;

		private ConfigEntry<KeyboardShortcut> _cycleGeometryHotkey;

		private ConfigEntry<KeyboardShortcut> _increaseOuterHotkey;

		private ConfigEntry<KeyboardShortcut> _decreaseOuterHotkey;

		private ConfigEntry<KeyboardShortcut> _increaseInnerHotkey;

		private ConfigEntry<KeyboardShortcut> _decreaseInnerHotkey;

		private ConfigEntry<KeyboardShortcut> _massInteractHotkey;

		private Vector3 _fixedCenter;

		private bool _hasSnap;

		private readonly List<GameObject> _ghostClones = new List<GameObject>();

		private readonly List<Piece> _ghostClonePieces = new List<Piece>();

		private GameObject _lastSourceGhost;

		private Requirement[] _activeResources;

		private GameObject[] _activeGrownPrefabs;

		private string _activePieceLabel;

		private int _validPointsCount;

		private int _totalPointsCount;

		private int _affordableCount = int.MaxValue;

		private bool _cloneFailedForGhost;

		private readonly StringBuilder _overlaySb = new StringBuilder(256);

		private GUIStyle _overlayStyle;

		private static OhMyGridPlugin Instance;

		private static bool _inDonutPlace;

		private static readonly FieldRef<Player, GameObject> PlacementGhostRef = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private static readonly Func<Plant, bool> HaveGrowSpaceCall = AccessTools.MethodDelegate<Func<Plant, bool>>(AccessTools.Method(typeof(Plant), "HaveGrowSpace", (Type[])null, (Type[])null), (object)null, true);

		private void Awake()
		{
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			_innerRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Grid", "InnerRadius", 2f, "Inner radius (m) of the donut. Points inside this radius are skipped.");
			_outerRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Grid", "OuterRadius", 6f, "Outer radius (m) of the donut.");
			_spacing = ((BaseUnityPlugin)this).Config.Bind<float>("Grid", "Spacing", 1f, "Spacing (m) between rings and target spacing along each ring.");
			_autoSnapRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Center", "AutoSnapRadius", 12f, "Search radius (m) for CursorSnap. The donut snaps to the CENTROID of all Plants found within this radius from the cursor — so for concentric donut placement, this should be ≥ the outer radius of the existing donut you're aligning to.");
			_centerMode = ((BaseUnityPlugin)this).Config.Bind<CenterMode>("Center", "Mode", CenterMode.Player, "Current center mode. F7 cycles. Persists across sessions; if the saved value is Fixed it resets to Player on load (no persisted fixed center).");
			if (_centerMode.Value == CenterMode.Fixed)
			{
				_centerMode.Value = CenterMode.Player;
			}
			_geometryMode = ((BaseUnityPlugin)this).Config.Bind<GeometryMode>("Geometry", "Mode", GeometryMode.Donut, "Active geometry. Off = vanilla single-plant (mod disabled). Donut = circular ring(s). F6 cycles. Persists.");
			_showCostOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Overlay", "ShowCostOverlay", true, "Show an on-screen overlay with valid-point count, required seeds and expected yield while a plant ghost is active.");
			_massInteractEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("MassInteract", "Enabled", true, "Shift+E interacts with all Pickable / Fireplace / Smelter switches within MassInteractRadius.");
			_massInteractRadius = ((BaseUnityPlugin)this).Config.Bind<float>("MassInteract", "Radius", 5f, "Search radius (m) for Shift+E mass interact.");
			_dumpGridHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "DumpGrid", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Dump donut grid points around the donut center to the log.");
			_cycleModeHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "CycleCenterMode", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Cycle the donut center mode: Player → Fixed → Cursor → CursorSnap → Player.");
			_cycleGeometryHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "CycleGeometry", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Cycle the geometry: Off → Donut → Off.");
			_increaseOuterHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "IncreaseOuter", new KeyboardShortcut((KeyCode)93, Array.Empty<KeyCode>()), "Increase outer radius by one Spacing step.");
			_decreaseOuterHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "DecreaseOuter", new KeyboardShortcut((KeyCode)91, Array.Empty<KeyCode>()), "Decrease outer radius by one Spacing step.");
			_increaseInnerHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "IncreaseInner", new KeyboardShortcut((KeyCode)93, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Increase inner radius by one Spacing step.");
			_decreaseInnerHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "DecreaseInner", new KeyboardShortcut((KeyCode)91, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Decrease inner radius by one Spacing step.");
			_massInteractHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "MassInteract", new KeyboardShortcut((KeyCode)101, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Mass-interact with all Pickable / Fireplace / Smelter switches in range.");
			Log.LogInfo((object)"OhMyGrid v1.0.2 loaded — hello from the grid.");
			_harmony.PatchAll();
		}

		private void Update()
		{
			HandleHotkeys();
			UpdateGhostClones();
		}

		private void HandleHotkeys()
		{
			//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_0021: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = _dumpGridHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				DumpGrid();
			}
			value = _cycleModeHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				CycleMode();
			}
			value = _cycleGeometryHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				CycleGeometry();
			}
			float num = Mathf.Max(_spacing.Value, 0.01f);
			bool flag = false;
			value = _increaseInnerHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				AdjustRadius(_innerRadius, num);
				flag = true;
			}
			else
			{
				value = _decreaseInnerHotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					AdjustRadius(_innerRadius, 0f - num);
					flag = true;
				}
			}
			if (!flag)
			{
				value = _increaseOuterHotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					AdjustRadius(_outerRadius, num);
				}
				else
				{
					value = _decreaseOuterHotkey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						AdjustRadius(_outerRadius, 0f - num);
					}
				}
			}
			if (!_massInteractEnabled.Value)
			{
				return;
			}
			value = _massInteractHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					MassInteract(localPlayer);
				}
			}
		}

		private void CycleMode()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				GameObject ghost = PlacementGhostRef.Invoke(localPlayer);
				Vector3 donutCenter = GetDonutCenter(localPlayer, ghost);
				_centerMode.Value = NextMode(_centerMode.Value);
				string text;
				if (_centerMode.Value == CenterMode.Fixed)
				{
					_fixedCenter = donutCenter;
					text = $"Mode: Fixed @ ({_fixedCenter.x:0.##}, {_fixedCenter.z:0.##})";
				}
				else
				{
					text = $"Mode: {_centerMode.Value}";
				}
				Log.LogInfo((object)text);
				ShowHudMessage(text);
			}
		}

		private static void ShowHudMessage(string text)
		{
			MessageHud instance = MessageHud.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}

		private static CenterMode NextMode(CenterMode m)
		{
			return m switch
			{
				CenterMode.Player => CenterMode.Fixed, 
				CenterMode.Fixed => CenterMode.Cursor, 
				CenterMode.Cursor => CenterMode.CursorSnap, 
				CenterMode.CursorSnap => CenterMode.Player, 
				_ => CenterMode.Player, 
			};
		}

		private void CycleGeometry()
		{
			Log.LogInfo((object)$"Geometry: {NextGeometry(_geometryMode.Value)}");
			_geometryMode.Value = NextGeometry(_geometryMode.Value);
			string text = $"Geometry: {_geometryMode.Value}";
			Log.LogInfo((object)text);
			ShowHudMessage(text);
		}

		private static GeometryMode NextGeometry(GeometryMode g)
		{
			return g switch
			{
				GeometryMode.Off => GeometryMode.Donut, 
				GeometryMode.Donut => GeometryMode.Off, 
				_ => GeometryMode.Donut, 
			};
		}

		private void AdjustRadius(ConfigEntry<float> entry, float delta)
		{
			entry.Value = Mathf.Clamp(entry.Value + delta, 0f, 32f);
			Log.LogInfo((object)($"{((ConfigEntryBase)entry).Definition.Key} = {entry.Value:0.##}m " + $"(inner={_innerRadius.Value:0.##} outer={_outerRadius.Value:0.##})"));
		}

		private Vector3 GetDonutCenter(Player player, GameObject ghost)
		{
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
			if (_centerMode.Value != CenterMode.CursorSnap && _hasSnap)
			{
				_hasSnap = false;
			}
			switch (_centerMode.Value)
			{
			case CenterMode.Fixed:
				return _fixedCenter;
			case CenterMode.Cursor:
				if (!((Object)(object)ghost != (Object)null))
				{
					return ((Component)player).transform.position;
				}
				return ghost.transform.position;
			case CenterMode.CursorSnap:
			{
				Vector3 val = (((Object)(object)ghost != (Object)null) ? ghost.transform.position : ((Component)player).transform.position);
				Vector3? val2 = FindPlantCentroid(val, _autoSnapRadius.Value);
				if (val2.HasValue)
				{
					if (!_hasSnap)
					{
						Vector3 value = val2.Value;
						Log.LogInfo((object)$"AutoSnap → centroid ({value.x:0.##}, {value.z:0.##}).");
						ShowHudMessage("AutoSnap: locked");
						_hasSnap = true;
					}
					return val2.Value;
				}
				if (_hasSnap)
				{
					Log.LogInfo((object)"AutoSnap: no plants in range.");
					ShowHudMessage("AutoSnap: lost");
					_hasSnap = false;
				}
				return val;
			}
			default:
				return ((Component)player).transform.position;
			}
		}

		private static Vector3? FindPlantCentroid(Vector3 from, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			Collider[] array = Physics.OverlapSphere(from, radius);
			Vector3 val = Vector3.zero;
			int num = 0;
			HashSet<int> hashSet = new HashSet<int>();
			for (int i = 0; i < array.Length; i++)
			{
				Plant componentInParent = ((Component)array[i]).GetComponentInParent<Plant>();
				if (!((Object)(object)componentInParent == (Object)null))
				{
					int instanceID = ((Object)componentInParent).GetInstanceID();
					if (hashSet.Add(instanceID))
					{
						val += ((Component)componentInParent).transform.position;
						num++;
					}
				}
			}
			if (num == 0)
			{
				return null;
			}
			return val / (float)num;
		}

		private void DumpGrid()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Log.LogInfo((object)"DumpGrid: no local player (in menu?).");
				return;
			}
			Vector3 donutCenter = GetDonutCenter(localPlayer, PlacementGhostRef.Invoke(localPlayer));
			Log.LogInfo((object)($"Donut grid @ ({donutCenter.x:0.##}, {donutCenter.z:0.##}) " + $"inner={_innerRadius.Value} outer={_outerRadius.Value} spacing={_spacing.Value}"));
			int num = 0;
			foreach (GridPoint item in GridGenerator.Donut(donutCenter.x, donutCenter.z, _innerRadius.Value, _outerRadius.Value, _spacing.Value))
			{
				Log.LogInfo((object)$"  [{num++}] {item}");
			}
			Log.LogInfo((object)$"Donut grid: {num} points.");
		}

		private void UpdateGhostClones()
		{
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_048b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0498: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0411: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				HideAllClones();
				return;
			}
			if (_geometryMode.Value == GeometryMode.Off)
			{
				HideAllClones();
				_lastSourceGhost = null;
				_activeResources = null;
				_activeGrownPrefabs = null;
				_activePieceLabel = null;
				_validPointsCount = 0;
				_totalPointsCount = 0;
				return;
			}
			GameObject val = PlacementGhostRef.Invoke(localPlayer);
			Plant val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Plant>() : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				HideAllClones();
				_lastSourceGhost = null;
				_activeResources = null;
				_activeGrownPrefabs = null;
				_activePieceLabel = null;
				_validPointsCount = 0;
				_totalPointsCount = 0;
				return;
			}
			if ((Object)(object)val != (Object)(object)_lastSourceGhost)
			{
				DestroyAllClones();
				_lastSourceGhost = val;
				_cloneFailedForGhost = false;
				Piece component = val.GetComponent<Piece>();
				_activeResources = (((Object)(object)component != (Object)null) ? component.m_resources : null);
				_activeGrownPrefabs = val2.m_grownPrefabs;
				_activePieceLabel = (((Object)(object)component != (Object)null) ? LocalizeOrRaw(component.m_name) : ((Object)val).name);
			}
			Vector3 donutCenter = GetDonutCenter(localPlayer, val);
			List<GridPoint> list = new List<GridPoint>();
			foreach (GridPoint item in GridGenerator.Donut(donutCenter.x, donutCenter.z, _innerRadius.Value, _outerRadius.Value, _spacing.Value))
			{
				list.Add(item);
			}
			_totalPointsCount = list.Count;
			if (_cloneFailedForGhost)
			{
				HideAllClones();
				_validPointsCount = 0;
				return;
			}
			if (_ghostClones.Count < list.Count)
			{
				int count = _ghostClones.Count;
				try
				{
					while (_ghostClones.Count < list.Count)
					{
						GameObject val3 = CreateGhostClone(val);
						_ghostClones.Add(val3);
						_ghostClonePieces.Add(val3.GetComponent<Piece>());
					}
					Log.LogDebug((object)$"Ghost clones: {count} → {_ghostClones.Count} for '{((Object)val).name}' (active={val.activeSelf})");
				}
				catch (Exception ex)
				{
					_cloneFailedForGhost = true;
					Log.LogError((object)("Ghost preview disabled for '" + ((Object)val).name + "': cloning threw " + ex.GetType().Name + ": " + ex.Message));
					HideAllClones();
					_validPointsCount = 0;
					return;
				}
			}
			Vector3 position = val.transform.position;
			Quaternion rotation = val.transform.rotation;
			Vector2 val4 = default(Vector2);
			((Vector2)(ref val4))..ctor(((Component)localPlayer).transform.position.x, ((Component)localPlayer).transform.position.z);
			float num = localPlayer.m_maxPlaceDistance * localPlayer.m_maxPlaceDistance;
			bool needCultivatedGround = val2.m_needCultivatedGround;
			Piece component2 = val.GetComponent<Piece>();
			int num2 = CountAffordable(localPlayer, component2);
			int num3 = 0;
			Vector3 val6 = default(Vector3);
			for (int i = 0; i < list.Count; i++)
			{
				GridPoint gridPoint = list[i];
				GameObject val5 = _ghostClones[i];
				float num4 = SampleGroundY(gridPoint.X, gridPoint.Z, position.y);
				((Vector3)(ref val6))..ctor(gridPoint.X, num4, gridPoint.Z);
				val5.transform.position = val6;
				val5.transform.rotation = rotation;
				if (!val5.activeSelf)
				{
					val5.SetActive(true);
				}
				float num5 = gridPoint.X - val4.x;
				float num6 = gridPoint.Z - val4.y;
				bool num7 = num5 * num5 + num6 * num6 > num;
				bool flag = needCultivatedGround && !IsCultivatedAt(val6);
				val.transform.position = val6;
				bool flag2 = !HaveGrowSpaceCall(val2);
				bool flag3 = num7 || flag2 || flag;
				bool flag4 = !flag3 && num3 >= num2;
				if (!flag3)
				{
					num3++;
				}
				Piece val7 = _ghostClonePieces[i];
				if ((Object)(object)val7 != (Object)null)
				{
					val7.SetInvalidPlacementHeightlight(flag3 || flag4);
				}
			}
			val.transform.position = position;
			val.transform.rotation = rotation;
			_validPointsCount = num3;
			_affordableCount = num2;
			for (int j = list.Count; j < _ghostClones.Count; j++)
			{
				if (_ghostClones[j].activeSelf)
				{
					_ghostClones[j].SetActive(false);
				}
			}
		}

		private static GameObject CreateGhostClone(GameObject ghost)
		{
			bool activeSelf = ghost.activeSelf;
			ghost.SetActive(false);
			GameObject val;
			try
			{
				val = Object.Instantiate<GameObject>(ghost);
			}
			finally
			{
				ghost.SetActive(activeSelf);
			}
			((Object)val).name = ((Object)ghost).name + " (OhMyGrid ghost)";
			Plant[] componentsInChildren = val.GetComponentsInChildren<Plant>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
			}
			ZNetView[] componentsInChildren2 = val.GetComponentsInChildren<ZNetView>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren2[i]);
			}
			Collider[] componentsInChildren3 = val.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren3.Length; i++)
			{
				componentsInChildren3[i].enabled = false;
			}
			val.SetActive(true);
			return val;
		}

		private static bool IsFreeBuild(Player player, Piece piece)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (player.NoCostCheat())
			{
				return true;
			}
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)piece != (Object)null)
			{
				return instance.GetGlobalKey(piece.FreeBuildKey());
			}
			return false;
		}

		private static int CountAffordable(Player player, Piece piece)
		{
			if ((Object)(object)piece == (Object)null || IsFreeBuild(player, piece))
			{
				return int.MaxValue;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return 0;
			}
			int num = int.MaxValue;
			Requirement[] resources = piece.m_resources;
			if (resources == null)
			{
				return int.MaxValue;
			}
			foreach (Requirement val in resources)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
				{
					SharedData val2 = val.m_resItem.m_itemData?.m_shared;
					if (val2 != null)
					{
						int num2 = inventory.CountItems(val2.m_name, -1, true);
						num = Mathf.Min(num, num2 / val.m_amount);
					}
				}
			}
			return num;
		}

		private static string DescribeStock(Player player, Piece piece)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null || (Object)(object)piece == (Object)null || piece.m_resources == null)
			{
				return "?";
			}
			List<string> list = new List<string>();
			Requirement[] resources = piece.m_resources;
			foreach (Requirement val in resources)
			{
				SharedData val2 = val?.m_resItem?.m_itemData?.m_shared;
				if (val2 != null && val.m_amount > 0)
				{
					list.Add($"{val2.m_name}×{val.m_amount} have {inventory.CountItems(val2.m_name, -1, true)}");
				}
			}
			return string.Join(", ", list);
		}

		private static float SampleGroundY(float x, float z, float fallback)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return fallback;
			}
			float result = default(float);
			if (instance.GetGroundHeight(new Vector3(x, 1000f, z), ref result))
			{
				return result;
			}
			return fallback;
		}

		private void HideAllClones()
		{
			for (int i = 0; i < _ghostClones.Count; i++)
			{
				GameObject val = _ghostClones[i];
				if ((Object)(object)val != (Object)null && val.activeSelf)
				{
					val.SetActive(false);
				}
			}
		}

		private void DestroyAllClones()
		{
			for (int i = 0; i < _ghostClones.Count; i++)
			{
				if ((Object)(object)_ghostClones[i] != (Object)null)
				{
					Object.Destroy((Object)(object)_ghostClones[i]);
				}
			}
			_ghostClones.Clear();
			_ghostClonePieces.Clear();
		}

		private bool PlaceDonut(Player player, Piece piece, GameObject ghost)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			Vector3 donutCenter = GetDonutCenter(player, ghost);
			Vector3 position = ghost.transform.position;
			Quaternion rotation = ghost.transform.rotation;
			Plant component = ghost.GetComponent<Plant>();
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(((Component)player).transform.position.x, ((Component)player).transform.position.z);
			float num = player.m_maxPlaceDistance * player.m_maxPlaceDistance;
			bool flag = (Object)(object)component != (Object)null && component.m_needCultivatedGround;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			bool flag2 = false;
			bool flag3 = IsFreeBuild(player, piece);
			int num6 = 0;
			int num7 = CountAffordable(player, piece);
			string text = DescribeStock(player, piece);
			_inDonutPlace = true;
			try
			{
				Vector3 val2 = default(Vector3);
				foreach (GridPoint item in GridGenerator.Donut(donutCenter.x, donutCenter.z, _innerRadius.Value, _outerRadius.Value, _spacing.Value))
				{
					if (!flag3 && CountAffordable(player, piece) < num6 + 1)
					{
						flag2 = true;
						break;
					}
					float num8 = item.X - val.x;
					float num9 = item.Z - val.y;
					if (num8 * num8 + num9 * num9 > num)
					{
						num4++;
						continue;
					}
					float num10 = SampleGroundY(item.X, item.Z, position.y);
					((Vector3)(ref val2))..ctor(item.X, num10, item.Z);
					if (flag && !IsCultivatedAt(val2))
					{
						num5++;
						continue;
					}
					Vector3 val3 = val2;
					if ((Object)(object)component != (Object)null)
					{
						ghost.transform.position = val3;
						if (!HaveGrowSpaceCall(component))
						{
							num3++;
							continue;
						}
					}
					try
					{
						player.PlacePiece(piece, val3, rotation, false, false);
						num2++;
						if (!flag3)
						{
							if (num6 > 0)
							{
								player.ConsumeResources(piece.m_resources, 0, -1, 1);
							}
							num6 = 1;
						}
					}
					catch (Exception ex)
					{
						Log.LogWarning((object)$"PlacePiece failed at ({item.X:0.##}, {item.Z:0.##}): {ex.Message}");
					}
				}
			}
			finally
			{
				_inDonutPlace = false;
				ghost.transform.position = position;
				ghost.transform.rotation = rotation;
			}
			Log.LogInfo((object)($"Donut plant: planted={num2} noSpace={num3} tooFar={num4} notCultivated={num5}" + " | cost: affordableBefore=" + ((num7 == int.MaxValue) ? "∞" : num7.ToString()) + " stockBefore=[" + text + "] stockAfterMod=[" + DescribeStock(player, piece) + "]" + $" paidHere={((!flag3) ? Mathf.Max(num2 - 1, 0) : 0)} paidByVanilla={((num2 > 0 && !flag3) ? 1 : 0)}" + (flag3 ? " (free build)" : "") + (flag2 ? " (out of resources)" : "")));
			return num2 > 0;
		}

		private static bool IsCultivatedAt(Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			Heightmap val = Heightmap.FindHeightmap(pos);
			if ((Object)(object)val != (Object)null)
			{
				return val.IsCultivated(pos);
			}
			return false;
		}

		private void OnGUI()
		{
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Expected O, but got Unknown
			//IL_0351: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0311: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_0322: Expected O, but got Unknown
			//IL_0327: Expected O, but got Unknown
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			if (!_showCostOverlay.Value || _totalPointsCount == 0 || (_activeResources == null && _activeGrownPrefabs == null))
			{
				return;
			}
			StringBuilder overlaySb = _overlaySb;
			overlaySb.Length = 0;
			int num = Mathf.Min(_validPointsCount, _affordableCount);
			overlaySb.Append(_activePieceLabel ?? "Plant").Append(" · valid ").Append(_validPointsCount)
				.Append('/')
				.Append(_totalPointsCount);
			if (num < _validPointsCount)
			{
				overlaySb.Append(" · can afford ").Append(num);
			}
			overlaySb.Append("\nDonut · inner ").Append(_innerRadius.Value.ToString("0.##")).Append("m · outer ")
				.Append(_outerRadius.Value.ToString("0.##"))
				.Append("m · spacing ")
				.Append(_spacing.Value.ToString("0.##"))
				.Append('m');
			if (_activeResources != null)
			{
				for (int i = 0; i < _activeResources.Length; i++)
				{
					Requirement val = _activeResources[i];
					if (val == null || (Object)(object)val.m_resItem == (Object)null || val.m_amount <= 0)
					{
						continue;
					}
					SharedData val2 = val.m_resItem.m_itemData?.m_shared;
					if (val2 != null)
					{
						overlaySb.Append('\n').Append("Need: ").Append(LocalizeOrRaw(val2.m_name))
							.Append(" × ")
							.Append(val.m_amount * num);
						Inventory val3 = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
						if (val3 != null)
						{
							overlaySb.Append(" (have ").Append(val3.CountItems(val2.m_name, -1, true)).Append(')');
						}
					}
				}
			}
			if (_activeGrownPrefabs != null && _activeGrownPrefabs.Length != 0)
			{
				GameObject val4 = _activeGrownPrefabs[0];
				Pickable val5 = (((Object)(object)val4 != (Object)null) ? val4.GetComponent<Pickable>() : null);
				if ((Object)(object)val5 != (Object)null && (Object)(object)val5.m_itemPrefab != (Object)null)
				{
					ItemDrop component = val5.m_itemPrefab.GetComponent<ItemDrop>();
					SharedData val6 = ((!((Object)(object)component != (Object)null)) ? null : component.m_itemData?.m_shared);
					if (val6 != null)
					{
						overlaySb.Append('\n').Append("Yield: ~").Append(val5.m_amount * num)
							.Append(' ')
							.Append(LocalizeOrRaw(val6.m_name));
					}
				}
			}
			if (_overlayStyle == null)
			{
				_overlayStyle = new GUIStyle(GUI.skin.box)
				{
					fontStyle = (FontStyle)1,
					fontSize = 14,
					alignment = (TextAnchor)4,
					wordWrap = false,
					padding = new RectOffset(12, 12, 8, 8)
				};
				_overlayStyle.normal.textColor = Color.white;
			}
			string text = overlaySb.ToString();
			GUIContent val7 = new GUIContent(text);
			float num2 = _overlayStyle.CalcSize(val7).x + 8f;
			float num3 = _overlayStyle.CalcHeight(val7, num2);
			float num4 = ((float)Screen.width - num2) * 0.5f;
			float num5 = 10f;
			GUI.Box(new Rect(num4, num5, num2, num3), text, _overlayStyle);
		}

		private static string LocalizeOrRaw(string key)
		{
			if (string.IsNullOrEmpty(key))
			{
				return string.Empty;
			}
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return key;
			}
			return instance.Localize(key);
		}

		private static string TryGetItemDisplayName(GameObject itemPrefab)
		{
			if ((Object)(object)itemPrefab == (Object)null)
			{
				return null;
			}
			ItemDrop component = itemPrefab.GetComponent<ItemDrop>();
			SharedData val = ((!((Object)(object)component != (Object)null)) ? null : component.m_itemData?.m_shared);
			if (val == null || string.IsNullOrEmpty(val.m_name))
			{
				return ((Object)itemPrefab).name;
			}
			return LocalizeOrRaw(val.m_name);
		}

		private void MassInteract(Player player)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			if ((Object)(object)hoverObject == (Object)null)
			{
				ShowHudMessage("Shift+E: no target");
				return;
			}
			float value = _massInteractRadius.Value;
			Collider[] array = Physics.OverlapSphere(((Component)player).transform.position, value);
			HashSet<int> hashSet = new HashSet<int>();
			int num = 0;
			Pickable componentInParent = hoverObject.GetComponentInParent<Pickable>();
			string arg;
			if ((Object)(object)componentInParent != (Object)null)
			{
				GameObject itemPrefab = componentInParent.m_itemPrefab;
				string text = TryGetItemDisplayName(itemPrefab);
				arg = "picked " + (text ?? "items");
				Log.LogInfo((object)string.Format("Shift+E target: pickable '{0}' radius={1}", ((Object)(object)itemPrefab != (Object)null) ? ((Object)itemPrefab).name : "?", value));
				for (int i = 0; i < array.Length; i++)
				{
					Pickable componentInParent2 = ((Component)array[i]).GetComponentInParent<Pickable>();
					if ((Object)(object)componentInParent2 == (Object)null || !hashSet.Add(((Object)componentInParent2).GetInstanceID()) || (Object)(object)componentInParent2.m_itemPrefab != (Object)(object)itemPrefab)
					{
						continue;
					}
					try
					{
						if (componentInParent2.Interact((Humanoid)(object)player, false, false))
						{
							num++;
						}
					}
					catch (Exception ex)
					{
						Log.LogWarning((object)("MassInteract pickable: " + ex.Message));
					}
				}
			}
			else
			{
				Fireplace fire = hoverObject.GetComponentInParent<Fireplace>();
				if (fire != null)
				{
					arg = "fueled " + LocalizeOrRaw(fire.m_name);
					num = FeedUntilRefused(() => fire.Interact((Humanoid)(object)player, false, true), "fireplace");
					Log.LogInfo((object)$"Shift+E target: fireplace '{fire.m_name}' fuel={fire.m_fuelItem?.m_itemData?.m_shared?.m_name} added={num}");
				}
				else
				{
					Smelter componentInParent3 = hoverObject.GetComponentInParent<Smelter>();
					if (componentInParent3 == null)
					{
						ShowHudMessage("Shift+E: target not supported");
						return;
					}
					arg = "fed " + LocalizeOrRaw(componentInParent3.m_name);
					Switch componentInParent4 = hoverObject.GetComponentInParent<Switch>();
					bool flag = (Object)(object)componentInParent4 != (Object)null && (Object)(object)componentInParent4 == (Object)(object)componentInParent3.m_addOreSwitch;
					bool flag2 = (Object)(object)componentInParent4 != (Object)null && (Object)(object)componentInParent4 == (Object)(object)componentInParent3.m_addWoodSwitch;
					int num2 = 0;
					int num3 = 0;
					if ((Object)(object)componentInParent3.m_addOreSwitch != (Object)null && !flag2)
					{
						Switch sw = componentInParent3.m_addOreSwitch;
						num2 = FeedUntilRefused(() => sw.Interact((Humanoid)(object)player, false, false), "smelter ore");
					}
					if ((Object)(object)componentInParent3.m_addWoodSwitch != (Object)null && !flag)
					{
						Switch sw2 = componentInParent3.m_addWoodSwitch;
						num3 = FeedUntilRefused(() => sw2.Interact((Humanoid)(object)player, false, false), "smelter fuel");
					}
					num = num2 + num3;
					Log.LogInfo((object)string.Format("Shift+E target: smelter '{0}' slot={1} oreAdded={2} fuelAdded={3} fuel={4}", componentInParent3.m_name, flag ? "ore" : (flag2 ? "fuel" : "both"), num2, num3, componentInParent3.m_fuelItem?.m_itemData?.m_shared?.m_name));
				}
			}
			string text2 = $"Shift+E: {arg} × {num}";
			Log.LogInfo((object)text2);
			ShowHudMessage(text2);
		}

		private static int FeedUntilRefused(Func<bool> feedOnce, string what)
		{
			int i = 0;
			try
			{
				for (; i < 64; i++)
				{
					if (!feedOnce())
					{
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("MassInteract " + what + ": " + ex.Message));
			}
			return i;
		}

		private void OnDestroy()
		{
			DestroyAllClones();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
}