Decompiled source of ValheimFloorPlan v1.0.3

BepInEx\plugins\ValheimFloorPlan\ValheimFloorPlan.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("ValheimFloorPlan")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("A floor plan building tool mod for Valheim")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: AssemblyInformationalVersion("1.0.3+680bffc7332f1c6a2517c65426a0cfb48c4aceb3")]
[assembly: AssemblyProduct("ValheimFloorPlan")]
[assembly: AssemblyTitle("ValheimFloorPlan")]
[assembly: AssemblyVersion("1.0.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 ValheimFloorPlan
{
	public enum WallFaceMode
	{
		Default,
		Outer,
		Inner
	}
	public class FloorPlanPiece
	{
		public int Col { get; set; }

		public int Row { get; set; }

		public string Type { get; set; } = "";


		public int Rotation { get; set; }

		public WallFaceMode WallFace { get; set; } = WallFaceMode.Default;

	}
	public class FloorPlan
	{
		public int Cols { get; set; }

		public int Rows { get; set; }

		public List<FloorPlanPiece> Pieces { get; } = new List<FloorPlanPiece>();


		public static FloorPlan Load(string path)
		{
			FloorPlan floorPlan = new FloorPlan();
			string[] array = File.ReadAllLines(path);
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.StartsWith("cols="))
				{
					floorPlan.Cols = int.Parse(text2.Substring(5));
				}
				else if (text2.StartsWith("rows="))
				{
					floorPlan.Rows = int.Parse(text2.Substring(5));
				}
				else
				{
					if (!text2.StartsWith("piece,"))
					{
						continue;
					}
					string[] array2 = text2.Split(new char[1] { ',' });
					if (array2.Length < 4)
					{
						continue;
					}
					int rotation = 0;
					int num = -1;
					if (array2.Length > 4)
					{
						if (int.TryParse(array2[4], out var result))
						{
							rotation = result;
							num = ((array2.Length > 5) ? 5 : (-1));
						}
						else
						{
							num = 4;
						}
					}
					floorPlan.Pieces.Add(new FloorPlanPiece
					{
						Col = int.Parse(array2[1]),
						Row = int.Parse(array2[2]),
						Type = array2[3],
						Rotation = rotation,
						WallFace = ((num >= 0) ? ParseWallFace(array2[num]) : WallFaceMode.Default)
					});
				}
			}
			return floorPlan;
		}

		private static WallFaceMode ParseWallFace(string raw)
		{
			string text = (raw ?? string.Empty).Trim().ToLowerInvariant();
			if (text == "outer" || text == "out" || text == "o")
			{
				return WallFaceMode.Outer;
			}
			if (text == "inner" || text == "in" || text == "i")
			{
				return WallFaceMode.Inner;
			}
			return WallFaceMode.Default;
		}
	}
	public class FloorPlanBuilder : MonoBehaviour
	{
		private const float PLACE_DELAY = 0.05f;

		private const float ORIGIN_MARKER_RADIUS = 0.75f;

		private const float ORIGIN_MARKER_LIFT = 0.45f;

		private const float PREVIEW_EDGE_RISK_SAMPLE_INTERVAL = 0.45f;

		private const float PREVIEW_EDGE_RISK_HINT_INTERVAL = 2f;

		private const float PREVIEW_EDGE_RISK_HINT_START_DELAY = 2.5f;

		private const float PREVIEW_STEEP_RELIEF_WARN = 6f;

		private const float PREVIEW_RISK_MARKER_RADIUS = 0.45f;

		private const float PREVIEW_RISK_MARKER_LIFT = 0.18f;

		public const string VFP_TAG = "vfp_build";

		private float _undoConfirmationExpireAt = 0f;

		private int _undoConfirmationPieceCount = 0;

		private int _undoConfirmationTerrainChunks = 0;

		private Coroutine _undoCountdownCoroutine = null;

		private Coroutine _undoRefreshCoroutine = null;

		private const float UNDO_REFRESH_RADIUS = 120f;

		private const float UNDO_REFRESH_DURATION = 2.5f;

		private const float UNDO_REFRESH_INTERVAL = 0.25f;

		private const float UNDO_RADIUS = 75f;

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

		private bool _previewActive = false;

		private FloorPlan? _previewPlan = null;

		private GameObject? _previewGo = null;

		private MeshFilter? _previewPadWalls = null;

		private MeshFilter? _previewOuterWalls = null;

		private LineRenderer? _previewOriginMarker = null;

		private float _previewRotationDeg = 0f;

		private Vector3 _previewOrigin = Vector3.zero;

		private TerrainLeveler.EdgeRiskLevel _previewEdgeRisk = TerrainLeveler.EdgeRiskLevel.Low;

		private float _previewEdgeRelief = 0f;

		private float _previewEdgeIrregularity = 0f;

		private float _previewEdgeMaxStep = 0f;

		private float _previewRiskNextSampleAt = 0f;

		private float _previewRiskNextHintAt = 0f;

		private float _previewRiskHintsEnabledAt = 0f;

		private bool _previewRiskDirty = true;

		private readonly List<Vector3> _previewRiskHotspots = new List<Vector3>();

		private readonly List<LineRenderer> _previewRiskMarkers = new List<LineRenderer>();

		private readonly List<Vector3> _previewRiskRenderPoints = new List<Vector3>();

		private int _previewRiskBottomCount = 0;

		public static FloorPlanBuilder Instance { get; private set; }

		public bool CanUndo => _lastPlaced.Count > 0 || TerrainSnapshot.HasSnapshot;

		private void Awake()
		{
			Instance = this;
		}

		public void StartPreview(string path)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			if (_previewActive)
			{
				CancelPreview();
			}
			FloorPlan floorPlan;
			try
			{
				floorPlan = FloorPlan.Load(path);
			}
			catch (Exception ex)
			{
				ValheimFloorPlanPlugin.Log.LogError((object)("Failed to load floor plan: " + ex.Message));
				return;
			}
			_previewPlan = floorPlan;
			_previewActive = true;
			Player localPlayer = Player.m_localPlayer;
			_previewOrigin = (((Object)(object)localPlayer != (Object)null) ? GetBuildOrigin(localPlayer) : Vector3.zero);
			_previewGo = new GameObject("VFP_Preview");
			_previewPadWalls = MakeWallRing(_previewGo, "VFP_WallsPad", new Color(1f, 1f, 1f, 0.28f));
			_previewOuterWalls = MakeWallRing(_previewGo, "VFP_WallsOuter", new Color(0.2f, 1f, 0.2f, 0.24f));
			_previewOriginMarker = MakeLine(_previewGo, new Color(0.18f, 0.05f, 0.02f, 0.98f), 0.14f, 7);
			_previewEdgeRisk = TerrainLeveler.EdgeRiskLevel.Low;
			_previewEdgeRelief = 0f;
			_previewEdgeIrregularity = 0f;
			_previewEdgeMaxStep = 0f;
			_previewRiskDirty = true;
			_previewRiskNextSampleAt = 0f;
			_previewRiskNextHintAt = Time.time + 2.5f;
			_previewRiskHintsEnabledAt = Time.time + 2.5f;
			ValheimFloorPlanPlugin.Log.LogInfo((object)($"[FloorPlanBuilder] Preview active ({floorPlan.Pieces.Count} pieces, " + $"{floorPlan.Cols}×{floorPlan.Rows} cells). {ValheimFloorPlanPlugin.PreviewConfirmKey} to build, RMB/ESC to cancel."));
			ValheimFloorPlanPlugin.ShowWrappedMessage((MessageType)2, $"ValheimFloorPlan: {ValheimFloorPlanPlugin.PreviewMoveLeftKey}/{ValheimFloorPlanPlugin.PreviewMoveRightKey}/{ValheimFloorPlanPlugin.PreviewMoveForwardKey}/{ValheimFloorPlanPlugin.PreviewMoveBackwardKey} move | {ValheimFloorPlanPlugin.PreviewRotateLeftKey}/{ValheimFloorPlanPlugin.PreviewRotateRightKey} rotate | {ValheimFloorPlanPlugin.PreviewFineAdjustKey} fine | {ValheimFloorPlanPlugin.PreviewConfirmKey} to place | RMB/{ValheimFloorPlanPlugin.PreviewCancelKey} cancel");
		}

		private static MeshFilter MakeWallRing(GameObject parent, string name, Color color)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: 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)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent.transform, false);
			MeshFilter val2 = val.AddComponent<MeshFilter>();
			MeshRenderer val3 = val.AddComponent<MeshRenderer>();
			((Renderer)val3).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val3).receiveShadows = false;
			Material val4 = new Material(Shader.Find("Sprites/Default"));
			val4.color = color;
			((Renderer)val3).sharedMaterial = val4;
			Mesh val5 = new Mesh
			{
				name = name + "_Mesh"
			};
			val5.vertices = (Vector3[])(object)new Vector3[16];
			val5.uv = (Vector2[])(object)new Vector2[16];
			for (int i = 0; i < 16; i++)
			{
				int num = i % 4;
				Vector2[] uv = val5.uv;
				int num2 = i;
				if (1 == 0)
				{
				}
				Vector2 val6 = (Vector2)(num switch
				{
					0 => new Vector2(0f, 0f), 
					1 => new Vector2(1f, 0f), 
					2 => new Vector2(1f, 1f), 
					_ => new Vector2(0f, 1f), 
				});
				if (1 == 0)
				{
				}
				uv[num2] = val6;
			}
			val5.triangles = new int[48]
			{
				0, 1, 2, 0, 2, 3, 2, 1, 0, 3,
				2, 0, 4, 5, 6, 4, 6, 7, 6, 5,
				4, 7, 6, 4, 8, 9, 10, 8, 10, 11,
				10, 9, 8, 11, 10, 8, 12, 13, 14, 12,
				14, 15, 14, 13, 12, 15, 14, 12
			};
			val5.RecalculateNormals();
			val2.sharedMesh = val5;
			return val2;
		}

		private static LineRenderer MakeLine(GameObject parent, Color color, float width, int positionCount = 5)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_006d: 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)
			GameObject val = new GameObject("VFP_Line");
			val.transform.SetParent(parent.transform, false);
			LineRenderer val2 = val.AddComponent<LineRenderer>();
			val2.useWorldSpace = true;
			val2.loop = false;
			val2.positionCount = positionCount;
			val2.widthMultiplier = width;
			((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val2).receiveShadows = false;
			((Renderer)val2).sharedMaterial = new Material(Shader.Find("Sprites/Default"));
			val2.startColor = color;
			val2.endColor = color;
			return val2;
		}

		private void CancelPreview()
		{
			//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)
			_previewActive = false;
			_previewPlan = null;
			_previewPadWalls = null;
			_previewOuterWalls = null;
			_previewOriginMarker = null;
			_previewRotationDeg = 0f;
			_previewOrigin = Vector3.zero;
			_previewEdgeRisk = TerrainLeveler.EdgeRiskLevel.Low;
			_previewEdgeRelief = 0f;
			_previewEdgeIrregularity = 0f;
			_previewEdgeMaxStep = 0f;
			_previewRiskDirty = true;
			_previewRiskNextSampleAt = 0f;
			_previewRiskNextHintAt = 0f;
			_previewRiskHintsEnabledAt = 0f;
			_previewRiskHotspots.Clear();
			_previewRiskRenderPoints.Clear();
			_previewRiskMarkers.Clear();
			if ((Object)(object)_previewGo != (Object)null)
			{
				Object.Destroy((Object)(object)_previewGo);
				_previewGo = null;
			}
		}

		private void Update()
		{
			if (_previewActive && _previewPlan != null)
			{
				UpdatePreviewMode();
			}
		}

		public void ToggleTearRepairMode()
		{
		}

		public void ToggleTerrainClipMode()
		{
		}

		private void UpdatePreviewMode()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: 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)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: 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_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: 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_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: 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_01d9: 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)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: 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)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			if (_previewPlan == null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				CancelPreview();
				return;
			}
			UpdatePreviewPosition(_previewOrigin);
			bool previewChanged = false;
			bool flag = IsFineAdjustHeld();
			float num = (flag ? ValheimFloorPlanPlugin.PreviewFineRotateStepDeg : ValheimFloorPlanPlugin.PreviewRotateStepDeg);
			float num2 = (flag ? ValheimFloorPlanPlugin.PreviewFineMoveStep : ValheimFloorPlanPlugin.PreviewMoveStep);
			if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewRotateLeftKey))
			{
				_previewRotationDeg = (_previewRotationDeg - num + 360f) % 360f;
				previewChanged = true;
				((Character)localPlayer).Message(ValheimFloorPlanPlugin.ProgressMessageType, $"ValheimFloorPlan: Rotation {_previewRotationDeg:F0}°", 0, (Sprite)null);
			}
			else if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewRotateRightKey))
			{
				_previewRotationDeg = (_previewRotationDeg + num) % 360f;
				previewChanged = true;
				((Character)localPlayer).Message(ValheimFloorPlanPlugin.ProgressMessageType, $"ValheimFloorPlan: Rotation {_previewRotationDeg:F0}°", 0, (Sprite)null);
			}
			Vector3 forward = Vector3.forward;
			Vector3 right = Vector3.right;
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null)
			{
				forward = ((Component)main).transform.forward;
				forward.y = 0f;
				if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f)
				{
					((Vector3)(ref forward)).Normalize();
					((Vector3)(ref right))..ctor(forward.z, 0f, 0f - forward.x);
				}
				else
				{
					forward = Vector3.forward;
					right = Vector3.right;
				}
			}
			Vector3 val = Vector3.zero;
			if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewMoveForwardKey))
			{
				val = forward * num2;
			}
			else if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewMoveBackwardKey))
			{
				val = -forward * num2;
			}
			else if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewMoveRightKey))
			{
				val = right * num2;
			}
			else if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewMoveLeftKey))
			{
				val = -right * num2;
			}
			if (val != Vector3.zero)
			{
				_previewOrigin += val;
				previewChanged = true;
				((Character)localPlayer).Message(ValheimFloorPlanPlugin.ProgressMessageType, $"ValheimFloorPlan: Origin ({_previewOrigin.x:F1}, {_previewOrigin.z:F1})", 0, (Sprite)null);
			}
			UpdatePreviewEdgeRisk(localPlayer, previewChanged);
			if (Input.GetMouseButtonDown(1) || IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewCancelKey))
			{
				CancelPreview();
				((Character)localPlayer).Message((MessageType)2, "ValheimFloorPlan: Build cancelled.", 0, (Sprite)null);
				return;
			}
			bool flag2 = (Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus();
			if (IsPreviewKeyDown(ValheimFloorPlanPlugin.PreviewConfirmKey) && !flag2)
			{
				FloorPlan previewPlan = _previewPlan;
				float previewRotationDeg = _previewRotationDeg;
				Vector3 previewOrigin = _previewOrigin;
				TerrainLeveler.EdgeRiskLevel previewEdgeRisk = _previewEdgeRisk;
				float previewEdgeRelief = _previewEdgeRelief;
				float previewEdgeMaxStep = _previewEdgeMaxStep;
				float previewEdgeIrregularity = _previewEdgeIrregularity;
				bool flag3 = previewEdgeRelief >= 6f;
				if (previewEdgeRisk == TerrainLeveler.EdgeRiskLevel.High || flag3)
				{
					ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.WarningMessageType, "ValheimFloorPlan: Final warning before build. " + $"Edge risk={previewEdgeRisk}, relief={previewEdgeRelief:F1}m, step={previewEdgeMaxStep:F2}m. " + "Terracing or downhill tears may occur.");
				}
				CancelPreview();
				ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Build confirmed by key {ValheimFloorPlanPlugin.PreviewConfirmKey}. Rotation={previewRotationDeg:F0}°  origin={previewOrigin}  edgeRisk={previewEdgeRisk}  edgeRelief={previewEdgeRelief:F2}  irregularity={previewEdgeIrregularity:F2}  maxEdgeStep={previewEdgeMaxStep:F2}");
				((MonoBehaviour)this).StartCoroutine(LevelThenPlace(previewPlan, previewRotationDeg, previewOrigin));
			}
		}

		private void UpdatePreviewEdgeRisk(Player player, bool previewChanged)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			if (_previewPlan == null)
			{
				return;
			}
			if (previewChanged)
			{
				_previewRiskDirty = true;
			}
			if (!_previewRiskDirty && Time.time < _previewRiskNextSampleAt)
			{
				return;
			}
			TerrainLeveler.EdgeRiskLevel previewEdgeRisk = _previewEdgeRisk;
			_previewEdgeRisk = TerrainLeveler.EvaluateEdgeRisk(_previewPlan, _previewOrigin, _previewRotationDeg, out _previewEdgeRelief, out _previewEdgeIrregularity, out _previewEdgeMaxStep, _previewRiskHotspots);
			_previewRiskBottomCount = BuildPreviewRiskRenderPoints(_previewRiskHotspots, _previewRiskRenderPoints);
			UpdatePreviewRiskMarkers(_previewEdgeRisk, _previewRiskRenderPoints, _previewRiskBottomCount);
			_previewRiskDirty = false;
			_previewRiskNextSampleAt = Time.time + 0.45f;
			bool flag = _previewEdgeRisk != TerrainLeveler.EdgeRiskLevel.Low;
			if ((!(Time.time < _previewRiskHintsEnabledAt) || flag) && (previewChanged || _previewEdgeRisk != previewEdgeRisk || Time.time >= _previewRiskNextHintAt))
			{
				if (_previewEdgeRisk == TerrainLeveler.EdgeRiskLevel.High || _previewEdgeRisk == TerrainLeveler.EdgeRiskLevel.Medium)
				{
					string text = ((_previewEdgeRisk == TerrainLeveler.EdgeRiskLevel.High) ? $"Edge risk HIGH: uneven boundary terrain may cause tears/spikes. Try nudging or rotating before build. step={_previewEdgeMaxStep:F2}m, relief={_previewEdgeRelief:F1}m" : $"Edge risk MEDIUM: some boundary irregularity detected. Small origin/rotation adjustments may improve results. step={_previewEdgeMaxStep:F2}m, relief={_previewEdgeRelief:F1}m");
					ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.WarningMessageType, "ValheimFloorPlan: " + text);
				}
				_previewRiskNextHintAt = Time.time + 2f;
			}
		}

		private int BuildPreviewRiskRenderPoints(List<Vector3> hotspots, List<Vector3> output)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			output.Clear();
			if (_previewPlan == null)
			{
				return 0;
			}
			float num = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainHighPointDelta, 0f, 4f);
			if (hotspots.Count == 0)
			{
				return 0;
			}
			for (int i = 0; i < hotspots.Count; i++)
			{
				output.Add(hotspots[i]);
			}
			int count = output.Count;
			TerrainLeveler.GetLeveledAreaBounds(_previewPlan, _previewOrigin, out var minX, out var maxX, out var minZ, out var maxZ);
			float num2 = _previewRotationDeg * ((float)Math.PI / 180f);
			float num3 = Mathf.Cos(num2);
			float num4 = Mathf.Sin(num2);
			float[] array = new float[4] { minX, maxX, maxX, minX };
			float[] array2 = new float[4] { minZ, minZ, maxZ, maxZ };
			float num5 = float.MinValue;
			float y = _previewOrigin.y;
			RaycastHit val = default(RaycastHit);
			for (int j = 0; j < 4; j++)
			{
				float num6 = array[j] - _previewOrigin.x;
				float num7 = array2[j] - _previewOrigin.z;
				float num8 = _previewOrigin.x + num6 * num3 + num7 * num4;
				float num9 = _previewOrigin.z - num6 * num4 + num7 * num3;
				if (Physics.Raycast(new Vector3(num8, y + 300f, num9), Vector3.down, ref val, 600f, 2048) && ((RaycastHit)(ref val)).point.y > num5)
				{
					num5 = ((RaycastHit)(ref val)).point.y;
				}
			}
			float num10 = ((num5 == float.MinValue) ? y : num5) + 0.3f + num;
			float num11 = maxZ - _previewOrigin.z;
			float[] array3 = new float[3] { 0.25f, 0.5f, 0.75f };
			for (int k = 0; k < array3.Length; k++)
			{
				float num12 = Mathf.Lerp(minX, maxX, array3[k]);
				float num13 = num12 - _previewOrigin.x;
				float num14 = _previewOrigin.x + num13 * num3 + num11 * num4;
				float num15 = _previewOrigin.z - num13 * num4 + num11 * num3;
				output.Add(new Vector3(num14, num10, num15));
			}
			return count;
		}

		private void UpdatePreviewRiskMarkers(TerrainLeveler.EdgeRiskLevel risk, List<Vector3> hotspots, int bottomCount)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: 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)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: 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_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			int num = ((risk != 0) ? Mathf.Min(hotspots.Count, 24) : 0);
			EnsureRiskMarkerCount(num);
			RaycastHit val3 = default(RaycastHit);
			Vector3 val4 = default(Vector3);
			for (int i = 0; i < _previewRiskMarkers.Count; i++)
			{
				LineRenderer val = _previewRiskMarkers[i];
				if (i >= num)
				{
					((Renderer)val).enabled = false;
					continue;
				}
				((Renderer)val).enabled = true;
				val.startColor = ((risk == TerrainLeveler.EdgeRiskLevel.High) ? new Color(1f, 0.22f, 0.12f, 0.95f) : new Color(1f, 0.72f, 0.18f, 0.92f));
				val.endColor = val.startColor;
				Vector3 val2 = hotspots[i];
				float y;
				if (i < bottomCount)
				{
					y = val2.y;
					if (Physics.Raycast(new Vector3(val2.x, val2.y + 300f, val2.z), Vector3.down, ref val3, 600f, 2048))
					{
						y = ((RaycastHit)(ref val3)).point.y;
					}
					y += 0.18f;
				}
				else
				{
					y = val2.y;
				}
				((Vector3)(ref val4))..ctor(val2.x, y, val2.z);
				float num2 = 0.45f;
				val.positionCount = 5;
				val.SetPosition(0, val4 + new Vector3(0f - num2, 0f, 0f));
				val.SetPosition(1, val4 + new Vector3(0f, 0f, num2));
				val.SetPosition(2, val4 + new Vector3(num2, 0f, 0f));
				val.SetPosition(3, val4 + new Vector3(0f, 0f, 0f - num2));
				val.SetPosition(4, val4 + new Vector3(0f - num2, 0f, 0f));
			}
		}

		private void EnsureRiskMarkerCount(int count)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_previewGo == (Object)null))
			{
				while (_previewRiskMarkers.Count < count)
				{
					LineRenderer val = MakeLine(_previewGo, new Color(1f, 0.72f, 0.18f, 0.92f), 0.06f);
					val.loop = false;
					_previewRiskMarkers.Add(val);
				}
			}
		}

		private static Vector3 GetBuildOrigin(Player player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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)
			return GetForwardOffsetOrigin(player, ValheimFloorPlanPlugin.BuildOriginForwardOffset);
		}

		private static Vector3 GetForwardOffsetOrigin(Player player, float forwardOffset)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			forwardOffset = Mathf.Max(0f, forwardOffset);
			if (forwardOffset <= 0f)
			{
				return position;
			}
			Vector3 forward = ((Component)player).transform.forward;
			forward.y = 0f;
			if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
			{
				return position;
			}
			((Vector3)(ref forward)).Normalize();
			return position + forward * forwardOffset;
		}

		private static bool IsPreviewKeyDown(KeyCode key)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			return (int)key != 0 && Input.GetKeyDown(key);
		}

		private static bool IsFineAdjustHeld()
		{
			//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: Invalid comparison between Unknown and I4
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_003a: 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)
			KeyCode previewFineAdjustKey = ValheimFloorPlanPlugin.PreviewFineAdjustKey;
			if ((int)previewFineAdjustKey == 304 || (int)previewFineAdjustKey == 303)
			{
				return Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303);
			}
			return (int)previewFineAdjustKey != 0 && Input.GetKey(previewFineAdjustKey);
		}

		private void UpdatePreviewPosition(Vector3 origin)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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_008e: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			if (_previewPlan != null)
			{
				float previewRaiseDelta = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainHighPointDelta, 0f, 4f);
				TerrainLeveler.GetPadBounds(_previewPlan, origin, out var minX, out var maxX, out var minZ, out var maxZ);
				TerrainLeveler.GetLeveledAreaBounds(_previewPlan, origin, out var minX2, out var maxX2, out var minZ2, out var maxZ2);
				SetWallRingRectangle(_previewPadWalls, origin.y, RotateBoundsCorners(origin, minX, maxX, minZ, maxZ, _previewRotationDeg), previewRaiseDelta);
				SetWallRingRectangle(_previewOuterWalls, origin.y, RotateBoundsCorners(origin, minX2, maxX2, minZ2, maxZ2, _previewRotationDeg), previewRaiseDelta);
				SetOriginMarker(_previewOriginMarker, origin.y, origin);
			}
		}

		private static Vector2[] RotateBoundsCorners(Vector3 origin, float minX, float maxX, float minZ, float maxZ, float rotDeg)
		{
			//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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: 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)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			Vector2[] array = (Vector2[])(object)new Vector2[4]
			{
				new Vector2(minX, minZ),
				new Vector2(maxX, minZ),
				new Vector2(maxX, maxZ),
				new Vector2(minX, maxZ)
			};
			if (Mathf.Approximately(rotDeg % 360f, 0f))
			{
				return array;
			}
			float num = rotDeg * ((float)Math.PI / 180f);
			float num2 = Mathf.Cos(num);
			float num3 = Mathf.Sin(num);
			float x = origin.x;
			float z = origin.z;
			for (int i = 0; i < 4; i++)
			{
				float num4 = array[i].x - x;
				float num5 = array[i].y - z;
				array[i] = new Vector2(x + num4 * num2 + num5 * num3, z - num4 * num3 + num5 * num2);
			}
			return array;
		}

		private static void SetWallRingRectangle(MeshFilter? mf, float referenceY, Vector2[] corners, float previewRaiseDelta)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)mf == (Object)null || (Object)(object)mf.sharedMesh == (Object)null)
			{
				return;
			}
			float num = referenceY + 300f;
			float[] array = new float[4];
			float num2 = float.MaxValue;
			float num3 = float.MinValue;
			RaycastHit val = default(RaycastHit);
			for (int i = 0; i < 4; i++)
			{
				float num4 = referenceY;
				if (Physics.Raycast(new Vector3(corners[i].x, num, corners[i].y), Vector3.down, ref val, 600f, 2048))
				{
					num4 = ((RaycastHit)(ref val)).point.y;
				}
				array[i] = num4;
				if (num4 < num2)
				{
					num2 = num4;
				}
				if (num4 > num3)
				{
					num3 = num4;
				}
			}
			float num5 = num2 + 0.06f;
			float num6 = num3 + 0.3f + Mathf.Max(0f, previewRaiseDelta);
			if (num6 - num5 < 0.75f)
			{
				num6 = num5 + 0.75f;
			}
			Mesh sharedMesh = mf.sharedMesh;
			Vector3[] vertices = sharedMesh.vertices;
			for (int j = 0; j < 4; j++)
			{
				int num7 = (j + 1) % 4;
				int num8 = j * 4;
				vertices[num8] = new Vector3(corners[j].x, num5, corners[j].y);
				vertices[num8 + 1] = new Vector3(corners[num7].x, num5, corners[num7].y);
				vertices[num8 + 2] = new Vector3(corners[num7].x, num6, corners[num7].y);
				vertices[num8 + 3] = new Vector3(corners[j].x, num6, corners[j].y);
			}
			sharedMesh.vertices = vertices;
			sharedMesh.RecalculateBounds();
			sharedMesh.RecalculateNormals();
		}

		private static void SetOriginMarker(LineRenderer? lr, float referenceY, Vector3 origin)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)lr == (Object)null))
			{
				float num = referenceY;
				float num2 = referenceY + 300f;
				RaycastHit val = default(RaycastHit);
				if (Physics.Raycast(new Vector3(origin.x, num2, origin.z), Vector3.down, ref val, 600f, 2048))
				{
					num = ((RaycastHit)(ref val)).point.y;
				}
				Vector3[] array = (Vector3[])(object)new Vector3[7];
				Vector3 val2 = default(Vector3);
				((Vector3)(ref val2))..ctor(origin.x, num + 0.45f, origin.z);
				array[0] = val2 + new Vector3(-0.75f, 0f, 0f);
				array[1] = val2;
				array[2] = val2 + new Vector3(0.75f, 0f, 0f);
				array[3] = val2;
				array[4] = val2 + new Vector3(0f, 0f, 0.75f);
				array[5] = val2;
				array[6] = val2 + new Vector3(0f, 0f, -0.75f);
				lr.SetPositions(array);
			}
		}

		private static void SetLinePositions(LineRenderer? lr, Vector3 from, Vector3 to)
		{
			//IL_0018: 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)
			if (!((Object)(object)lr == (Object)null))
			{
				lr.positionCount = 2;
				lr.SetPosition(0, from);
				lr.SetPosition(1, to);
			}
		}

		public void Undo()
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				ValheimFloorPlanPlugin.Log.LogWarning((object)"[FloorPlanBuilder] No local player for Undo.");
				return;
			}
			if (_undoConfirmationExpireAt > Time.time)
			{
				_undoConfirmationExpireAt = 0f;
				if (_undoCountdownCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_undoCountdownCoroutine);
				}
				_undoCountdownCoroutine = null;
				PerformUndo(localPlayer);
				return;
			}
			CountUndoStats(localPlayer, out var pieceCount, out var terrainChunkCount);
			if (pieceCount == 0 && terrainChunkCount == 0)
			{
				ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.ProgressMessageType, "ValheimFloorPlan: Nothing to undo.");
				return;
			}
			_undoConfirmationPieceCount = pieceCount;
			_undoConfirmationTerrainChunks = terrainChunkCount;
			_undoConfirmationExpireAt = Time.time + 5f;
			if (_undoCountdownCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_undoCountdownCoroutine);
			}
			_undoCountdownCoroutine = ((MonoBehaviour)this).StartCoroutine(UndoCountdownCoroutine());
			ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.ProgressMessageType, BuildUndoConfirmationMessage(5));
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Undo confirmation pending: {pieceCount} pieces, {terrainChunkCount} terrain chunks.");
		}

		private string BuildUndoConfirmationMessage(int secondsLeft)
		{
			string text = $"ValheimFloorPlan: Confirm Undo? Will remove {_undoConfirmationPieceCount} piece(s)";
			if (_undoConfirmationTerrainChunks > 0)
			{
				text += $" and restore {_undoConfirmationTerrainChunks} terrain chunk(s)";
			}
			return text + $". Press Undo again ({secondsLeft}s remaining) to confirm.";
		}

		private void CountUndoStats(Player player, out int pieceCount, out int terrainChunkCount)
		{
			//IL_000a: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			pieceCount = 0;
			Vector3 position = ((Component)player).transform.position;
			ZNetView[] array = Object.FindObjectsByType<ZNetView>((FindObjectsSortMode)0);
			foreach (ZNetView val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					ZDO zDO = val.GetZDO();
					if (zDO != null && !(zDO.GetString("vfp_build", "") != "1") && !(Vector3.Distance(((Component)val).transform.position, position) > 75f))
					{
						pieceCount++;
					}
				}
			}
			terrainChunkCount = TerrainSnapshot.GetSnapshotChunkCount();
		}

		private void PerformUndo(Player player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			int num = 0;
			ZNetView[] array = Object.FindObjectsByType<ZNetView>((FindObjectsSortMode)0);
			foreach (ZNetView val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					ZDO zDO = val.GetZDO();
					if (zDO != null && !(zDO.GetString("vfp_build", "") != "1") && !(Vector3.Distance(((Component)val).transform.position, position) > 75f))
					{
						ZNetScene.instance.Destroy(((Component)val).gameObject);
						num++;
					}
				}
			}
			_lastPlaced.Clear();
			bool hasSnapshot = TerrainSnapshot.HasSnapshot;
			int snapshotChunkCount = TerrainSnapshot.GetSnapshotChunkCount();
			TerrainSnapshot.Restore();
			if (hasSnapshot && snapshotChunkCount > 0)
			{
				if (_undoRefreshCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_undoRefreshCoroutine);
				}
				_undoRefreshCoroutine = ((MonoBehaviour)this).StartCoroutine(PostUndoTerrainRefresh(position, snapshotChunkCount));
			}
			if (!hasSnapshot)
			{
				ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.WarningMessageType, "ValheimFloorPlan: No terrain snapshot in this session. Undo removed pieces only.");
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Undo: removed {num} VFP pieces within {75f}m, restored {snapshotChunkCount} terrain chunks.");
			((Character)player).Message((MessageType)2, $"ValheimFloorPlan: Undone ({num} pieces removed, {snapshotChunkCount} terrain chunks restored).", 0, (Sprite)null);
		}

		private IEnumerator PostUndoTerrainRefresh(Vector3 center, int restoredChunks)
		{
			//IL_000e: 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)
			float elapsed = 0f;
			int passes = 0;
			int touched = 0;
			for (; elapsed < 2.5f; elapsed += 0.25f)
			{
				Heightmap[] hmaps = Object.FindObjectsOfType<Heightmap>() ?? Array.Empty<Heightmap>();
				int passTouched = 0;
				Heightmap[] array = hmaps;
				foreach (Heightmap hmap in array)
				{
					if (!((Object)(object)hmap == (Object)null) && !(Vector3.Distance(((Component)hmap).transform.position, center) > 120f))
					{
						hmap.Poke(false);
						passTouched++;
					}
				}
				passes++;
				touched = passTouched;
				yield return (object)new WaitForSeconds(0.25f);
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Post-undo refresh complete: {passes} passes, {touched} nearby heightmaps touched, restoredChunks={restoredChunks}.");
			_undoRefreshCoroutine = null;
		}

		private IEnumerator UndoCountdownCoroutine()
		{
			float nextUpdateAt = Time.time + 1f;
			while (_undoConfirmationExpireAt > Time.time)
			{
				if (Time.time >= nextUpdateAt)
				{
					float remainingSeconds = _undoConfirmationExpireAt - Time.time;
					ValheimFloorPlanPlugin.ShowWrappedMessage(text: BuildUndoConfirmationMessage((int)Mathf.Ceil(remainingSeconds)), messageType: ValheimFloorPlanPlugin.ProgressMessageType);
					nextUpdateAt = Time.time + 1f;
				}
				yield return null;
			}
			_undoCountdownCoroutine = null;
			_undoConfirmationExpireAt = 0f;
		}

		public void BuildFromFile(string path)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			FloorPlan floorPlan;
			try
			{
				floorPlan = FloorPlan.Load(path);
			}
			catch (Exception ex)
			{
				ValheimFloorPlanPlugin.Log.LogError((object)("Failed to load floor plan: " + ex.Message));
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				ValheimFloorPlanPlugin.Log.LogError((object)"No local player found.");
				return;
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"Building floor plan: {floorPlan.Pieces.Count} pieces from {path}");
			((MonoBehaviour)this).StartCoroutine(LevelThenPlace(floorPlan, 0f, GetBuildOrigin(localPlayer)));
		}

		private IEnumerator LevelThenPlace(FloorPlan plan, float rotationDeg, Vector3 origin)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			Player player = Player.m_localPlayer;
			if ((Object)(object)player == (Object)null)
			{
				ValheimFloorPlanPlugin.Log.LogError((object)"No local player found.");
				yield break;
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"Build origin: {origin}  rotation={rotationDeg:F0}°");
			_lastPlaced.Clear();
			TerrainLeveler.GetSnapshotBounds(plan, origin, out var sMinX, out var sMaxX, out var sMinZ, out var sMaxZ, rotationDeg);
			TerrainSnapshot.Capture(sMinX, sMaxX, sMinZ, sMaxZ, origin.y);
			if (!TerrainSnapshot.HasSnapshot)
			{
				ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.WarningMessageType, "ValheimFloorPlan: Warning - terrain snapshot capture failed. Undo may remove pieces without restoring terrain.");
			}
			((Character)player).Message((MessageType)2, "Clearing rocks...", 0, (Sprite)null);
			ClearRocksInPad(plan, origin, rotationDeg);
			((Character)player).Message((MessageType)2, "Leveling terrain...", 0, (Sprite)null);
			yield return ((MonoBehaviour)this).StartCoroutine(TerrainLeveler.LevelForPlan(plan, origin, rotationDeg));
			((Character)player).Message((MessageType)2, "Waiting for terrain physics...", 0, (Sprite)null);
			ShowBuildProgress("Waiting for terrain physics...");
			TerrainLeveler.GetPadBounds(plan, origin, out var padMinX, out var padMaxX, out var padMinZ, out var padMaxZ, rotationDeg);
			yield return ((MonoBehaviour)this).StartCoroutine(WaitForTerrainPhysics(padMinX, padMaxX, padMinZ, padMaxZ, TerrainLeveler.TargetLevelY));
			((Character)player).Message((MessageType)2, "Placing floor plan pieces...", 0, (Sprite)null);
			ShowBuildProgress($"Placing pieces... 0/{plan.Pieces.Count}");
			yield return ((MonoBehaviour)this).StartCoroutine(PlacePieces(plan, origin, rotationDeg));
			ShowBuildProgress("Final checks...");
			yield return ((MonoBehaviour)this).StartCoroutine(PostBuildSpikeGuard(plan, origin, rotationDeg));
		}

		private void ClearRocksInPad(FloorPlan plan, Vector3 origin, float rotationDeg = 0f)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_050e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0513: Unknown result type (might be due to invalid IL or missing references)
			//IL_0515: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_051f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0698: Unknown result type (might be due to invalid IL or missing references)
			//IL_069a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0529: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_055b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0533: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0602: Unknown result type (might be due to invalid IL or missing references)
			//IL_0634: Unknown result type (might be due to invalid IL or missing references)
			//IL_060c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0409: Unknown result type (might be due to invalid IL or missing references)
			//IL_040e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0412: Unknown result type (might be due to invalid IL or missing references)
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0423: Unknown result type (might be due to invalid IL or missing references)
			//IL_0427: Unknown result type (might be due to invalid IL or missing references)
			//IL_0484: Unknown result type (might be due to invalid IL or missing references)
			TerrainLeveler.GetLeveledAreaBounds(plan, origin, out var minX, out var maxX, out var minZ, out var maxZ, rotationDeg);
			int num = 0;
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor((minX + maxX) * 0.5f, origin.y, (minZ + maxZ) * 0.5f);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor((maxX - minX) * 0.5f, 100f, (maxZ - minZ) * 0.5f);
			Bounds val3 = default(Bounds);
			((Bounds)(ref val3))..ctor(val, new Vector3(maxX - minX, 400f, maxZ - minZ));
			Collider[] array = Physics.OverlapBox(val, val2, Quaternion.identity, -1, (QueryTriggerInteraction)2);
			foreach (Collider val4 in array)
			{
				if ((Object)(object)val4 == (Object)null)
				{
					continue;
				}
				MineRock5 componentInParent = ((Component)val4).GetComponentInParent<MineRock5>();
				if ((Object)(object)componentInParent != (Object)null && hashSet.Add(((Component)componentInParent).gameObject))
				{
					ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing MineRock5 '{((Object)componentInParent).name}' at {((Component)componentInParent).transform.position}");
					ZNetScene.instance.Destroy(((Component)componentInParent).gameObject);
					num++;
					continue;
				}
				MineRock componentInParent2 = ((Component)val4).GetComponentInParent<MineRock>();
				if ((Object)(object)componentInParent2 != (Object)null && hashSet.Add(((Component)componentInParent2).gameObject))
				{
					ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing MineRock '{((Object)componentInParent2).name}' at {((Component)componentInParent2).transform.position}");
					ZNetScene.instance.Destroy(((Component)componentInParent2).gameObject);
					num++;
					continue;
				}
				Destructible componentInParent3 = ((Component)val4).GetComponentInParent<Destructible>();
				if ((Object)(object)componentInParent3 != (Object)null && hashSet.Add(((Component)componentInParent3).gameObject) && IsRockLikeName(((Object)componentInParent3).name) && (Object)(object)((Component)componentInParent3).GetComponent<Piece>() == (Object)null)
				{
					ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing rock-like Destructible '{((Object)componentInParent3).name}' at {((Component)componentInParent3).transform.position}");
					ZNetScene.instance.Destroy(((Component)componentInParent3).gameObject);
					num++;
				}
			}
			Renderer[] array2 = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0);
			foreach (Renderer val5 in array2)
			{
				if ((Object)(object)val5 == (Object)null || !val5.enabled)
				{
					continue;
				}
				Bounds bounds = val5.bounds;
				if (!((Bounds)(ref bounds)).Intersects(val3))
				{
					continue;
				}
				GameObject val6 = (((Object)(object)((Component)val5).transform.root != (Object)null) ? ((Component)((Component)val5).transform.root).gameObject : ((Component)val5).gameObject);
				if (hashSet.Add(val6) && !((Object)(object)val6.GetComponentInChildren<Piece>() != (Object)null))
				{
					string text = ((Object)val6).name.ToLowerInvariant();
					bool flag = HasAnyComponentNamed(val6, "MineRock", "MineRock5", "Destructible", "StaticPhysics", "TerrainModifier", "LocationProxy");
					bool flag2 = text.Contains("rock") || text.Contains("stone") || text.Contains("cliff") || text.Contains("spike") || text.Contains("obelisk") || text.Contains("monolith");
					bool flag3 = text.Contains("pickable") || text.Contains("flint") || text.Contains("branch") || text.Contains("mushroom") || text.Contains("thistle") || text.Contains("berry");
					bounds = val5.bounds;
					float y = ((Bounds)(ref bounds)).size.y;
					bounds = val5.bounds;
					float x = ((Bounds)(ref bounds)).size.x;
					bounds = val5.bounds;
					float num2 = Mathf.Max(x, ((Bounds)(ref bounds)).size.z);
					bool flag4 = y >= 2f && num2 >= 0.8f;
					if ((flag2 || (flag && flag4)) && !flag3)
					{
						ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing renderer blocker '{((Object)val6).name}' at {val6.transform.position}");
						ZNetScene.instance.Destroy(val6);
						num++;
					}
				}
			}
			MineRock5[] array3 = Object.FindObjectsByType<MineRock5>((FindObjectsSortMode)0);
			foreach (MineRock5 val7 in array3)
			{
				if (!((Object)(object)val7 == (Object)null) && hashSet.Add(((Component)val7).gameObject))
				{
					Vector3 position = ((Component)val7).transform.position;
					if (position.x >= minX && position.x <= maxX && position.z >= minZ && position.z <= maxZ)
					{
						ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing MineRock5 '{((Object)val7).name}' at {position}");
						ZNetScene.instance.Destroy(((Component)val7).gameObject);
						num++;
					}
				}
			}
			MineRock[] array4 = Object.FindObjectsByType<MineRock>((FindObjectsSortMode)0);
			foreach (MineRock val8 in array4)
			{
				if (!((Object)(object)val8 == (Object)null) && hashSet.Add(((Component)val8).gameObject))
				{
					Vector3 position2 = ((Component)val8).transform.position;
					if (position2.x >= minX && position2.x <= maxX && position2.z >= minZ && position2.z <= maxZ)
					{
						ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Removing MineRock '{((Object)val8).name}' at {position2}");
						ZNetScene.instance.Destroy(((Component)val8).gameObject);
						num++;
					}
				}
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] ClearRocksInPad: {num} rock(s) removed.");
			if (num == 0)
			{
				LogAreaBlockers(val, val2);
			}
		}

		private IEnumerator PostBuildSpikeGuard(FloorPlan plan, Vector3 origin, float rotationDeg = 0f)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			TerrainLeveler.GetLeveledAreaBounds(plan, origin, out var minX, out var maxX, out var minZ, out var maxZ, rotationDeg);
			int totalRemoved = 0;
			for (int i = 0; i < 4; i++)
			{
				totalRemoved += RemoveTallBlockersAboveTerrain(minX, maxX, minZ, maxZ, TerrainLeveler.TargetLevelY);
				if (i < 3)
				{
					yield return (object)new WaitForSeconds(0.75f);
				}
			}
			if (totalRemoved > 0)
			{
				ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] PostBuildSpikeGuard removed {totalRemoved} blocker(s).");
			}
			else
			{
				ValheimFloorPlanPlugin.Log.LogInfo((object)"[FloorPlanBuilder] PostBuildSpikeGuard found no blockers.");
			}
		}

		private int RemoveTallBlockersAboveTerrain(float minX, float maxX, float minZ, float maxZ, float referenceY)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: 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)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			int num2 = Mathf.CeilToInt((maxX - minX) / 0.5f);
			int num3 = Mathf.CeilToInt((maxZ - minZ) / 0.5f);
			float num4 = referenceY + 300f;
			RaycastHit val = default(RaycastHit);
			for (int i = 0; i <= num2; i++)
			{
				float num5 = ((i == num2) ? maxX : (minX + (float)i * 0.5f));
				for (int j = 0; j <= num3; j++)
				{
					float num6 = ((j == num3) ? maxZ : (minZ + (float)j * 0.5f));
					if (!Physics.Raycast(new Vector3(num5, num4, num6), Vector3.down, ref val, 600f, 2048))
					{
						continue;
					}
					RaycastHit[] array = Physics.RaycastAll(new Vector3(num5, num4, num6), Vector3.down, 600f);
					if (array == null || array.Length == 0)
					{
						continue;
					}
					Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref b)).point.y.CompareTo(((RaycastHit)(ref a)).point.y));
					RaycastHit[] array2 = array;
					for (int k = 0; k < array2.Length; k++)
					{
						RaycastHit val2 = array2[k];
						if ((Object)(object)((RaycastHit)(ref val2)).collider == (Object)null)
						{
							continue;
						}
						GameObject val3 = (((Object)(object)((Component)((RaycastHit)(ref val2)).collider).transform.root != (Object)null) ? ((Component)((Component)((RaycastHit)(ref val2)).collider).transform.root).gameObject : ((Component)((RaycastHit)(ref val2)).collider).gameObject);
						if ((Object)(object)val3 == (Object)null || (Object)(object)val3.GetComponentInChildren<Piece>() != (Object)null)
						{
							continue;
						}
						float num7 = ((RaycastHit)(ref val2)).point.y - ((RaycastHit)(ref val)).point.y;
						if (!(num7 < 0.8f))
						{
							string text = ((Object)val3).name.ToLowerInvariant();
							bool flag = text.Contains("pickable") || text.Contains("flint") || text.Contains("branch") || text.Contains("mushroom") || text.Contains("thistle") || text.Contains("berry");
							bool flag2 = text.Contains("rock") || text.Contains("stone") || text.Contains("cliff") || text.Contains("spike") || text.Contains("obelisk") || text.Contains("monolith");
							bool flag3 = HasAnyComponentNamed(val3, "MineRock", "MineRock5", "Destructible", "StaticPhysics", "TerrainModifier", "LocationProxy");
							if ((flag2 || flag3) && !flag && hashSet.Add(val3))
							{
								ValheimFloorPlanPlugin.Log.LogWarning((object)$"[FloorPlanBuilder] PostBuildSpikeGuard removing '{((Object)val3).name}' protrusion={num7:F2}m at {val3.transform.position}");
							}
						}
						break;
					}
				}
			}
			foreach (GameObject item in hashSet)
			{
				ZNetScene.instance.Destroy(item);
				num++;
			}
			return num;
		}

		private static bool IsRockLikeName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			string text = name.ToLowerInvariant();
			return text.Contains("rock") || text.Contains("stone") || text.Contains("boulder") || text.Contains("cliff");
		}

		private static bool HasAnyComponentNamed(GameObject root, params string[] names)
		{
			HashSet<string> hashSet = new HashSet<string>(names);
			Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && hashSet.Contains(((object)val).GetType().Name))
				{
					return true;
				}
			}
			return false;
		}

		private static void LogAreaBlockers(Vector3 center, Vector3 halfExtents)
		{
			//IL_000f: 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_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0461: Unknown result type (might be due to invalid IL or missing references)
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			List<string> list = new List<string>();
			Bounds val = default(Bounds);
			((Bounds)(ref val))..ctor(center, new Vector3(halfExtents.x * 2f, halfExtents.y * 2f, halfExtents.z * 2f));
			Collider[] array = Physics.OverlapBox(center, halfExtents, Quaternion.identity, -1, (QueryTriggerInteraction)2);
			foreach (Collider val2 in array)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				GameObject val3 = (((Object)(object)((Component)val2).transform.root != (Object)null) ? ((Component)((Component)val2).transform.root).gameObject : ((Component)val2).gameObject);
				if (!hashSet.Add(val3))
				{
					continue;
				}
				bool flag = false;
				string name = ((Object)val3).name;
				string text = name.ToLowerInvariant();
				if (text.Contains("rock") || text.Contains("stone") || text.Contains("cliff") || text.Contains("location"))
				{
					flag = true;
				}
				Component[] componentsInChildren = val3.GetComponentsInChildren<Component>(true);
				HashSet<string> hashSet2 = new HashSet<string>();
				Component[] array2 = componentsInChildren;
				foreach (Component val4 in array2)
				{
					if ((Object)(object)val4 == (Object)null)
					{
						continue;
					}
					string name2 = ((object)val4).GetType().Name;
					switch (name2)
					{
					default:
						if (!(name2 == "LocationProxy"))
						{
							continue;
						}
						break;
					case "MineRock":
					case "MineRock5":
					case "Destructible":
					case "StaticPhysics":
					case "TerrainModifier":
						break;
					}
					hashSet2.Add(name2);
					flag = true;
				}
				if (flag)
				{
					string text2 = ((hashSet2.Count > 0) ? string.Join(",", hashSet2) : "none");
					list.Add($"{name} @ {val3.transform.position} layer={val3.layer} tags={text2}");
				}
			}
			Renderer[] array3 = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0);
			foreach (Renderer val5 in array3)
			{
				if ((Object)(object)val5 == (Object)null || !val5.enabled)
				{
					continue;
				}
				Bounds bounds = val5.bounds;
				if (!((Bounds)(ref bounds)).Intersects(val))
				{
					continue;
				}
				GameObject val6 = (((Object)(object)((Component)val5).transform.root != (Object)null) ? ((Component)((Component)val5).transform.root).gameObject : ((Component)val5).gameObject);
				if (!hashSet.Add(val6))
				{
					continue;
				}
				string text3 = ((Object)val6).name.ToLowerInvariant();
				bool flag2 = text3.Contains("rock") || text3.Contains("stone") || text3.Contains("cliff") || text3.Contains("spike") || text3.Contains("obelisk") || text3.Contains("monolith");
				Component[] componentsInChildren2 = val6.GetComponentsInChildren<Component>(true);
				HashSet<string> hashSet3 = new HashSet<string>();
				Component[] array4 = componentsInChildren2;
				foreach (Component val7 in array4)
				{
					if ((Object)(object)val7 == (Object)null)
					{
						continue;
					}
					string name3 = ((object)val7).GetType().Name;
					switch (name3)
					{
					default:
						if (!(name3 == "LocationProxy"))
						{
							continue;
						}
						break;
					case "MineRock":
					case "MineRock5":
					case "Destructible":
					case "StaticPhysics":
					case "TerrainModifier":
						break;
					}
					hashSet3.Add(name3);
					flag2 = true;
				}
				if (flag2)
				{
					string text4 = ((hashSet3.Count > 0) ? string.Join(",", hashSet3) : "none");
					list.Add($"{((Object)val6).name} @ {val6.transform.position} layer={val6.layer} tags={text4} (renderer)");
				}
			}
			if (list.Count == 0)
			{
				ValheimFloorPlanPlugin.Log.LogInfo((object)"[FloorPlanBuilder] ClearRocksInPad diagnostics: no rock/location-like roots found in leveled area.");
				return;
			}
			int num = Mathf.Min(15, list.Count);
			ValheimFloorPlanPlugin.Log.LogWarning((object)$"[FloorPlanBuilder] ClearRocksInPad diagnostics: {list.Count} candidate blocker root(s) in leveled area. Showing {num}:");
			for (int m = 0; m < num; m++)
			{
				ValheimFloorPlanPlugin.Log.LogWarning((object)("[FloorPlanBuilder]   " + list[m]));
			}
		}

		private IEnumerator WaitForTerrainPhysics(float minX, float maxX, float minZ, float maxZ, float targetY)
		{
			float midX = (minX + maxX) * 0.5f;
			float midZ = (minZ + maxZ) * 0.5f;
			float rayY = targetY + 300f;
			Vector3[] probes = (Vector3[])(object)new Vector3[9]
			{
				new Vector3(minX, rayY, minZ),
				new Vector3(midX, rayY, minZ),
				new Vector3(maxX, rayY, minZ),
				new Vector3(minX, rayY, midZ),
				new Vector3(midX, rayY, midZ),
				new Vector3(maxX, rayY, midZ),
				new Vector3(minX, rayY, maxZ),
				new Vector3(midX, rayY, maxZ),
				new Vector3(maxX, rayY, maxZ)
			};
			float elapsed = 0f;
			bool firstLog = true;
			float nextProgressAt = 2f;
			RaycastHit hit = default(RaycastHit);
			for (; elapsed < 30f; elapsed += 0.25f)
			{
				bool allReady = true;
				float worstDelta = 0f;
				StringBuilder sb = (firstLog ? new StringBuilder($"[FloorPlanBuilder] Physics collider probes (targetY={targetY:F2}): ") : null);
				Vector3[] array = probes;
				for (int i = 0; i < array.Length; i++)
				{
					Vector3 p = array[i];
					if (Physics.Raycast(p, Vector3.down, ref hit, 600f, 2048))
					{
						float delta = Mathf.Abs(((RaycastHit)(ref hit)).point.y - targetY);
						if (delta > worstDelta)
						{
							worstDelta = delta;
						}
						if (delta > 0.3f)
						{
							allReady = false;
						}
						sb?.Append($"({p.x:F0},{p.z:F0})={((RaycastHit)(ref hit)).point.y:F2}  ");
					}
					else
					{
						allReady = false;
						sb?.Append($"({p.x:F0},{p.z:F0})=MISS  ");
					}
					hit = default(RaycastHit);
				}
				if (firstLog)
				{
					ValheimFloorPlanPlugin.Log.LogInfo((object)sb.ToString());
					firstLog = false;
				}
				if (allReady)
				{
					ValheimFloorPlanPlugin.Log.LogInfo((object)$"[FloorPlanBuilder] Physics collider ready after {elapsed:F1}s (worst delta {worstDelta:F2}m).");
					ShowBuildProgress("Waiting for terrain physics... done");
					yield break;
				}
				if (elapsed >= nextProgressAt)
				{
					ShowBuildProgress($"Waiting for terrain physics... {elapsed:F0}s");
					nextProgressAt += 2f;
				}
				yield return (object)new WaitForSeconds(0.25f);
			}
			ValheimFloorPlanPlugin.Log.LogWarning((object)$"[FloorPlanBuilder] Physics collider did not settle within {30f:F0}s — placing anyway.");
			ShowBuildProgress("Waiting for terrain physics... timeout, placing anyway");
		}

		private IEnumerator PlacePieces(FloorPlan plan, Vector3 origin, float rotationDeg = 0f)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Player player = Player.m_localPlayer;
			if ((Object)(object)player == (Object)null)
			{
				ValheimFloorPlanPlugin.Log.LogError((object)"No local player found during placement.");
				yield break;
			}
			int placed = 0;
			int skipped = 0;
			Vector3 firstPos = Vector3.zero;
			int totalPieces = plan.Pieces.Count;
			int processed = 0;
			int nextProgressPct = 10;
			int configuredExternalWallHeight = Mathf.Clamp(ValheimFloorPlanPlugin.ExternalWallHeight, 1, 4);
			bool useWoodStructure = ValheimFloorPlanPlugin.WallPillarMaterial == ValheimFloorPlanPlugin.StructuralMaterial.Wood;
			GetPlanPieceBounds(plan, out var minCol, out var maxColExclusive, out var minRow, out var maxRowExclusive);
			float cosR = Mathf.Cos(rotationDeg * ((float)Math.PI / 180f));
			float sinR = Mathf.Sin(rotationDeg * ((float)Math.PI / 180f));
			RaycastHit hit = default(RaycastHit);
			foreach (FloorPlanPiece piece in plan.Pieces)
			{
				PieceDef def = PieceMap.GetDef(piece.Type);
				if (def == null)
				{
					ValheimFloorPlanPlugin.Log.LogWarning((object)("Unknown piece type '" + piece.Type + "' — skipped."));
					skipped++;
					continue;
				}
				int effectivePieceRotation = piece.Rotation;
				if (useWoodStructure && piece.Type == "Wall" && piece.WallFace == WallFaceMode.Inner)
				{
					effectivePieceRotation = (effectivePieceRotation + 180) % 360;
				}
				string prefabName = ResolvePrefabName(piece.Type, def.Prefab, useWoodStructure);
				ZNetScene instance = ZNetScene.instance;
				GameObject prefab = ((instance != null) ? instance.GetPrefab(prefabName) : null);
				if ((Object)(object)prefab == (Object)null)
				{
					ValheimFloorPlanPlugin.Log.LogWarning((object)("Prefab '" + prefabName + "' not found in ZNetScene — skipped."));
					skipped++;
					continue;
				}
				int effW = def.EffW(effectivePieceRotation);
				int effH = def.EffH(effectivePieceRotation);
				float dx = ((float)piece.Col + (float)effW * 0.5f) * 1f;
				float dz = ((float)piece.Row + (float)effH * 0.5f) * 1f;
				float x = origin.x + dx * cosR + dz * sinR;
				float z = origin.z - dx * sinR + dz * cosR;
				float terrainY = TerrainLeveler.TargetLevelY;
				if (Physics.Raycast(new Vector3(x, TerrainLeveler.TargetLevelY + 300f, z), Vector3.down, ref hit, 600f, 2048))
				{
					terrainY = ((RaycastHit)(ref hit)).point.y;
				}
				float y = terrainY + def.YOffset;
				Vector3 pos = new Vector3(x, y, z);
				bool isExternal = IsOnPlanOuterPerimeter(piece.Col, piece.Row, effW, effH, minCol, maxColExclusive, minRow, maxRowExclusive);
				int stackCount = ((!(IsExternalWallOrPillarType(piece.Type) && isExternal)) ? 1 : configuredExternalWallHeight);
				float stackStepY = GetStackStepY(piece.Type);
				Quaternion rot = Quaternion.Euler(0f, (float)effectivePieceRotation + rotationDeg, 0f);
				Vector3 materialOffset = Vector3.zero;
				if (useWoodStructure && (piece.Type == "Wall" || piece.Type == "Pillar") && isExternal)
				{
					materialOffset = GetWoodPerimeterOffset(piece.Type, piece.Col, piece.Row, effW, effH, minCol, maxColExclusive, minRow, maxRowExclusive, rotationDeg);
				}
				for (int i = 0; i < stackCount; i++)
				{
					Vector3 stackedPos = new Vector3(pos.x, pos.y + stackStepY * (float)i, pos.z) + materialOffset;
					if (placed == 0)
					{
						firstPos = stackedPos;
						ValheimFloorPlanPlugin.Log.LogInfo((object)$"First piece: type={piece.Type} prefab={prefabName} pos={stackedPos}");
					}
					GameObject go = Object.Instantiate<GameObject>(prefab, stackedPos, rot);
					_lastPlaced.Add(go);
					ZNetView zNetView = go.GetComponent<ZNetView>();
					if ((Object)(object)zNetView != (Object)null)
					{
						ZDO zdo = zNetView.GetZDO();
						if (zdo != null)
						{
							zdo.SetOwner(ZDOMan.GetSessionID());
							zdo.Set("vfp_build", "1");
						}
					}
					Piece pieceComp = go.GetComponent<Piece>();
					if (pieceComp != null)
					{
						pieceComp.SetCreator(player.GetPlayerID());
					}
					placed++;
					if (placed % 10 == 0)
					{
						yield return (object)new WaitForSeconds(0.05f);
					}
				}
				processed++;
				if (totalPieces > 0)
				{
					int pct = Mathf.FloorToInt((float)processed * 100f / (float)totalPieces);
					if (pct >= nextProgressPct)
					{
						ShowBuildProgress($"Placing pieces... {processed}/{totalPieces}");
						nextProgressPct += 10;
					}
				}
				hit = default(RaycastHit);
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"Floor plan complete: {placed} placed, {skipped} skipped.");
			ValheimFloorPlanPlugin.Log.LogInfo((object)$"First piece was at: {firstPos}  — player was at: {origin}");
			ShowBuildProgress($"Placing pieces... done ({placed}/{totalPieces})");
			((Character)player).Message((MessageType)2, $"Floor plan built: {placed} pieces placed, {skipped} skipped. Check log for position info.", 0, (Sprite)null);
		}

		private static void ShowBuildProgress(string message)
		{
			ValheimFloorPlanPlugin.ShowProgressMessage(message);
		}

		private static bool IsExternalWallOrPillarType(string type)
		{
			return type == "Wall" || type == "Pillar";
		}

		private static string ResolvePrefabName(string type, string defaultPrefab, bool useWoodStructure)
		{
			if (!useWoodStructure)
			{
				return defaultPrefab;
			}
			if (type == "Wall")
			{
				return "wood_wall_half";
			}
			if (type == "Pillar")
			{
				return "wood_pole_log";
			}
			return defaultPrefab;
		}

		private static float GetStackStepY(string type)
		{
			if (type == "Wall")
			{
				return 1f;
			}
			if (type == "Pillar")
			{
				return 2f;
			}
			return 0f;
		}

		private static bool IsOnPlanOuterPerimeter(int col, int row, int effW, int effH, int minCol, int maxColExclusive, int minRow, int maxRowExclusive)
		{
			return col <= minCol || row <= minRow || col + effW >= maxColExclusive || row + effH >= maxRowExclusive;
		}

		private static Vector3 GetWoodPerimeterOffset(string pieceType, int col, int row, int effW, int effH, int minCol, int maxColExclusive, int minRow, int maxRowExclusive, float planRotationDeg)
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: 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_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: 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)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			float num = ((pieceType == "Pillar") ? 0.15f : 0.35f);
			float num2 = 0f;
			float num3 = 0f;
			if (col <= minCol)
			{
				num2 -= 1f;
			}
			if (col + effW >= maxColExclusive)
			{
				num2 += 1f;
			}
			if (row <= minRow)
			{
				num3 -= 1f;
			}
			if (row + effH >= maxRowExclusive)
			{
				num3 += 1f;
			}
			if (pieceType == "Wall")
			{
				if (effH == 1)
				{
					num2 = 0f;
				}
				else
				{
					num3 = 0f;
				}
			}
			if (num2 == 0f && num3 == 0f)
			{
				return Vector3.zero;
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(num2 * num, 0f, num3 * num);
			return Quaternion.Euler(0f, planRotationDeg, 0f) * val;
		}

		private static void GetPlanPieceBounds(FloorPlan plan, out int minCol, out int maxColExclusive, out int minRow, out int maxRowExclusive)
		{
			minCol = int.MaxValue;
			maxColExclusive = int.MinValue;
			minRow = int.MaxValue;
			maxRowExclusive = int.MinValue;
			foreach (FloorPlanPiece piece in plan.Pieces)
			{
				int num = 1;
				int num2 = 1;
				PieceDef def = PieceMap.GetDef(piece.Type);
				if (def != null)
				{
					num = def.EffW(piece.Rotation);
					num2 = def.EffH(piece.Rotation);
				}
				if (piece.Col < minCol)
				{
					minCol = piece.Col;
				}
				if (piece.Col + num > maxColExclusive)
				{
					maxColExclusive = piece.Col + num;
				}
				if (piece.Row < minRow)
				{
					minRow = piece.Row;
				}
				if (piece.Row + num2 > maxRowExclusive)
				{
					maxRowExclusive = piece.Row + num2;
				}
			}
			if (minCol == int.MaxValue)
			{
				minCol = 0;
				minRow = 0;
				maxColExclusive = plan.Cols;
				maxRowExclusive = plan.Rows;
			}
		}
	}
	public sealed class PieceDef
	{
		public readonly string Prefab;

		public readonly int BaseW;

		public readonly int BaseH;

		public readonly float YOffset;

		public PieceDef(string prefab, int baseW, int baseH, float yOffset = 0f)
		{
			Prefab = prefab;
			BaseW = baseW;
			BaseH = baseH;
			YOffset = yOffset;
		}

		public int EffW(int rotation)
		{
			return (rotation == 90 || rotation == 270) ? BaseH : BaseW;
		}

		public int EffH(int rotation)
		{
			return (rotation == 90 || rotation == 270) ? BaseW : BaseH;
		}
	}
	public static class PieceMap
	{
		public const float CELL_SIZE = 1f;

		private static readonly Dictionary<string, PieceDef> Map = new Dictionary<string, PieceDef>
		{
			{
				"Floor2x2",
				new PieceDef("wood_floor", 2, 2)
			},
			{
				"Floor1x1",
				new PieceDef("wood_floor_1x1", 1, 1)
			},
			{
				"Wall",
				new PieceDef("stone_wall_2x1", 2, 1, 0.5f)
			},
			{
				"Doorway",
				new PieceDef("wood_door", 2, 1, 1f)
			},
			{
				"Pillar",
				new PieceDef("stone_pillar", 1, 1, 1f)
			},
			{
				"Hearth",
				new PieceDef("hearth", 3, 2)
			}
		};

		public static PieceDef? GetDef(string vfpType)
		{
			PieceDef value;
			return Map.TryGetValue(vfpType, out value) ? value : null;
		}

		public static string? GetPrefab(string vfpType)
		{
			return GetDef(vfpType)?.Prefab;
		}
	}
	public static class TerrainLeveler
	{
		public enum EdgeRiskLevel
		{
			Low,
			Medium,
			High
		}

		private struct EdgeRiskHotspot
		{
			public Vector3 Position;

			public float Score;

			public EdgeRiskHotspot(Vector3 position, float score)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0003: Unknown result type (might be due to invalid IL or missing references)
				Position = position;
				Score = score;
			}
		}

		private const float PRE_SAMPLE_STEP = 0.5f;

		private const float LEVEL_SAMPLE_STEP = 0.5f;

		private const float EDGE_LEVEL_SAMPLE_STEP = 0.25f;

		private const float EDGE_BAND_WIDTH = 0.6f;

		private const float EDGE_LEVEL_RADIUS = 1.8f;

		private const float EDGE_RAISE_EPSILON = 0.03f;

		private const float STAGE_RAISE_EPSILON = 0.02f;

		private const float SPIKE_SCAN_STEP = 0.5f;

		private const float SPIKE_LEVEL_RADIUS = 1.5f;

		private const float SPIKE_TOLERANCE = 0.2f;

		private const float TEAR_REPAIR_SAMPLE_RADIUS = 1.2f;

		private const float TEAR_REPAIR_MAIN_RADIUS = 1.1f;

		private const float TEAR_REPAIR_BLEND_RADIUS = 0.7f;

		private const float TEAR_REPAIR_SECOND_BLEND_RADIUS = 1.05f;

		private const float TEAR_REPAIR_MAX_LOWER = 0.45f;

		private const float TEAR_REPAIR_SPIKE_THRESHOLD = 0.28f;

		private const float TEAR_REPAIR_CUT_RADIUS = 0.42f;

		private const float TEAR_REPAIR_RAISE_TOLERANCE = 0.01f;

		private const float TEAR_REPAIR_MIN_RELIEF = 0.08f;

		private const float TEAR_REPAIR_EDGE_RADIUS = 0.48f;

		private const float TEAR_REPAIR_EDGE_LENGTH = 1.25f;

		private const float TEAR_REPAIR_EDGE_STEP = 0.32f;

		private const float TEAR_REPAIR_EDGE_MAX_RAISE = 0.14f;

		private const float TEAR_REPAIR_EDGE_MEDIAN_HEADROOM = 0.08f;

		private const float TERRAIN_CLIP_HEIGHT_TOLERANCE = 0.02f;

		private const int INNER_PAD = 2;

		private const float WARN_RAISE = 6f;

		private const int OPS_PER_FRAME = 10;

		private static float LEVEL_RADIUS => Mathf.Clamp(ValheimFloorPlanPlugin.TerrainStampRadius, 3f, 6f);

		public static float TargetLevelY { get; private set; } = 0f;


		public static float RecommendedPlacementWait { get; private set; } = 2f;


		public static IEnumerator LevelForPlan(FloorPlan plan, Vector3 origin, float rotationDeg = 0f)
		{
			//IL_000e: 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)
			GetBounds(plan, origin, 2, 0f, out var innerMinX, out var innerMaxX, out var innerMinZ, out var innerMaxZ);
			float cosR = Mathf.Cos(rotationDeg * ((float)Math.PI / 180f));
			float sinR = Mathf.Sin(rotationDeg * ((float)Math.PI / 180f));
			bool axisAligned = Mathf.Approximately(rotationDeg % 90f, 0f);
			float derivedStep = Mathf.Min(0.5f, LEVEL_RADIUS * 0.17f);
			float preSampleStep = (axisAligned ? Mathf.Min(0.5f, LEVEL_RADIUS * 0.17f) : 0.25f);
			float levelSampleStep = (axisAligned ? derivedStep : Mathf.Min(0.25f, derivedStep));
			float spikeScanStep = (axisAligned ? 0.5f : 0.25f);
			float sampleMinX = innerMinX - LEVEL_RADIUS;
			float sampleMaxX = innerMaxX + LEVEL_RADIUS;
			float sampleMinZ = innerMinZ - LEVEL_RADIUS;
			float sampleMaxZ = innerMaxZ + LEVEL_RADIUS;
			float maxY = float.MinValue;
			float minY = float.MaxValue;
			int preStepsX = Mathf.CeilToInt((sampleMaxX - sampleMinX) / preSampleStep);
			int preStepsZ = Mathf.CeilToInt((sampleMaxZ - sampleMinZ) / preSampleStep);
			for (int ix = 0; ix <= preStepsX; ix++)
			{
				float lx = ((ix == preStepsX) ? sampleMaxX : (sampleMinX + (float)ix * preSampleStep));
				for (int iz = 0; iz <= preStepsZ; iz++)
				{
					float lz = ((iz == preStepsZ) ? sampleMaxZ : (sampleMinZ + (float)iz * preSampleStep));
					float ldx = lx - origin.x;
					float ldz = lz - origin.z;
					float wx = origin.x + ldx * cosR + ldz * sinR;
					float wz = origin.z - ldx * sinR + ldz * cosR;
					float h = SampleHeight(wx, wz, origin.y);
					if (h > maxY)
					{
						maxY = h;
					}
					if (h < minY)
					{
						minY = h;
					}
				}
			}
			if (maxY == float.MinValue)
			{
				maxY = origin.y;
				minY = origin.y;
			}
			float range = maxY - minY;
			float highPointDelta = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainHighPointDelta, 0f, 4f);
			float targetY = (TargetLevelY = maxY + highPointDelta);
			if (range > 6f)
			{
				ValheimFloorPlanPlugin.Log.LogWarning((object)($"[TerrainLeveler] Footprint height range {range:F1}m — steep ground," + " cliff-edge tears may appear on the downhill side."));
				ValheimFloorPlanPlugin.ShowWrappedMessage(ValheimFloorPlanPlugin.WarningMessageType, $"ValheimFloorPlan: Steep terrain warning. Height range is {range:F1}m. " + "Terracing or downhill tears may occur.");
			}
			ValheimFloorPlanPlugin.Log.LogInfo((object)($"[TerrainLeveler] Raising pad to Y={targetY:F2} (maxY={maxY:F2} + delta={highPointDelta:F2})  minY={minY:F2}  range={range:F1}m" + $"  rotation={rotationDeg:F0}°" + $"  inner(local)=[{innerMinX:F1}..{innerMaxX:F1}] x [{innerMinZ:F1}..{innerMaxZ:F1}]"));
			int totalPasses = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainLevelPasses, 1, 5);
			bool stagedRaise = ValheimFloorPlanPlugin.TerrainUseStagedRaise;
			float raiseStepHeight = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainRaiseStepHeight, 0.15f, 1.5f);
			int maxRaiseStages = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainMaxRaiseStages, 1, 16);
			bool skipSatisfiedCenterStamps = ValheimFloorPlanPlugin.TerrainSkipSatisfiedCenterStamps;
			float totalRaiseHeight = targetY - minY;
			int stageCount = 1;
			if (stagedRaise && totalRaiseHeight > 0.02f)
			{
				stageCount = Mathf.Clamp(Mathf.CeilToInt(totalRaiseHeight / raiseStepHeight), 1, maxRaiseStages);
			}
			float stageHeight = ((stageCount > 0) ? (totalRaiseHeight / (float)stageCount) : totalRaiseHeight);
			int stepsX = Mathf.CeilToInt((innerMaxX - innerMinX) / levelSampleStep);
			int stepsZ = Mathf.CeilToInt((innerMaxZ - innerMinZ) / levelSampleStep);
			float edgeSampleStep = Mathf.Min(levelSampleStep, 0.25f);
			int edgeStepsX = Mathf.CeilToInt((innerMaxX - innerMinX) / edgeSampleStep);
			int edgeStepsZ = Mathf.CeilToInt((innerMaxZ - innerMinZ) / edgeSampleStep);
			int edgePointsPerPass = 0;
			for (int ix5 = 0; ix5 <= edgeStepsX; ix5++)
			{
				float lx2 = ((ix5 == edgeStepsX) ? innerMaxX : (innerMinX + (float)ix5 * edgeSampleStep));
				for (int iz2 = 0; iz2 <= edgeStepsZ; iz2++)
				{
					float lz2 = ((iz2 == edgeStepsZ) ? innerMaxZ : (innerMinZ + (float)iz2 * edgeSampleStep));
					if (IsInEdgeBand(lx2, lz2, innerMinX, innerMaxX, innerMinZ, innerMaxZ, 0.6f))
					{
						edgePointsPerPass++;
					}
				}
			}
			int ops = 0;
			HashSet<TerrainComp> modified = new HashSet<TerrainComp>();
			int totalLevelOps = stageCount * totalPasses * ((stepsX + 1) * (stepsZ + 1) + edgePointsPerPass);
			int nextLevelPct = 5;
			float nextLevelHeartbeatAt = Time.time + 1.5f;
			int spinnerIndex = 0;
			string[] spinnerFrames = new string[4] { "|", "/", "-", "\\" };
			ValheimFloorPlanPlugin.Log.LogInfo((object)($"[TerrainLeveler] Running {totalPasses} leveling pass(es) across {stageCount} raise stage(s) " + $"(range={range:F1}m, totalRaise={totalRaiseHeight:F1}m, staged={stagedRaise}, stageStep={raiseStepHeight:F2}m)."));
			ShowProgress($"Leveling terrain... 0% ({totalPasses} pass(es), {stageCount} stage(s))");
			for (int stage = 1; stage <= stageCount; stage++)
			{
				float stageTargetY = ((stage == stageCount) ? targetY : Mathf.Min(targetY, minY + stageHeight * (float)stage));
				for (int pass = 1; pass <= totalPasses; pass++)
				{
					float effectiveEdgeRadius = Mathf.Min(1.8f, LEVEL_RADIUS);
					for (int ix3 = 0; ix3 <= stepsX; ix3++)
					{
						float lx3 = ((ix3 == stepsX) ? innerMaxX : (innerMinX + (float)ix3 * levelSampleStep));
						for (int iz3 = 0; iz3 <= stepsZ; iz3++)
						{
							float lz3 = ((iz3 == stepsZ) ? innerMaxZ : (innerMinZ + (float)iz3 * levelSampleStep));
							float ldx2 = lx3 - origin.x;
							float ldz2 = lz3 - origin.z;
							float wx2 = origin.x + ldx2 * cosR + ldz2 * sinR;
							float wz2 = origin.z - ldx2 * sinR + ldz2 * cosR;
							float h2 = SampleHeight(wx2, wz2, stageTargetY);
							if (!skipSatisfiedCenterStamps || h2 < stageTargetY - 0.02f)
							{
								ApplyLevel(wx2, stageTargetY, wz2, LEVEL_RADIUS, modified);
							}
							ops++;
							if (totalLevelOps > 0)
							{
								int pct = Mathf.FloorToInt((float)ops * 100f / (float)totalLevelOps);
								bool emitProgress = false;
								if (pct >= nextLevelPct)
								{
									for (; nextLevelPct <= pct; nextLevelPct += 5)
									{
									}
									emitProgress = true;
								}
								if (Time.time >= nextLevelHeartbeatAt)
								{
									nextLevelHeartbeatAt = Time.time + 1.5f;
									spinnerIndex = (spinnerIndex + 1) % spinnerFrames.Length;
									emitProgress = true;
								}
								if (emitProgress)
								{
									int shownPct = Mathf.Clamp(pct, 0, 99);
									ShowProgress($"Leveling terrain... {shownPct}% {spinnerFrames[spinnerIndex]} " + $"(stage {stage}/{stageCount}, pass {pass}/{totalPasses})");
								}
							}
							if (ops % 10 == 0)
							{
								yield return null;
							}
						}
					}
					for (int ix2 = 0; ix2 <= edgeStepsX; ix2++)
					{
						float lx4 = ((ix2 == edgeStepsX) ? innerMaxX : (innerMinX + (float)ix2 * edgeSampleStep));
						for (int iz4 = 0; iz4 <= edgeStepsZ; iz4++)
						{
							float lz4 = ((iz4 == edgeStepsZ) ? innerMaxZ : (innerMinZ + (float)iz4 * edgeSampleStep));
							if (!IsInEdgeBand(lx4, lz4, innerMinX, innerMaxX, innerMinZ, innerMaxZ, 0.6f))
							{
								continue;
							}
							float ldx3 = lx4 - origin.x;
							float ldz3 = lz4 - origin.z;
							float wx3 = origin.x + ldx3 * cosR + ldz3 * sinR;
							float wz3 = origin.z - ldx3 * sinR + ldz3 * cosR;
							float h3 = SampleHeight(wx3, wz3, stageTargetY);
							if (h3 >= stageTargetY - 0.03f)
							{
								continue;
							}
							ApplyLevel(wx3, stageTargetY, wz3, effectiveEdgeRadius, modified);
							ops++;
							if (totalLevelOps > 0)
							{
								int pct2 = Mathf.FloorToInt((float)ops * 100f / (float)totalLevelOps);
								bool emitProgress2 = false;
								if (pct2 >= nextLevelPct)
								{
									for (; nextLevelPct <= pct2; nextLevelPct += 5)
									{
									}
									emitProgress2 = true;
								}
								if (Time.time >= nextLevelHeartbeatAt)
								{
									nextLevelHeartbeatAt = Time.time + 1.5f;
									spinnerIndex = (spinnerIndex + 1) % spinnerFrames.Length;
									emitProgress2 = true;
								}
								if (emitProgress2)
								{
									int shownPct2 = Mathf.Clamp(pct2, 0, 99);
									ShowProgress($"Leveling terrain... {shownPct2}% {spinnerFrames[spinnerIndex]} " + $"(stage {stage}/{stageCount}, pass {pass}/{totalPasses})");
								}
							}
							if (ops % 10 == 0)
							{
								yield return null;
							}
						}
					}
					if (pass < totalPasses)
					{
						yield return (object)new WaitForSeconds(0.1f);
					}
				}
				if (stage < stageCount)
				{
					yield return (object)new WaitForSeconds(0.08f);
				}
			}
			int spikePasses = Mathf.Clamp(ValheimFloorPlanPlugin.TerrainSpikeCleanupPasses, 1, 5);
			int spikeOps = 0;
			int spikeStepsX = Mathf.CeilToInt((sampleMaxX - sampleMinX) / spikeScanStep);
			int spikeStepsZ = Mathf.CeilToInt((sampleMaxZ - sampleMinZ) / spikeScanStep);
			float nextCleanupProgressAt = Time.time + 1.5f;
			for (int pass2 = 1; pass2 <= spikePasses; pass2++)
			{
				for (int ix4 = 0; ix4 <= spikeStepsX; ix4++)
				{
					float lx5 = ((ix4 == spikeStepsX) ? sampleMaxX : (sampleMinX + (float)ix4 * spikeScanStep));
					for (int iz5 = 0; iz5 <= spikeStepsZ; iz5++)
					{
						float lz5 = ((iz5 == spikeStepsZ) ? sampleMaxZ : (sampleMinZ + (float)iz5 * spikeScanStep));
						float ldx4 = lx5 - origin.x;
						float ldz4 = lz5 - origin.z;
						float wx4 = origin.x + ldx4 * cosR + ldz4 * sinR;
						float wz4 = origin.z - ldx4 * sinR + ldz4 * cosR;
						float h4 = SampleHeight(wx4, wz4, targetY);
						if (h4 > targetY + 0.2f)
						{
							ApplyLevel(wx4, targetY, wz4, 1.5f, modified);
							spikeOps++;
							if (spikeOps % 10 == 0)
							{
								yield return null;
							}
						}
						if (Time.time >= nextCleanupProgressAt)
						{
							nextCleanupProgressAt = Time.time + 1.5f;
							ShowProgress($"Leveling terrain... cleanup (pass {pass2}/{spikePasses})");
						}
					}
				}
				if (pass2 < spikePasses)
				{
					yield return (object)new WaitForSeconds(0.05f);
				}
			}
			if (spikeOps > 0)
			{
				ValheimFloorPlanPlugin.Log.LogInfo((object)$"[TerrainLeveler] Spike suppression applied: {spikeOps} ops across {spikePasses} pass(es).");
				ShowProgress("Leveling terrain... final cleanup");
			}
			else
			{
				ValheimFloorPlanPlugin.Log.LogInfo((object)"[TerrainLeveler] Spike suppression: no residual peaks detected.");
			}
			RecommendedPlacementWait = Mathf.Max(2f, (float)modified.Count * 0.5f);
			ValheimFloorPlanPlugin.Log.LogInfo((object)($"[TerrainLeveler] Leveling done: {totalPasses} passes, {ops} ops, " + $"{modified.Count} chunks.  Placement wait: {RecommendedPlacementWait:F1}s."));
			ShowProgress("Leveling terrain... done");
		}

		public static bool IsRepairTargetValid(Vector3 point, out string reason)
		{
			//IL_0001: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			float referenceY = SampleHeight(point.x, point.z, point.y);
			Vector2 slopeDir;
			float ridgeY;
			float troughY;
			List<float> list = CollectRepairSamples(point, referenceY, out slopeDir, out ridgeY, out troughY);
			if (list.Count == 0)
			{
				reason = "Unable to sample terrain.";
				return false;
			}
			list.Sort();
			float num = list[0];
			float num2 = list[list.Count - 1];
			float num3 = num2 - num;
			if (num3 < 0.08f)
			{
				reason = "Area is too flat — no tear detected here.";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		public static IEnumerator RepairTearAtPoint(Vector3 point)
		{
			//IL_0007: 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)
			HashSet<TerrainComp> modified = new HashSet<TerrainComp>();
			float centerY = SampleHeight(point.x, point.z, point.y);
			Vector2 slopeDir;
			float ridgeY;
			float troughY;
			List<float> samples = CollectRepairSamples(point, centerY, out slopeDir, out ridgeY, out troughY);
			samples.Sort();
			float medianY = ((samples.Count > 0) ? samples[samples.Count / 2] : centerY);
			float lowerTargetY2 = Mathf.Min(centerY, medianY + 0.03f);
			lowerTargetY2 = Mathf.Max(centerY - 0.45f, lowerTargetY2);
			Vector2[] passOffsets = (Vector2[])(object)new Vector2[5]
			{
				new Vecto