Decompiled source of ImmersiveTrader v1.0.10

ImmersiveTrader.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using ImmersiveTrader.Commands;
using ImmersiveTrader.Components;
using ImmersiveTrader.Models;
using ImmersiveTrader.Patches;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Valheim.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ImmersiveTrader")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ImmersiveTrader")]
[assembly: AssemblyTitle("ImmersiveTrader")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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;
		}
	}
	internal static class IsExternalInit
	{
	}
}
namespace ImmersiveTrader
{
	public sealed class TraderSanctuary : MonoBehaviour
	{
		private const float SafeRadius = 11f;

		private const float ExitRadius = 15f;

		private readonly Collider[] _nearby = (Collider[])(object)new Collider[128];

		private readonly HashSet<Character> _seen = new HashSet<Character>();

		private float _nextCheck;

		private void Update()
		{
			//IL_0056: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: 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)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || Time.time < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.time + 0.5f;
			_seen.Clear();
			Vector3 position = ((Component)this).transform.position;
			int num = Physics.OverlapSphereNonAlloc(position, 11f, _nearby);
			for (int i = 0; i < num; i++)
			{
				Character val = (((Object)(object)_nearby[i] == (Object)null) ? null : ((Component)_nearby[i]).GetComponentInParent<Character>());
				if ((Object)(object)val == (Object)null || val.IsPlayer() || (Object)(object)((Component)val).GetComponent<BaseAI>() == (Object)null || ((Object)((Component)val).gameObject).name.StartsWith("ImmersiveTrader_", StringComparison.Ordinal) || !_seen.Add(val))
				{
					continue;
				}
				Vector3 val2 = ((Component)val).transform.position - position;
				val2.y = 0f;
				if (!(((Vector3)(ref val2)).sqrMagnitude >= 121f))
				{
					if (((Vector3)(ref val2)).sqrMagnitude < 0.01f)
					{
						val2 = Vector3.forward;
					}
					Vector3 position2 = position + ((Vector3)(ref val2)).normalized * 15f;
					position2.y = ((Component)val).transform.position.y;
					((Component)val).transform.position = position2;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Awake")]
	internal static class ItAintFunnyLaughPatch
	{
		private static void Postfix(Player __instance)
		{
			if (!MultiplayerNetwork.IsDedicatedServer && (Object)(object)((Component)__instance).GetComponent<ItAintFunnyAudio>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<ItAintFunnyAudio>();
			}
		}
	}
	internal sealed class ItAintFunnyAudio : MonoBehaviour
	{
		private const string MaleResource = "ImmersiveTrader.Assets.ItAintFunny_Male.ogg";

		private const string FemaleResource = "ImmersiveTrader.Assets.ItAintFunny_Female.ogg";

		private const string LaughRpc = "ImmersiveTrader_NoKiddingLaugh";

		private const int Stop = 0;

		private const int Male = 1;

		private const int Female = 2;

		private static AudioClip? _male;

		private static AudioClip? _female;

		private static bool _loading;

		private AudioSource? _source;

		private Player? _player;

		private ZNetView? _nview;

		private float _nextLaugh;

		private bool _wasActive;

		private bool _ownsLoad;

		private static readonly FieldInfo? ModelIndexField = AccessTools.Field(typeof(Player), "m_modelIndex");

		private void Awake()
		{
			_player = ((Component)this).GetComponent<Player>();
			_nview = ((Component)this).GetComponent<ZNetView>();
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.playOnAwake = false;
			_source.loop = false;
			_source.spatialBlend = 1f;
			_source.rolloffMode = (AudioRolloffMode)1;
			_source.minDistance = 2f;
			_source.maxDistance = 25f;
			_source.dopplerLevel = 0f;
			if ((Object)(object)_nview != (Object)null && _nview.GetZDO() != null)
			{
				_nview.Register<int>("ImmersiveTrader_NoKiddingLaugh", (Action<long, int>)RPC_Laugh);
			}
			if (!_loading)
			{
				_loading = true;
				_ownsLoad = true;
				((MonoBehaviour)this).StartCoroutine(LoadClips());
			}
		}

		private void OnDestroy()
		{
			if (_ownsLoad && ((Object)(object)_male == (Object)null || (Object)(object)_female == (Object)null))
			{
				_loading = false;
			}
		}

		private static IEnumerator LoadClips()
		{
			yield return LoadEmbeddedOgg("ImmersiveTrader.Assets.ItAintFunny_Male.ogg", "ImmersiveTrader_ItAintFunny_Male.ogg", delegate(AudioClip c)
			{
				_male = c;
			});
			yield return LoadEmbeddedOgg("ImmersiveTrader.Assets.ItAintFunny_Female.ogg", "ImmersiveTrader_ItAintFunny_Female.ogg", delegate(AudioClip c)
			{
				_female = c;
			});
		}

		private static IEnumerator LoadEmbeddedOgg(string resource, string fileName, Action<AudioClip> assign)
		{
			Assembly asm = typeof(ItAintFunnyAudio).Assembly;
			using Stream stream = asm.GetManifestResourceStream(resource);
			if (stream == null)
			{
				Debug.LogError((object)("Missing embedded audio resource: " + resource));
				yield break;
			}
			string dir = Path.Combine(Paths.CachePath, "ImmersiveTrader");
			Directory.CreateDirectory(dir);
			string path = Path.Combine(dir, fileName);
			using (FileStream file = File.Create(path))
			{
				stream.CopyTo(file);
			}
			UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip("file:///" + path.Replace('\\', '/'), (AudioType)14);
			try
			{
				yield return req.SendWebRequest();
				if ((int)req.result != 1)
				{
					Debug.LogError((object)("Failed loading " + resource + ": " + req.error));
					yield break;
				}
				AudioClip clip = DownloadHandlerAudioClip.GetContent(req);
				((Object)clip).name = Path.GetFileNameWithoutExtension(fileName);
				assign(clip);
			}
			finally
			{
				((IDisposable)req)?.Dispose();
			}
		}

		private void Update()
		{
			if ((Object)(object)_player == (Object)null || (Object)(object)_source == (Object)null || (Object)(object)_player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if (!EnhancementStatusRegistry.Has((Character)(object)_player, "nokidding"))
			{
				if (_wasActive)
				{
					Broadcast(0);
				}
				_wasActive = false;
				_nextLaugh = 0f;
			}
			else if (!_wasActive)
			{
				_wasActive = true;
				_nextLaugh = Time.time + Random.Range(2f, 5f);
			}
			else if (!_source.isPlaying && !(Time.time < _nextLaugh))
			{
				int num = ((!IsFemale()) ? 1 : 2);
				AudioClip val = ((num == 2) ? _female : _male);
				if (!((Object)(object)val == (Object)null))
				{
					Broadcast(num);
					_nextLaugh = Time.time + val.length + Random.Range(2f, 5f);
				}
			}
		}

		private void Broadcast(int voice)
		{
			if ((Object)(object)_nview != (Object)null && _nview.IsValid())
			{
				_nview.InvokeRPC(ZNetView.Everybody, "ImmersiveTrader_NoKiddingLaugh", new object[1] { voice });
			}
			else
			{
				Play(voice);
			}
		}

		private void RPC_Laugh(long sender, int voice)
		{
			if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.GetZDO().GetOwner() == sender)
			{
				Play(voice);
			}
		}

		private void Play(int voice)
		{
			if (!((Object)(object)_source == (Object)null))
			{
				AudioClip? obj;
				switch (voice)
				{
				case 0:
					_source.Stop();
					return;
				default:
					obj = _male;
					break;
				case 2:
					obj = _female;
					break;
				}
				AudioClip val = obj;
				if (!((Object)(object)val == (Object)null))
				{
					_source.clip = val;
					_source.Play();
				}
			}
		}

		private bool IsFemale()
		{
			if ((Object)(object)_player == (Object)null || ModelIndexField == null)
			{
				return false;
			}
			try
			{
				return ModelIndexField.GetValue(_player) is int num && num == 1;
			}
			catch
			{
				return false;
			}
		}
	}
	[BepInPlugin("p0kahontazz.immersivetrader", "ImmersiveTrader", "0.7.18")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		private bool _rewardValidationPending;

		internal static ManualLogSource Log;

		public const string ModGuid = "p0kahontazz.immersivetrader";

		public const string ModName = "ImmersiveTrader";

		public const string ModVersion = "0.7.18";

		internal static ConfigEntry<float> TreasureWeight;

		internal static ConfigEntry<int> MaxCarriedTreasures;

		internal static ConfigEntry<int> MidkaHealingMeadPrice;

		internal static ConfigEntry<bool> ProgressionLock;

		internal static ConfigEntry<int> TraderCooldownWorldDays;

		internal static ConfigEntry<int> MietegActiveWorldDays;

		internal static ConfigEntry<float> MietegRevealDistance;

		internal static ConfigEntry<EnhancementStockMode> EnhancementOffers;

		private void Awake()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Expected O, but got Unknown
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Expected O, but got Unknown
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Expected O, but got Unknown
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PlayerLikeNpcVisual.InitializeOutfitFiles();
			ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			};
			TreasureWeight = ((BaseUnityPlugin)this).Config.Bind<float>("Treasures", "Weight", 80f, new ConfigDescription("Weight of each quest treasure.", (AcceptableValueBase)null, new object[1] { val }));
			MaxCarriedTreasures = ((BaseUnityPlugin)this).Config.Bind<int>("Treasures", "MaxCarried", 2, new ConfigDescription("Maximum number of active quest treasures.", (AcceptableValueBase)null, new object[1] { val }));
			MidkaHealingMeadPrice = ((BaseUnityPlugin)this).Config.Bind<int>("Midka", "MinorHealingMeadPrice", 70, new ConfigDescription("Legacy configuration value. Midka's native shop always sells Minor Healing Mead for 70 coins.", (AcceptableValueBase)null, new object[1] { val }));
			ProgressionLock = ((BaseUnityPlugin)this).Config.Bind<bool>("Progression", "Enabled", true, new ConfigDescription("Prevent rewards from biomes not unlocked by boss progression.", (AcceptableValueBase)null, new object[1] { val }));
			TraderCooldownWorldDays = ((BaseUnityPlugin)this).Config.Bind<int>("Quests", "TraderCooldownWorldDays", 3, new ConfigDescription("World days before the same trader can issue another shipment to a player.", (AcceptableValueBase)null, new object[1] { val }));
			MietegActiveWorldDays = ((BaseUnityPlugin)this).Config.Bind<int>("LegendaryMieteg", "ActiveWorldDays", 2, new ConfigDescription("How many world days Mieteg remains active.", (AcceptableValueBase)null, new object[1] { val }));
			MietegRevealDistance = ((BaseUnityPlugin)this).Config.Bind<float>("LegendaryMieteg", "RevealDistance", 600f, new ConfigDescription("Distance in metres at which Mieteg becomes discoverable.", (AcceptableValueBase)null, new object[1] { val }));
			EnhancementOffers = ((BaseUnityPlugin)this).Config.Bind<EnhancementStockMode>("TraderStock", "EnhancementOffers", EnhancementStockMode.ScrollsAndWards, new ConfigDescription("Enhancement items offered by all NPCs, including Mieteg: ScrollsAndWards, ScrollsOnly, WardsOnly, None. The server controls multiplayer stock. Existing items and rewards are unchanged.", (AcceptableValueBase)null, new object[1] { val }));
			MultiplayerNetwork.Register();
			_harmony = new Harmony("p0kahontazz.immersivetrader");
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0)
				{
					try
					{
						_harmony.CreateClassProcessor(type).Patch();
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)("Patch " + type.Name + " failed and was skipped: " + ex.Message));
					}
				}
			}
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ImmersiveTraderCommand());
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
			ZoneManager.OnVanillaLocationsAvailable += OnVanillaLocationsAvailable;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"ImmersiveTrader 0.7.18 loaded.");
		}

		private void OnDestroy()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			ZoneManager.OnVanillaLocationsAvailable -= OnVanillaLocationsAvailable;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void OnVanillaPrefabsAvailable()
		{
			TreasureRegistry.Register();
			ContractRegistry.Register();
			EnhancementStatusRegistry.Register();
			EnhancementScrollItems.Register();
			ShellItems.Register();
			TraderRegistry.Initialize();
			TraderActivityRegistry.Validate();
			RewardRegistry.Initialize();
			_rewardValidationPending = true;
			NpcPrefabRegistry.Register();
			LegendaryMietegRegistry.RegisterPrefab();
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
		}

		private void Update()
		{
			MultiplayerNetwork.TickStock();
			LegendaryMietegSchedule.Tick();
			if (_rewardValidationPending && !((Object)(object)ObjectDB.instance == (Object)null) && !((Object)(object)ObjectDB.instance.GetItemPrefab("Coins") == (Object)null))
			{
				RewardRegistry.Validate();
				TraderShopRegistry.Validate();
				_rewardValidationPending = false;
			}
		}

		private void OnVanillaLocationsAvailable()
		{
			TraderLocationRegistry.Register();
			LegendaryMietegRegistry.RegisterLocations();
			ZoneManager.OnVanillaLocationsAvailable -= OnVanillaLocationsAvailable;
		}
	}
	internal static class AshlandsCampPlacement
	{
		private static readonly Dictionary<string, float> Footprints = new Dictionary<string, float>(StringComparer.Ordinal);

		private const float Border = 4f;

		private const float Grid = 2f;

		private const float LavaThreshold = 0.25f;

		internal static void Register(string group, float footprintRadius)
		{
			Footprints[group] = Math.Max(12f, footprintRadius) + 4f;
		}

		internal static bool Accept(string group, Vector3 centre)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: 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)
			if (!Footprints.TryGetValue(group, out var value))
			{
				return true;
			}
			if (WorldGenerator.instance == null)
			{
				return false;
			}
			if (ZoneSystem.IsLavaPreHeightmap(centre, 0.25f))
			{
				return false;
			}
			float num = (float)Math.Floor((centre.x - value) / 2f) * 2f;
			float num2 = (float)Math.Floor((centre.z - value) / 2f) * 2f;
			float num3 = (float)Math.Ceiling((centre.x + value) / 2f) * 2f;
			float num4 = (float)Math.Ceiling((centre.z + value) / 2f) * 2f;
			float num5 = value + 2f;
			for (float num6 = num; num6 <= num3; num6 += 2f)
			{
				for (float num7 = num2; num7 <= num4; num7 += 2f)
				{
					float num8 = num6 - centre.x;
					float num9 = num7 - centre.z;
					if (!(num8 * num8 + num9 * num9 > num5 * num5) && ZoneSystem.IsLavaPreHeightmap(new Vector3(num6, centre.y, num7), 0.25f))
					{
						return false;
					}
				}
			}
			return true;
		}
	}
	internal static class BlueprintLibrary
	{
		internal sealed record Piece(string Prefab, Quaternion Rotation, Vector3 Position, Vector3 Scale);

		internal sealed record Blueprint(string Name, IReadOnlyList<Piece> Pieces, Vector3 Origin, Vector3 Size);

		internal sealed record Options(float? BaseY = null, float Crop = 0f, float? Cut = null, IReadOnlyList<Vector4>? Exclude = null, bool Foundation = false, IReadOnlyList<Bounds>? Remove = null, bool Settle = false, bool NoNature = false)
		{
			internal string Key
			{
				get
				{
					string[] obj = new string[8]
					{
						BaseY.HasValue ? $"@base{BaseY.Value:0.##}" : string.Empty,
						(Crop > 0f) ? $"@crop{Crop:0.#}" : string.Empty,
						Cut.HasValue ? $"@cut{Cut.Value:0.##}" : string.Empty,
						null,
						null,
						null,
						null,
						null
					};
					IReadOnlyList<Vector4> exclude = Exclude;
					obj[3] = ((exclude != null && exclude.Count > 0) ? ("@ex" + string.Join("_", Exclude.Select((Vector4 r) => $"{r.x:0}.{r.y:0}.{r.z:0}.{r.w:0}"))) : string.Empty);
					obj[4] = (Foundation ? "@found" : string.Empty);
					IReadOnlyList<Bounds> remove = Remove;
					obj[5] = ((remove != null && remove.Count > 0) ? ("@rm" + string.Join("_", Remove.Select((Bounds b) => $"{((Bounds)(ref b)).center.x:0.#}.{((Bounds)(ref b)).center.y:0.#}.{((Bounds)(ref b)).center.z:0.#}.{((Bounds)(ref b)).size.x:0.#}.{((Bounds)(ref b)).size.y:0.#}.{((Bounds)(ref b)).size.z:0.#}"))) : string.Empty);
					obj[6] = (Settle ? "@settle" : string.Empty);
					obj[7] = (NoNature ? "@nonature" : string.Empty);
					return string.Concat(obj);
				}
			}

			[CompilerGenerated]
			private bool PrintMembers(StringBuilder builder)
			{
				RuntimeHelpers.EnsureSufficientExecutionStack();
				builder.Append("BaseY = ");
				builder.Append(BaseY.ToString());
				builder.Append(", Crop = ");
				builder.Append(Crop.ToString());
				builder.Append(", Cut = ");
				builder.Append(Cut.ToString());
				builder.Append(", Exclude = ");
				builder.Append(Exclude);
				builder.Append(", Foundation = ");
				builder.Append(Foundation.ToString());
				builder.Append(", Remove = ");
				builder.Append(Remove);
				builder.Append(", Settle = ");
				builder.Append(Settle.ToString());
				builder.Append(", NoNature = ");
				builder.Append(NoNature.ToString());
				return true;
			}
		}

		private static readonly string[] Extensions = new string[2] { ".vbuild", ".blueprint" };

		private static readonly Dictionary<string, string> StandIns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["stone_1x1_enforced_bal"] = "stone_wall_1x1",
			["hexwood_floor_4m_bal"] = "stone_floor_2x2",
			["hexwood_floor_half_4m_bal"] = "stone_floor_2x2",
			["rune_floor_bal"] = "stone_floor_2x2",
			["darkwoodwall1m_bal"] = "stone_wall_1x1"
		};

		private static readonly List<GameObject> Previews = new List<GameObject>();

		private static readonly Dictionary<string, Blueprint> Parsed = new Dictionary<string, Blueprint>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, HashSet<(int, int)>> CellSets = new Dictionary<string, HashSet<(int, int)>>();

		private static readonly Dictionary<string, (GameObject Root, int Built, int Total, List<string> Missing)> Templates = new Dictionary<string, (GameObject, int, int, List<string>)>(StringComparer.OrdinalIgnoreCase);

		private static GameObject? _templateHolder;

		internal static string Folder => Path.Combine(Paths.ConfigPath, "ImmersiveTrader", "blueprints");

		internal static bool SkippedByDefault(string prefab)
		{
			return prefab.Equals("piece_xmastree", StringComparison.OrdinalIgnoreCase) || prefab.IndexOf("door", StringComparison.OrdinalIgnoreCase) >= 0 || prefab.IndexOf("gate", StringComparison.OrdinalIgnoreCase) >= 0 || prefab.IndexOf("portal", StringComparison.OrdinalIgnoreCase) >= 0 || prefab.Equals("iron_grate", StringComparison.OrdinalIgnoreCase);
		}

		internal static IEnumerable<string> List()
		{
			Directory.CreateDirectory(Folder);
			return from x in (from f in Directory.GetFiles(Folder)
					where Extensions.Contains<string>(Path.GetExtension(f), StringComparer.OrdinalIgnoreCase)
					select f).Select(Path.GetFileNameWithoutExtension).Distinct()
				orderby x
				select x;
		}

		private static string? FindFile(string name)
		{
			string text = Path.Combine(Folder, name);
			if (Extensions.Contains<string>(Path.GetExtension(name), StringComparer.OrdinalIgnoreCase) && File.Exists(text))
			{
				return text;
			}
			string[] extensions = Extensions;
			foreach (string text2 in extensions)
			{
				string text3 = Path.Combine(Folder, name + text2);
				if (File.Exists(text3))
				{
					return text3;
				}
			}
			return null;
		}

		internal static Blueprint? Load(string name, out string error, Options? options = null)
		{
			//IL_0471: Unknown result type (might be due to invalid IL or missing references)
			//IL_0478: Unknown result type (might be due to invalid IL or missing references)
			//IL_0488: Unknown result type (might be due to invalid IL or missing references)
			//IL_048f: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ab: Unknown result type (might be due to invalid IL or missing references)
			error = string.Empty;
			if ((object)options == null)
			{
				options = new Options();
			}
			string text = name + options.Key;
			if (Parsed.TryGetValue(text, out Blueprint value))
			{
				return value;
			}
			string text2 = FindFile(name);
			bool flag;
			string[] lines;
			if (text2 != null)
			{
				flag = text2.EndsWith(".blueprint", StringComparison.OrdinalIgnoreCase);
				lines = File.ReadAllLines(text2);
			}
			else
			{
				using Stream stream = typeof(BlueprintLibrary).Assembly.GetManifestResourceStream("ImmersiveTrader.Camps." + Path.GetFileNameWithoutExtension(name) + ".blueprint");
				if (stream == null)
				{
					error = "Blueprint not found: " + name;
					return null;
				}
				using StreamReader streamReader = new StreamReader(stream);
				lines = streamReader.ReadToEnd().Split('\n');
				flag = true;
			}
			List<Piece> list = (flag ? ParsePlanBuild(name, lines) : ParseVBuild(name, lines));
			if (list.Count == 0)
			{
				error = "Blueprint " + name + " has no readable pieces.";
				return null;
			}
			if (name.Equals("home-on-stilts-dock", StringComparison.OrdinalIgnoreCase))
			{
				list = list.Where((Piece p) => !p.Prefab.Equals("wood_floor", StringComparison.OrdinalIgnoreCase) || Mathf.Abs(p.Position.y) > 0.1f).ToList();
			}
			float num = options.BaseY ?? (flag ? 0f : list.Min((Piece p) => p.Position.y));
			IReadOnlyList<Vector4> exclude = options.Exclude;
			if (exclude != null && exclude.Count > 0)
			{
				list = list.Where((Piece p) => !options.Exclude.Any((Vector4 r) => p.Position.x >= Mathf.Min(r.x, r.z) && p.Position.x <= Mathf.Max(r.x, r.z) && p.Position.z >= Mathf.Min(r.y, r.w) && p.Position.z <= Mathf.Max(r.y, r.w))).ToList();
			}
			if (options.NoNature)
			{
				list = list.Where((Piece p) => !IsNature(p.Prefab)).ToList();
			}
			IReadOnlyList<Bounds> remove = options.Remove;
			if (remove != null && remove.Count > 0)
			{
				list = list.Where((Piece p) => !options.Remove.Any((Bounds b) => ((Bounds)(ref b)).Contains(p.Position))).ToList();
			}
			if (options.Cut.HasValue)
			{
				list = list.Where((Piece p) => p.Position.y >= options.Cut.Value).ToList();
			}
			if (options.Crop > 0f)
			{
				list = CropClusters(list, options.Crop);
			}
			if (options.Settle)
			{
				list = SettleBuildings(list, num);
			}
			if (options.Foundation)
			{
				list = WithFoundation(list, num);
			}
			if (list.Count == 0)
			{
				error = "Blueprint " + name + ": nothing left after crop/cut/exclude.";
				return null;
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(list.Min((Piece p) => p.Position.x), list.Min((Piece p) => p.Position.y), list.Min((Piece p) => p.Position.z));
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(list.Max((Piece p) => p.Position.x), list.Max((Piece p) => p.Position.y), list.Max((Piece p) => p.Position.z));
			Vector3 origin = default(Vector3);
			((Vector3)(ref origin))..ctor((val.x + val2.x) * 0.5f, num, (val.z + val2.z) * 0.5f);
			Blueprint blueprint = new Blueprint(text, list, origin, val2 - val);
			Parsed[text] = blueprint;
			return blueprint;
		}

		internal static Blueprint WithPieces(Blueprint blueprint, string suffix, IEnumerable<Piece> extra)
		{
			string text = blueprint.Name + suffix;
			if (Parsed.TryGetValue(text, out Blueprint value))
			{
				return value;
			}
			Blueprint blueprint2 = blueprint with
			{
				Name = text,
				Pieces = blueprint.Pieces.Concat(extra).ToList()
			};
			Parsed[text] = blueprint2;
			return blueprint2;
		}

		internal static List<(Vector3 Centre, float Radius)> BuildingSpots(Blueprint blueprint)
		{
			return BuildingBounds(blueprint).Select(delegate((Vector3 Min, Vector3 Max) b)
			{
				//IL_0002: 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_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: 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_001e: 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_002e: 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_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = Flat3(b.Min - blueprint.Origin);
				Vector3 val2 = Flat3(b.Max - blueprint.Origin);
				Vector3 item = (val + val2) * 0.5f;
				Vector3 val3 = val2 - val;
				return (item, ((Vector3)(ref val3)).magnitude * 0.5f + 1f);
			}).ToList();
		}

		private static Vector3 Flat3(Vector3 v)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(v.x, 0f, v.z);
		}

		internal static List<(Vector3 Min, Vector3 Max)> BuildingBounds(Blueprint blueprint)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: 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_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<(int, int), List<Vector3>> dictionary = new Dictionary<(int, int), List<Vector3>>();
			foreach (Piece piece in blueprint.Pieces)
			{
				if (!IsNature(piece.Prefab))
				{
					Vector3 position = piece.Position;
					(int, int) key = (Mathf.FloorToInt(position.x / 3f), Mathf.FloorToInt(position.z / 3f));
					if (!dictionary.TryGetValue(key, out var value))
					{
						value = (dictionary[key] = new List<Vector3>());
					}
					value.Add(new Vector3(position.x, 0f, position.z));
				}
			}
			List<(Vector3, Vector3)> list2 = new List<(Vector3, Vector3)>();
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			Vector3 item = default(Vector3);
			Vector3 item2 = default(Vector3);
			foreach (var key3 in dictionary.Keys)
			{
				if (!hashSet.Add(key3))
				{
					continue;
				}
				List<Vector3> list3 = new List<Vector3>();
				Queue<(int, int)> queue = new Queue<(int, int)>();
				queue.Enqueue(key3);
				while (queue.Count > 0)
				{
					(int, int) key2 = queue.Dequeue();
					list3.AddRange(dictionary[key2]);
					for (int i = -1; i <= 1; i++)
					{
						for (int j = -1; j <= 1; j++)
						{
							(int, int) tuple = (key2.Item1 + i, key2.Item2 + j);
							if (dictionary.ContainsKey(tuple) && hashSet.Add(tuple))
							{
								queue.Enqueue(tuple);
							}
						}
					}
				}
				if (list3.Count >= 8)
				{
					((Vector3)(ref item))..ctor(list3.Min((Vector3 p) => p.x), 0f, list3.Min((Vector3 p) => p.z));
					((Vector3)(ref item2))..ctor(list3.Max((Vector3 p) => p.x), 0f, list3.Max((Vector3 p) => p.z));
					list2.Add((item, item2));
				}
			}
			return list2;
		}

		internal static List<(Vector3 A, Vector3 B)> VillagePaths(Blueprint blueprint, Vector3 squareFile)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: 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)
			//IL_023a: 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)
			List<Vector3> nodes = (from b in BuildingBounds(blueprint)
				select (b.Min + b.Max) * 0.5f).Prepend(new Vector3(squareFile.x, 0f, squareFile.z)).ToList();
			HashSet<(int, int)> edges = new HashSet<(int, int)>();
			List<int> list = new List<int> { 0 };
			List<int> list2 = Enumerable.Range(1, nodes.Count - 1).ToList();
			Vector3 val;
			while (list2.Count > 0)
			{
				float num = float.MaxValue;
				int a = 0;
				int num2 = 0;
				foreach (int item in list)
				{
					foreach (int item2 in list2)
					{
						val = nodes[item] - nodes[item2];
						float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
						if (sqrMagnitude < num)
						{
							num = sqrMagnitude;
							a = item;
							num2 = item2;
						}
					}
				}
				Link(a, num2);
				list.Add(num2);
				list2.Remove(num2);
			}
			int i;
			for (i = 1; i < nodes.Count; i++)
			{
				foreach (int item3 in (from j in Enumerable.Range(1, nodes.Count - 1)
					where j != i
					select j).OrderBy(delegate(int j)
				{
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_0022: 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_002c: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val2 = nodes[i] - nodes[j];
					return ((Vector3)(ref val2)).sqrMagnitude;
				}).Take(2))
				{
					val = nodes[i] - nodes[item3];
					if (((Vector3)(ref val)).magnitude < 45f)
					{
						Link(i, item3);
					}
				}
			}
			return edges.Select(((int, int) e) => (nodes[e.Item1], nodes[e.Item2])).ToList();
			void Link(int num3, int b)
			{
				if (num3 != b)
				{
					edges.Add((num3 < b) ? (num3, b) : (b, num3));
				}
			}
		}

		internal static bool OnBuilding(Blueprint blueprint, float fileX, float fileZ)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			HashSet<(int, int)> hashSet = GroundCellSet(blueprint);
			return hashSet.Contains((Mathf.FloorToInt((fileX - blueprint.Origin.x) / 3f), Mathf.FloorToInt((fileZ - blueprint.Origin.z) / 3f)));
		}

		private static HashSet<(int, int)> GroundCellSet(Blueprint blueprint)
		{
			if (CellSets.TryGetValue(blueprint.Name, out HashSet<(int, int)> value))
			{
				return value;
			}
			value = new HashSet<(int, int)>(from c in GroundCells(blueprint)
				select (Mathf.FloorToInt(c.x / 3f), Mathf.FloorToInt(c.z / 3f)));
			CellSets[blueprint.Name] = value;
			return value;
		}

		internal static List<Vector3> GroundCells(Blueprint blueprint, float cell = 3f)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_007b: 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)
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			foreach (Piece piece in blueprint.Pieces)
			{
				if (!IsNature(piece.Prefab) && !SkippedByDefault(piece.Prefab))
				{
					Vector3 val = piece.Position - blueprint.Origin;
					if (!(val.y > 3f))
					{
						hashSet.Add((Mathf.FloorToInt(val.x / cell), Mathf.FloorToInt(val.z / cell)));
					}
				}
			}
			return ((IEnumerable<(int, int)>)hashSet).Select((Func<(int, int), Vector3>)(((int, int) c) => new Vector3(((float)c.Item1 + 0.5f) * cell, 0f, ((float)c.Item2 + 0.5f) * cell))).ToList();
		}

		private static bool IsNature(string prefab)
		{
			return new string[11]
			{
				"Pickable", "Bush", "Beech", "Oak", "Fir", "Pine", "Birch", "shrub", "Rock", "vines",
				"Tree"
			}.Any((string w) => prefab.IndexOf(w, StringComparison.OrdinalIgnoreCase) >= 0);
		}

		private static List<Piece> SettleBuildings(List<Piece> pieces, float ground)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = new List<Piece>();
			Dictionary<(int, int), List<Piece>> dictionary = new Dictionary<(int, int), List<Piece>>();
			foreach (Piece piece in pieces)
			{
				if (IsNature(piece.Prefab))
				{
					list.Add(piece with
					{
						Position = new Vector3(piece.Position.x, ground, piece.Position.z)
					});
					continue;
				}
				(int, int) key = (Mathf.FloorToInt(piece.Position.x / 3f), Mathf.FloorToInt(piece.Position.z / 3f));
				if (!dictionary.TryGetValue(key, out var value))
				{
					value = (dictionary[key] = new List<Piece>());
				}
				value.Add(piece);
			}
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			foreach (var key3 in dictionary.Keys)
			{
				if (!hashSet.Add(key3))
				{
					continue;
				}
				List<Piece> list3 = new List<Piece>();
				Queue<(int, int)> queue = new Queue<(int, int)>();
				queue.Enqueue(key3);
				while (queue.Count > 0)
				{
					(int, int) key2 = queue.Dequeue();
					list3.AddRange(dictionary[key2]);
					for (int i = -1; i <= 1; i++)
					{
						for (int j = -1; j <= 1; j++)
						{
							(int, int) tuple = (key2.Item1 + i, key2.Item2 + j);
							if (dictionary.ContainsKey(tuple) && hashSet.Add(tuple))
							{
								queue.Enqueue(tuple);
							}
						}
					}
				}
				List<float> list4 = (from p in list3
					select p.Position.y into y
					orderby y
					select y).ToList();
				float num = list4[Mathf.Min(list4.Count - 1, list4.Count / 20)];
				float num2 = Mathf.Max(0f, num - ground);
				foreach (Piece item in list3)
				{
					list.Add((num2 > 0.01f) ? item with
					{
						Position = item.Position + Vector3.down * num2
					} : item);
				}
			}
			return list;
		}

		private static List<Piece> CropClusters(List<Piece> pieces, float radius)
		{
			//IL_00d4: 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_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			float cx = (pieces.Min((Piece p) => p.Position.x) + pieces.Max((Piece p) => p.Position.x)) * 0.5f;
			float cz = (pieces.Min((Piece p) => p.Position.z) + pieces.Max((Piece p) => p.Position.z)) * 0.5f;
			Dictionary<(int, int), List<Piece>> dictionary = new Dictionary<(int, int), List<Piece>>();
			foreach (Piece piece in pieces)
			{
				(int, int) key = (Mathf.FloorToInt(piece.Position.x / 4f), Mathf.FloorToInt(piece.Position.z / 4f));
				if (!dictionary.TryGetValue(key, out var value))
				{
					value = (dictionary[key] = new List<Piece>());
				}
				value.Add(piece);
			}
			List<Piece> list2 = new List<Piece>();
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			foreach (var key3 in dictionary.Keys)
			{
				if (!hashSet.Add(key3))
				{
					continue;
				}
				List<Piece> list3 = new List<Piece>();
				Queue<(int, int)> queue = new Queue<(int, int)>();
				queue.Enqueue(key3);
				while (queue.Count > 0)
				{
					(int, int) key2 = queue.Dequeue();
					list3.AddRange(dictionary[key2]);
					for (int num = -1; num <= 1; num++)
					{
						for (int num2 = -1; num2 <= 1; num2++)
						{
							(int, int) tuple = (key2.Item1 + num, key2.Item2 + num2);
							if (dictionary.ContainsKey(tuple) && hashSet.Add(tuple))
							{
								queue.Enqueue(tuple);
							}
						}
					}
				}
				float num3 = list3.Average((Piece p) => p.Position.x);
				float num4 = list3.Average((Piece p) => p.Position.z);
				Vector2 val = new Vector2(num3 - cx, num4 - cz);
				bool flag = ((Vector2)(ref val)).magnitude <= radius;
				bool flag2 = list3.Max(delegate(Piece p)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0013: Unknown result type (might be due to invalid IL or missing references)
					//IL_0024: 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)
					Vector2 val2 = new Vector2(p.Position.x - cx, p.Position.z - cz);
					return ((Vector2)(ref val2)).magnitude;
				}) <= radius * 1.4f;
				if (flag && flag2)
				{
					list2.AddRange(list3);
				}
			}
			return list2;
		}

		private static List<Piece> WithFoundation(List<Piece> pieces, float ground)
		{
			//IL_0051: 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_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = new List<Piece>(pieces);
			foreach (Piece piece in pieces)
			{
				if ((piece.Prefab.IndexOf("stone_wall", StringComparison.OrdinalIgnoreCase) >= 0 || piece.Prefab.IndexOf("stone_pillar", StringComparison.OrdinalIgnoreCase) >= 0) && !(piece.Position.y < ground - 0.3f) && !(piece.Position.y > ground + 1.2f))
				{
					float num = ((piece.Prefab.IndexOf("x2", StringComparison.OrdinalIgnoreCase) >= 0 || piece.Prefab.IndexOf("pillar", StringComparison.OrdinalIgnoreCase) >= 0) ? 2f : 1f);
					for (float num2 = piece.Position.y - num; num2 > ground - 4.5f; num2 -= num)
					{
						list.Add(piece with
						{
							Position = new Vector3(piece.Position.x, num2, piece.Position.z)
						});
					}
				}
			}
			return list;
		}

		internal static void ClearParsed()
		{
			Parsed.Clear();
			CellSets.Clear();
		}

		private static List<Piece> ParseVBuild(string name, string[] lines)
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = new List<Piece>();
			int num = 0;
			int num2 = 0;
			foreach (string text in lines)
			{
				num++;
				string text2 = text.TrimEnd('\r');
				if (text2.Trim().Length == 0 || text2.TrimStart().StartsWith("#"))
				{
					continue;
				}
				string[] array = text2.Split(' ');
				if (array.Length < 8 || array[0].Length == 0 || !TryF(array[1], out var value) || !TryF(array[2], out var value2) || !TryF(array[3], out var value3) || !TryF(array[4], out var value4) || !TryF(array[5], out var value5) || !TryF(array[6], out var value6) || !TryF(array[7], out var value7))
				{
					if (num2++ < 5)
					{
						Plugin.Log.LogWarning((object)$"Blueprint {name} line {num}: expected 'prefab qx qy qz qw px py pz'.");
					}
				}
				else
				{
					list.Add(new Piece(array[0], Rotation(value, value2, value3, value4), new Vector3(value5, value6, value7), Vector3.one));
				}
			}
			return list;
		}

		private static List<Piece> ParsePlanBuild(string name, string[] lines)
		{
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: 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_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = new List<Piece>();
			string text = string.Empty;
			int num = 0;
			int num2 = 0;
			foreach (string text2 in lines)
			{
				num++;
				string text3 = text2.Trim();
				if (text3.Length == 0)
				{
					continue;
				}
				if (text3.StartsWith("#"))
				{
					if (text3.IndexOf(':') < 0)
					{
						text = text3.Substring(1).Trim().ToLowerInvariant();
					}
				}
				else
				{
					if (text != "pieces")
					{
						continue;
					}
					string[] array = text3.Split(';');
					if (array.Length < 9 || array[0].Length == 0 || !TryP(array[2], out var value) || !TryP(array[3], out var value2) || !TryP(array[4], out var value3) || !TryP(array[5], out var value4) || !TryP(array[6], out var value5) || !TryP(array[7], out var value6) || !TryP(array[8], out var value7))
					{
						if (num2++ < 5)
						{
							Plugin.Log.LogWarning((object)$"Blueprint {name} line {num}: expected 'prefab;category;px;py;pz;qx;qy;qz;qw;...'.");
						}
						continue;
					}
					Vector3 one = Vector3.one;
					if (array.Length >= 13 && TryP(array[^3], out var value8) && TryP(array[^2], out var value9) && TryP(array[^1], out var value10) && value8 > 0f && value9 > 0f && value10 > 0f)
					{
						((Vector3)(ref one))..ctor(value8, value9, value10);
					}
					list.Add(new Piece(array[0], Rotation(value4, value5, value6, value7), new Vector3(value, value2, value3), one));
				}
			}
			return list;
		}

		private static Quaternion Rotation(float x, float y, float z, float w)
		{
			//IL_000c: 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_0019: 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)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			Quaternion val = default(Quaternion);
			((Quaternion)(ref val))..ctor(x, y, z, w);
			return (val.x == 0f && val.y == 0f && val.z == 0f && val.w == 0f) ? Quaternion.identity : Quaternion.Normalize(val);
		}

		private static bool TryP(string s, out float value)
		{
			return float.TryParse(s.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
		}

		internal static void ClearTemplates()
		{
			foreach (var value in Templates.Values)
			{
				if ((Object)(object)value.Root != (Object)null)
				{
					Object.Destroy((Object)(object)value.Root);
				}
			}
			Templates.Clear();
		}

		internal static GameObject? Instantiate(string name, Transform parent, out string report, IReadOnlyCollection<string>? skip = null)
		{
			string error;
			Blueprint blueprint = Load(name, out error);
			if (blueprint == null)
			{
				report = error;
				return null;
			}
			return Instantiate(blueprint, parent, out report, skip);
		}

		internal static GameObject? Instantiate(Blueprint blueprint, Transform parent, out string report, IReadOnlyCollection<string>? skip = null)
		{
			//IL_01fd: 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_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Expected O, but got Unknown
			report = string.Empty;
			string name = blueprint.Name;
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				report = "Game prefabs are not loaded yet.";
				return null;
			}
			string key = ((skip == null || skip.Count == 0) ? name : (name + "|" + string.Join(",", skip.OrderBy<string, string>((string x) => x, StringComparer.OrdinalIgnoreCase))));
			if (!Templates.TryGetValue(key, out (GameObject, int, int, List<string>) value) || (Object)(object)value.Item1 == (Object)null)
			{
				if ((Object)(object)_templateHolder == (Object)null)
				{
					_templateHolder = new GameObject("ImmersiveTrader_BlueprintTemplates");
					_templateHolder.SetActive(false);
					Object.DontDestroyOnLoad((Object)(object)_templateHolder);
				}
				Func<string, bool> skip2 = ((skip == null || skip.Count == 0) ? new Func<string, bool>(SkippedByDefault) : ((Func<string, bool>)((string prefab) => SkippedByDefault(prefab) || skip.Contains<string>(prefab, StringComparer.OrdinalIgnoreCase))));
				List<string> missing;
				int item = Build(blueprint, _templateHolder.transform, skip2, out missing);
				GameObject gameObject = ((Component)_templateHolder.transform.GetChild(_templateHolder.transform.childCount - 1)).gameObject;
				value = (gameObject, item, blueprint.Pieces.Count, missing);
				Templates[key] = value;
				if (missing.Count > 0)
				{
					Plugin.Log.LogWarning((object)("Blueprint " + name + ": missing prefabs skipped: " + string.Join(", ", missing)));
				}
			}
			GameObject val = Object.Instantiate<GameObject>(value.Item1, parent, false);
			((Object)val).name = "ImmersiveTrader_Blueprint_" + name;
			val.transform.localPosition = Vector3.zero;
			val.transform.localRotation = Quaternion.identity;
			report = $"{value.Item2}/{value.Item3} pieces" + ((value.Item4.Count > 0) ? ("; missing: " + string.Join(", ", value.Item4)) : string.Empty);
			return val;
		}

		internal static int Build(Blueprint blueprint, Transform parent, Func<string, bool>? skip, out List<string> missing)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: 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_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			if (skip == null)
			{
				skip = SkippedByDefault;
			}
			missing = new List<string>();
			GameObject val = new GameObject("ImmersiveTrader_Blueprint_" + blueprint.Name);
			val.transform.SetParent(parent, false);
			int num = 0;
			int num2 = 0;
			foreach (Piece piece in blueprint.Pieces)
			{
				if (skip(piece.Prefab))
				{
					continue;
				}
				bool flag = blueprint.Name.StartsWith("home-on-stilts-dock", StringComparison.OrdinalIgnoreCase);
				if (flag && piece.Prefab.StartsWith("rug_", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				int damageStage = 0;
				if (blueprint.Name.StartsWith("home-on-stilts-dock", StringComparison.OrdinalIgnoreCase) && !piece.Prefab.StartsWith("wood_pole_log", StringComparison.OrdinalIgnoreCase))
				{
					damageStage = (num2++ & 1) + 1;
				}
				string text2;
				if (flag)
				{
					string prefab = piece.Prefab;
					if (1 == 0)
					{
					}
					string text = ((prefab == "piece_groundtorch") ? "piece_groundtorch_green" : ((!(prefab == "piece_throne01")) ? piece.Prefab : "piece_moose_throne"));
					if (1 == 0)
					{
					}
					text2 = text;
				}
				else
				{
					text2 = piece.Prefab;
				}
				string prefabName = text2;
				GameObject val2 = TraderNoticeBoard.SpawnVisual(prefabName, val.transform, damageStage);
				if ((Object)(object)val2 == (Object)null && StandIns.TryGetValue(piece.Prefab, out string value))
				{
					val2 = TraderNoticeBoard.SpawnVisual(value, val.transform);
				}
				if ((Object)(object)val2 == (Object)null)
				{
					if (!missing.Contains(piece.Prefab))
					{
						missing.Add(piece.Prefab);
					}
					continue;
				}
				val2.transform.localPosition = piece.Position - blueprint.Origin;
				val2.transform.localRotation = piece.Rotation;
				if (flag && piece.Prefab == "piece_throne01")
				{
					TraderSeatMarker[] componentsInChildren = val2.GetComponentsInChildren<TraderSeatMarker>(true);
					foreach (TraderSeatMarker traderSeatMarker in componentsInChildren)
					{
						traderSeatMarker.Prefab = piece.Prefab;
					}
				}
				if (piece.Scale != Vector3.one)
				{
					val2.transform.localScale = Vector3.Scale(val2.transform.localScale, piece.Scale);
				}
				num++;
			}
			return num;
		}

		internal static string Preview(string name, Player player, float distance)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			string error;
			Blueprint blueprint = Load(name, out error);
			if (blueprint == null)
			{
				return error;
			}
			float y = ((Component)player).transform.eulerAngles.y;
			Vector3 val = Quaternion.Euler(0f, y, 0f) * Vector3.forward;
			Vector3 val2 = ((Component)player).transform.position + val * (distance + blueprint.Size.z * 0.5f);
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				val2.y = ZoneSystem.instance.GetGroundHeight(val2);
			}
			GameObject val3 = new GameObject("ImmersiveTrader_BlueprintPreview_" + blueprint.Name);
			val3.transform.SetPositionAndRotation(val2, Quaternion.Euler(0f, y, 0f));
			Instantiate(name, val3.transform, out string report);
			Previews.Add(val3);
			string text = "Blueprint " + blueprint.Name + ": " + report + ", " + $"{blueprint.Size.x:0.#} x {blueprint.Size.z:0.#} m, {blueprint.Size.y:0.#} m high.";
			Plugin.Log.LogInfo((object)text);
			return text;
		}

		internal static int ClearPreviews()
		{
			int num = 0;
			foreach (GameObject preview in Previews)
			{
				if ((Object)(object)preview != (Object)null)
				{
					Object.Destroy((Object)(object)preview);
					num++;
				}
			}
			Previews.Clear();
			return num;
		}

		private static bool TryF(string s, out float value)
		{
			if (s.Length == 0)
			{
				value = 0f;
				return true;
			}
			return float.TryParse(s.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
		}
	}
	internal static class CampBlueprintConfig
	{
		internal sealed record Building(string TraderId, string Blueprint, Vector2 Offset, float RotationY, float LevelMargin, bool AutoPosition = false, bool FaceWater = false)
		{
			internal bool Levels => LevelMargin >= 0f;

			internal float? BaseY { get; init; }

			internal float Crop { get; init; }

			internal Vector2? Entrance { get; init; }

			internal Vector2? EntranceOut { get; init; }

			internal float Step { get; init; } = 3f;

			internal float Side { get; init; }

			internal float FloorHint { get; init; }

			internal Vector2? BoardOffset { get; init; }

			internal float? Cut { get; init; }

			internal IReadOnlyList<Vector4> Exclude { get; init; } = Array.Empty<Vector4>();

			internal bool Foundation { get; init; }

			internal IReadOnlyList<Bounds> Remove { get; init; } = Array.Empty<Bounds>();

			internal bool Settle { get; init; }

			internal bool PavedPaths { get; init; }

			internal bool Greenery { get; init; }

			internal bool NoNature { get; init; }

			internal string? Fence { get; init; }

			internal BlueprintLibrary.Blueprint? Load(out string error)
			{
				//IL_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_0155: Unknown result type (might be due to invalid IL or missing references)
				//IL_015f: Unknown result type (might be due to invalid IL or missing references)
				//IL_013b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0164: 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_0192: Unknown result type (might be due to invalid IL or missing references)
				//IL_0197: Unknown result type (might be due to invalid IL or missing references)
				//IL_0199: Unknown result type (might be due to invalid IL or missing references)
				//IL_019e: 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_01aa: 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_01be: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
				//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
				//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
				//IL_01f8: 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_01ff: 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_0210: 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_0219: Unknown result type (might be due to invalid IL or missing references)
				//IL_021e: 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_023c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0244: Unknown result type (might be due to invalid IL or missing references)
				//IL_024b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0266: Unknown result type (might be due to invalid IL or missing references)
				//IL_0268: Unknown result type (might be due to invalid IL or missing references)
				//IL_027f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0284: Unknown result type (might be due to invalid IL or missing references)
				//IL_0289: Unknown result type (might be due to invalid IL or missing references)
				//IL_0296: Unknown result type (might be due to invalid IL or missing references)
				//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
				//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
				BlueprintLibrary.Blueprint blueprint = BlueprintLibrary.Load(Blueprint, out error, new BlueprintLibrary.Options(BaseY, Crop, Cut, Exclude, Foundation, Remove, Settle, NoNature));
				if (blueprint != null && !string.IsNullOrEmpty(Fence))
				{
					blueprint = CampLayout.WithFence(this, blueprint, Fence);
				}
				if (blueprint != null && Greenery)
				{
					blueprint = CampLayout.WithGreenery(this, blueprint);
				}
				List<Decor> list = ((blueprint == null) ? new List<Decor>() : (from d in DecorFor(TraderId)
					where !BlueprintLibrary.OnBuilding(blueprint, d.Position.x, d.Position.y)
					select d).ToList());
				if (blueprint != null && PavedPaths)
				{
					TraderSpot traderSpot = TraderFor(TraderId);
					Vector3 squareFile = (Vector3)((traderSpot?.Mode == "file") ? new Vector3(traderSpot.Offset.x, 0f, traderSpot.Offset.y) : blueprint.Origin);
					List<BlueprintLibrary.Piece> list2 = new List<BlueprintLibrary.Piece>();
					int num = 0;
					Vector3 val3 = default(Vector3);
					foreach (var (val, val2) in BlueprintLibrary.VillagePaths(blueprint, squareFile))
					{
						((Vector3)(ref val3))..ctor(val2.x - val.x, 0f, val2.z - val.z);
						float magnitude = ((Vector3)(ref val3)).magnitude;
						if (magnitude < 8f)
						{
							continue;
						}
						Vector3 val4 = val3 / magnitude;
						Vector3 val5 = Vector3.Cross(Vector3.up, val4);
						float num2 = 7f;
						while (num2 < magnitude - 4f)
						{
							Vector3 val6 = val + val4 * num2 + val5 * ((num % 2 == 0) ? 2.2f : (-2.2f));
							if (!BlueprintLibrary.OnBuilding(blueprint, val6.x, val6.z))
							{
								list2.Add(new BlueprintLibrary.Piece("piece_dvergr_lantern_pole", Quaternion.LookRotation(-val5 * ((num % 2 == 0) ? 1f : (-1f))), new Vector3(val6.x, blueprint.Origin.y, val6.z), Vector3.one));
							}
							num2 += 14f;
							num++;
						}
					}
					if (list2.Count > 0)
					{
						blueprint = BlueprintLibrary.WithPieces(blueprint, "@lamps" + list2.Count, list2);
					}
				}
				if (blueprint != null && list.Count > 0)
				{
					blueprint = BlueprintLibrary.WithPieces(blueprint, "@decor" + StringExtensionMethods.GetStableHashCode(string.Join("_", list.Select((Decor d) => $"{d.Prefab}{d.Position.x:0.#}{d.Position.y:0.#}"))), list.Select((Decor d) => new BlueprintLibrary.Piece(d.Prefab, Quaternion.Euler(0f, d.RotationY, 0f), new Vector3(d.Position.x, blueprint.Origin.y, d.Position.y), Vector3.one * d.Scale)));
				}
				return blueprint;
			}

			[CompilerGenerated]
			private bool PrintMembers(StringBuilder builder)
			{
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				RuntimeHelpers.EnsureSufficientExecutionStack();
				builder.Append("TraderId = ");
				builder.Append((object?)TraderId);
				builder.Append(", Blueprint = ");
				builder.Append((object?)Blueprint);
				builder.Append(", Offset = ");
				builder.Append(((object)Offset/*cast due to .constrained prefix*/).ToString());
				builder.Append(", RotationY = ");
				builder.Append(RotationY.ToString());
				builder.Append(", LevelMargin = ");
				builder.Append(LevelMargin.ToString());
				builder.Append(", AutoPosition = ");
				builder.Append(AutoPosition.ToString());
				builder.Append(", FaceWater = ");
				builder.Append(FaceWater.ToString());
				return true;
			}
		}

		internal sealed record Altitude(string TraderId, float Min, float Max);

		internal sealed record TraderSpot(string TraderId, string Mode, Vector2 Offset, float? Height, float RotationY, string Base = "front", string? Prefab = null, float Pull = 0f);

		internal sealed record BoardSpot(string TraderId, string Mode, Vector2 Offset, float RotationY, float? Height = null);

		internal sealed record Decor(string TraderId, string Prefab, Vector2 Position, float RotationY, float Scale);

		private static List<Altitude> _altitudes = new List<Altitude>();

		private static Dictionary<string, BiomeArea> _areas = new Dictionary<string, BiomeArea>(StringComparer.OrdinalIgnoreCase);

		private const string FormatMarker = "# format 15";

		private static List<Building>? _buildings;

		private static List<TraderSpot> _traders = new List<TraderSpot>();

		private static List<BoardSpot> _boards = new List<BoardSpot>();

		private static List<BoardSpot> _caravans = new List<BoardSpot>();

		private static List<BoardSpot> _wagons = new List<BoardSpot>();

		private static List<Decor> _decor = new List<Decor>();

		private static Dictionary<string, HashSet<string>> _skips = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

		internal const string DefaultTemplate = "# ImmersiveTrader - trader camp layout (template to paste into)\r\n# format 15\r\n#\r\n# Everything is relative to the camp centre, in metres: x = right, z = forward, rotY in degrees.\r\n#\r\n#   building ; trader ; blueprint ; x ; z ; rotY ; margin\r\n#       blueprint = file name without extension from BepInEx/config/ImmersiveTrader/blueprints\r\n#                   (.vbuild from valheimians.com or .blueprint from PlanBuild)\r\n#       x, z      = where the CENTRE of the building's footprint goes, or auto = moved so that the\r\n#                   trader spot ('inside' / 'front') is the camp centre\r\n#       rotY      = turn in degrees, or water = entrance turned towards the nearest water\r\n#       margin    = flat dirt ground around the building before the terrain slopes gently away,\r\n#                   or none = leave the terrain as it is\r\n#       options (optional, any order):\r\n#         base=11         ground level in the blueprint file (default y 0; foundations go below)\r\n#         crop=60         keep only pieces within 60 m of the centre (very large builds)\r\n#         entrance=x z    the entrance in the blueprint file's coordinates (when it has no outer door);\r\n#         entrance=x z dx dz   ... and the direction out of it (x, z), e.g. -1 0 = out towards -x\r\n#         step=5 side=2   trader spot this far from the entrance / off to the side\r\n#         board=7.5 5     board this far out from the entrance / aside (default 7.5 5)\r\n#         floor=5         an inside trader stands on a floor this high above the ground\r\n#         cut=5           leave out everything below this height of the file (a cellar)\r\n#         exclude=x1 z1 x2 z2   leave out everything inside this rectangle of the file\r\n#         foundation      extend the lowest stone walls 4 m down into the ground\r\n#         fence=stake_wall      a fence around the building with a gap at the entrance\r\n#         fence=a@h+b@h   several pieces per fence segment at these heights (Midka-like yard fence)\r\n#         remove=x1 z1 x2 z2 y1 y2   leave out everything inside this box of the file (one roof, one bench)\r\n#         settle          every separate building goes down onto the ground (a village built on hills)\r\n#         paths           grass stays: dirt only under each building, paved paths between them\r\n#         greenery        vines on the outer walls of the building (a forest house)\r\n#         nature=none     leave out the blueprint's own trees, stones and bushes\r\n#         (crop keeps whole buildings: those whose centre lies within the distance)\r\n#   trader ; trader ; inside | front\r\n#       inside = on a floor 5+ m in from the entrance, facing it; front = step m out from the entrance\r\n#       (the entrance is the building's outer door, or its outermost stairs when it has no door)\r\n#   trader ; trader ; seat ; prefab\r\n#       seated on that seat piece of the building (a chair, a bench, piece_bathtub), with its animation\r\n#       optional: ; x ; z  (the seat of that prefab nearest to it) and ; pull=0.4 (the seat moved back first)\r\n#   decor ; trader ; prefab ; x ; z ; rotY [; scale]\r\n#       an extra piece (a rock, a bush, a tree) on the camp ground, in the blueprint file's coordinates\r\n#   caravan ; trader ; file ; x ; y ; z ; rotY      or      caravan ; trader ; x ; z ; rotY\r\n#       where the trader's Lox caravan stands: it camp caravan (stand there, look\n#       where its head should point); file = blueprint coordinates, else the camp's own frame\r\n#   board ; trader ; file ; x ; y ; z ; rotY\r\n#       exact board spot in file coordinates (it camp board: stand there, look where readers will stand)\r\n#   trader ; trader ; file ; x ; y ; z ; rotY\r\n#       exact spot in the blueprint file's coordinates, the same in every world: stand there and\r\n#       type  it camp trader  (it camp drop  leaves out the building you stand next to)\r\n#   trader ; trader ; x ; y ; z ; rotY\r\n#       exact spot; y = height above the camp ground, or auto = on the floor found there\r\n#   board ; trader ; door\r\n#       beside the path in front of the entrance, facing out\r\n#   board ; trader ; near\r\n#       beside the door nearest to the trader (a trader standing in a village square)\r\n#   altitude ; trader ; min ; max\r\n#       height above the water where the camp may be generated (e.g. low / high mountain slopes)\r\n#   area ; trader ; edge | median | everywhere\r\n#       where in its biome the camp may be generated (edge = the border of the biome)\r\n#   board ; trader ; x ; z ; rotY\r\n#       exact spot (default without a board line: in front of the trader)\r\n#   skip ; trader ; prefab, prefab, ...\r\n#       blueprint pieces left out of this trader's buildings (e.g. a stone floor: dirt instead)\r\n#\r\n# Buildings are local visuals: nothing is saved into the world, they cannot be damaged, built on\r\n# or looted, and doors are left out so doorways stay open. Terrain levelling, the no-build area and\r\n# the camp size apply to camps generated after the change (a new world shows everything).\r\n# Preview anywhere:  it blueprint build <name>     Rebuild camps after editing:  it camp reload\r\n# Lines starting with # are ignored. Decimal separator: dot or comma.\r\n\r\n\r\n# Current saved positions; moved together with their original houses.\r\nbuilding ; midka ; meadows-home ; auto ; auto ; 0 ; 4 ; step=5\r\ntrader   ; midka ; inside\r\nboard    ; midka ; door\r\ncaravan  ; midka ; file ; 3.61 ; 0 ; -5.64 ; 286.55\r\nskip     ; midka ; piece_chest_wood, piece_xmastree\r\nbuilding ; troldad ; arty-mtippi ; auto ; auto ; 0 ; 3 ; step=5.5 ; side=2.5\r\ntrader   ; troldad ; front\r\nboard    ; troldad ; file ; 9.13 ; 0 ; 15.34 ; 20.9\r\ncaravan  ; troldad ; file ; 16.94 ; 0 ; 6.35 ; 23.19\r\nbuilding ; encek ; death-mountain ; auto ; auto ; water ; none ; base=4 ; entrance=-9.5 5 ; step=3 ; side=2.5 ; board=2 6\r\ntrader   ; encek ; file ; -51.09 ; 21.67 ; 10.18 ; 296.19 ; base=front\nboard    ; encek ; file ; -8.08 ; 2.74 ; -13.25 ; 124.84\r\ncaravan  ; encek ; file ; -3.12 ; 3.75 ; 7.81 ; 160.39\r\naltitude ; encek ; 0.5 ; 3.5\r\narea     ; encek ; edge\r\nbuilding ; hrothgar ; forteca ; auto ; auto ; 0 ; 3 ; base=6.5 ; cut=5 ; foundation ; entrance=28.1 26.1 -1 0 ; step=6 ; side=2.5 ; board=10.5 3\r\ntrader   ; hrothgar ; file ; 30.95 ; 6.61 ; 26.13 ; 266.5\r\nboard    ; hrothgar ; file ; 26.18 ; 6.5 ; 14.87 ; 269.5\r\ncaravan  ; hrothgar ; file ; 23.06 ; 6.5 ; 38.3 ; 222.1\r\naltitude ; hrothgar ; 60 ; 100\r\nbuilding ; ylva_frost ; mountainfob ; auto ; auto ; 0 ; 3 ; step=5\r\ntrader   ; ylva_frost ; inside\r\nboard    ; ylva_frost ; door\r\nskip     ; ylva_frost ; piece_chest_wood, piece_chest_barrel\r\naltitude ; ylva_frost ; 110 ; 165\r\nbuilding ; bjarki_goldtooth ; fantasyhouse ; 1.1 ; -1.35 ; 0 ; 4\r\ntrader   ; bjarki_goldtooth ; seat ; piece_chair03 ; -16.95 ; 0.05 ; pull=0.4\r\nboard    ; bjarki_goldtooth ; file ; -1.17 ; 0 ; 9.42 ; 134.45\r\ncaravan  ; bjarki_goldtooth ; file ; -3.3 ; 0 ; -12.23 ; 68.75\r\nbuilding ; ragnar_turnipson ; plains-house ; auto ; auto ; 0 ; 4 ; step=5\r\ntrader   ; ragnar_turnipson ; seat ; piece_bathtub\r\nboard    ; ragnar_turnipson ; file ; 6.83 ; 0 ; -0.58 ; 315.55\r\ncaravan  ; ragnar_turnipson ; file ; 13.48 ; 0 ; 6.83 ; 245.55\r\nwagon    ; midka ; file ; -1.95 ; 0 ; 15.89 ; 188.35\r\nwagon    ; troldad ; file ; 15.11 ; 0.11 ; -3.58 ; 2.85\r\nwagon    ; bjarki_goldtooth ; file ; -9.91 ; 0 ; -18.12 ; 38.95\r\nwagon    ; ragnar_turnipson ; file ; 27.62 ; 0 ; 6.96 ; 283.8\r\nwagon    ; ylva_frost ; file ; -18.93 ; 0 ; -3.3 ; 131.1\r\nwagon    ; hrothgar ; file ; 17.45 ; 6.5 ; 16.07 ; 312.15\r\nskip ; hrothgar ; rune_floor_bal, stone_floor_triangle_bal, stone_platform_bal, vigvisir_floor_bal\r\nbuilding ; rudy_warg ; gumdrop-v2 ; auto ; auto ; 0 ; 8 ; foundation ; step=6\r\ntrader ; rudy_warg ; file ; -0.78 ; 4.22 ; 14.28 ; 187.35 ; base=front\nboard ; rudy_warg ; file ; -6.42 ; 0 ; -1.99 ; 214.45\r\nskip ; rudy_warg ; piece_chest_wood, piece_chest_barrel\r\nbuilding ; grelka ; sorcerer-tower ; auto ; auto ; 0 ; 4 ; foundation ; step=5\r\ntrader ; grelka ; file ; 0 ; 0.89 ; -1.35 ; 31.79\r\nboard ; grelka ; file ; 8.24 ; 0 ; 0.52 ; 43.39\r\nwagon ; grelka ; file ; -7.64 ; 0 ; 5.82 ; 70.84\r\ncaravan ; grelka ; file ; -8.39 ; 0 ; 12.42 ; 103.69\r\nwagon ; rudy_warg ; file ; 10.43 ; 0 ; -1.06 ; 241.15\r\ncaravan ; rudy_warg ; file ; -11.81 ; 0 ; 4.72 ; 175.95\r\n# camps coastal revision 20260929\r\nbuilding ; grimvald ; small-lighthouse ; 0 ; 0 ; water ; none ; entrance=3.15 -1.28 0.924 -0.383 ; step=3\r\ntrader ; grimvald ; file ; 0.89 ; 0.46 ; 1.24 ; 150.11 ; base=front\nboard ; grimvald ; file ; 0.23 ; -0.49 ; -6.03 ; 140.56\ncaravan ; grimvald ; file ; 4.85 ; -0.32 ; 5.34 ; 153.91\naltitude ; grimvald ; 1 ; 3\r\nbuilding ; cmok ; gjall-hunter-keep ; 0 ; 0 ; 0 ; 4 ; base=-0.1 ; entrance=-7.9 -4.49 0.924 0.383 ; step=5\ntrader ; cmok ; file ; -14.4 ; 11.29 ; -5.84 ; 258.69 ; base=front\nboard ; cmok ; door\r\nbuilding ; spalony_zenek ; ashen-spire ; 0 ; 0 ; 0 ; 4 ; base=-1.5 ; entrance=-12.6 5.5 -0.924 0.383 ; step=5\ntrader ; spalony_zenek ; inside\r\nboard ; spalony_zenek ; file ; -8.18 ; -2.29 ; 9.03 ; 325.41\ncaravan ; spalony_zenek ; file ; -13.68 ; -2.5 ; -5.01 ; 341.31\nbuilding ; skjold_cinderborn ; barber-shop ; 0 ; 0 ; 0 ; 4 ; base=-0.5 ; entrance=-0.33 -0.47 0.707 0.707\ntrader ; skjold_cinderborn ; file ; -7.12 ; 10.93 ; -3.98 ; 183.1 ; base=front\nboard ; skjold_cinderborn ; file ; 3 ; -0.5 ; -2 ; 45\r\ncaravan ; skjold_cinderborn ; file ; -9.46 ; -0.5 ; 4.09 ; 109.6\nskip ; skjold_cinderborn ; piece_barber, wood_floor, fire_pit_iron\nbuilding ; mokra_dzika ; home-on-stilts-dock ; 0 ; 0 ; 0 ; none ; base=0.8 ; entrance=6 0 0 -1\ntrader ; mokra_dzika ; seat ; piece_throne01\r\nboard ; mokra_dzika ; file ; 6.28 ; 1.05 ; -2.48 ; 151.3\ncaravan ; mokra_dzika ; file ; 20.77 ; 1.05 ; 3.36 ; 231.6\n# camps land-and-cellar revision 20260930\n# camps mokra-water revision 20260930\n";

		internal static string FilePath => Path.Combine(Paths.ConfigPath, "ImmersiveTrader-camps.cfg");

		internal static IReadOnlyList<Building> Buildings
		{
			get
			{
				Load();
				return _buildings;
			}
		}

		internal static BiomeArea? AreaFor(string traderId)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			Load();
			BiomeArea value;
			return _areas.TryGetValue(traderId, out value) ? new BiomeArea?(value) : ((BiomeArea?)null);
		}

		internal static Altitude? AltitudeFor(string traderId)
		{
			Load();
			return _altitudes.LastOrDefault((Altitude a) => a.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static IEnumerable<Building> For(string traderId)
		{
			return Buildings.Where((Building b) => b.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static TraderSpot? TraderFor(string traderId)
		{
			Load();
			return _traders.LastOrDefault((TraderSpot t) => t.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static IEnumerable<Decor> DecorFor(string traderId)
		{
			Load();
			return _decor.Where((Decor d) => d.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static BoardSpot? CaravanFor(string traderId)
		{
			Load();
			return _caravans.LastOrDefault((BoardSpot b) => b.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static BoardSpot? WagonFor(string traderId)
		{
			Load();
			return _wagons.LastOrDefault((BoardSpot b) => b.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static BoardSpot? BoardFor(string traderId)
		{
			Load();
			return _boards.LastOrDefault((BoardSpot b) => b.TraderId.Equals(traderId, StringComparison.OrdinalIgnoreCase));
		}

		internal static IReadOnlyCollection<string> SkipFor(string traderId)
		{
			Load();
			IReadOnlyCollection<string> result;
			if (!_skips.TryGetValue(traderId, out HashSet<string> value))
			{
				IReadOnlyCollection<string> readOnlyCollection = (IReadOnlyCollection<string>)(object)Array.Empty<string>();
				result = readOnlyCollection;
			}
			else
			{
				IReadOnlyCollection<string> readOnlyCollection = value;
				result = readOnlyCollection;
			}
			return result;
		}

		internal static bool TryFootprint(Building building, out Vector3 centre, out Vector2 halfSize)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			centre = new Vector3(building.Offset.x, 0f, building.Offset.y);
			halfSize = Vector2.zero;
			string error;
			BlueprintLibrary.Blueprint blueprint = building.Load(out error);
			if (blueprint == null)
			{
				return false;
			}
			halfSize = new Vector2(blueprint.Size.x * 0.5f + 1.5f, blueprint.Size.z * 0.5f + 1.5f);
			return true;
		}

		internal static void Reload()
		{
			_buildings = null;
		}

		private static void Load()
		{
			//IL_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_09df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ab7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bf2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ac7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c8c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ac2: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0532: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d0d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b3c: Unknown result type (might be due to invalid IL or missing references)
			//IL_063a: Unknown result type (might be due to invalid IL or missing references)
			//IL_065d: Unknown result type (might be due to invalid IL or missing references)
			//IL_067d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0697: Unknown result type (might be due to invalid IL or missing references)
			//IL_0daf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e30: Unknown result type (might be due to invalid IL or missing references)
			//IL_072b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ed2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0fad: Unknown result type (might be due to invalid IL or missing references)
			//IL_1058: Unknown result type (might be due to invalid IL or missing references)
			//IL_1193: Unknown result type (might be due to invalid IL or missing references)
			//IL_121a: Unknown result type (might be due to invalid IL or missing references)
			if (_buildings != null)
			{
				return;
			}
			_buildings = new List<Building>();
			_traders = new List<TraderSpot>();
			_boards = new List<BoardSpot>();
			_caravans = new List<BoardSpot>();
			_wagons = new List<BoardSpot>();
			_decor = new List<Decor>();
			_skips = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
			_altitudes = new List<Altitude>();
			_areas = new Dictionary<string, BiomeArea>(StringComparer.OrdinalIgnoreCase);
			string[] lines;
			try
			{
				Directory.CreateDirectory(Paths.ConfigPath);
				if (File.Exists(FilePath) && !File.ReadAllText(FilePath).Contains("# format 15"))
				{
					string text = FilePath + ".old.bak";
					File.Copy(FilePath, text, overwrite: true);
					File.Delete(FilePath);
					Plugin.Log.LogInfo((object)("Camp layout upgraded; previous file kept as " + text + "."));
				}
				if (!File.Exists(FilePath))
				{
					File.WriteAllText(FilePath, "# ImmersiveTrader - trader camp layout (template to paste into)\r\n# format 15\r\n#\r\n# Everything is relative to the camp centre, in metres: x = right, z = forward, rotY in degrees.\r\n#\r\n#   building ; trader ; blueprint ; x ; z ; rotY ; margin\r\n#       blueprint = file name without extension from BepInEx/config/ImmersiveTrader/blueprints\r\n#                   (.vbuild from valheimians.com or .blueprint from PlanBuild)\r\n#       x, z      = where the CENTRE of the building's footprint goes, or auto = moved so that the\r\n#                   trader spot ('inside' / 'front') is the camp centre\r\n#       rotY      = turn in degrees, or water = entrance turned towards the nearest water\r\n#       margin    = flat dirt ground around the building before the terrain slopes gently away,\r\n#                   or none = leave the terrain as it is\r\n#       options (optional, any order):\r\n#         base=11         ground level in the blueprint file (default y 0; foundations go below)\r\n#         crop=60         keep only pieces within 60 m of the centre (very large builds)\r\n#         entrance=x z    the entrance in the blueprint file's coordinates (when it has no outer door);\r\n#         entrance=x z dx dz   ... and the direction out of it (x, z), e.g. -1 0 = out towards -x\r\n#         step=5 side=2   trader spot this far from the entrance / off to the side\r\n#         board=7.5 5     board this far out from the entrance / aside (default 7.5 5)\r\n#         floor=5         an inside trader stands on a floor this high above the ground\r\n#         cut=5           leave out everything below this height of the file (a cellar)\r\n#         exclude=x1 z1 x2 z2   leave out everything inside this rectangle of the file\r\n#         foundation      extend the lowest stone walls 4 m down into the ground\r\n#         fence=stake_wall      a fence around the building with a gap at the entrance\r\n#         fence=a@h+b@h   several pieces per fence segment at these heights (Midka-like yard fence)\r\n#         remove=x1 z1 x2 z2 y1 y2   leave out everything inside this box of the file (one roof, one bench)\r\n#         settle          every separate building goes down onto the ground (a village built on hills)\r\n#         paths           grass stays: dirt only under each building, paved paths between them\r\n#         greenery        vines on the outer walls of the building (a forest house)\r\n#         nature=none     leave out the blueprint's own trees, stones and bushes\r\n#         (crop keeps whole buildings: those whose centre lies within the distance)\r\n#   trader ; trader ; inside | front\r\n#       inside = on a floor 5+ m in from the entrance, facing it; front = step m out from the entrance\r\n#       (the entrance is the building's outer door, or its outermost stairs when it has no door)\r\n#   trader ; trader ; seat ; prefab\r\n#       seated on that seat piece of the building (a chair, a bench, piece_bathtub), with its animation\r\n#       optional: ; x ; z  (the seat of that prefab nearest to it) and ; pull=0.4 (the seat moved back first)\r\n#   decor ; trader ; prefab ; x ; z ; rotY [; scale]\r\n#       an extra piece (a rock, a bush, a tree) on the camp ground, in the blueprint file's coordinates\r\n#   caravan ; trader ; file ; x ; y ; z ; rotY      or      caravan ; trader ; x ; z ; rotY\r\n#       where the trader's Lox caravan stands: it camp caravan (stand there, look\n#       where its head should point); file = blueprint coordinates, else the camp's own frame\r\n#   board ; trader ; file ; x ; y ; z ; rotY\r\n#       exact board spot in file coordinates (it camp board: stand there, look where readers will stand)\r\n#   trader ; trader ; file ; x ; y ; z ; rotY\r\n#       exact spot in the blueprint file's coordinates, the same in every world: stand there and\r\n#       type  it camp trader  (it camp drop  leaves out the building you stand next to)\r\n#   trader ; trader ; x ; y ; z ; rotY\r\n#       exact spot; y = height above the camp ground, or auto = on the floor found there\r\n#   board ; trader ; door\r\n#       beside the path in front of the entrance, facing out\r\n#   board ; trader ; near\r\n#       beside the door nearest to the trader (a trader standing in a village square)\r\n#   altitude ; trader ; min ; max\r\n#       height above the water where the camp may be generated (e.g. low / high mountain slopes)\r\n#   area ; trader ; edge | median | everywhere\r\n#       where in its biome the camp may be generated (edge = the border of the biome)\r\n#   board ; trader ; x ; z ; rotY\r\n#       exact spot (default without a board line: in front of the trader)\r\n#   skip ; trader ; prefab, prefab, ...\r\n#       blueprint pieces left out of this trader's buildings (e.g. a stone floor: dirt instead)\r\n#\r\n# Buildings are local visuals: nothing is saved into the world, they cannot be damaged, built on\r\n# or looted, and doors are left out so doorways stay open. Terrain levelling, the no-build area and\r\n# the camp size apply to camps generated after the change (a new world shows everything).\r\n# Preview anywhere:  it blueprint build <name>     Rebuild camps after editing:  it camp reload\r\n# Lines starting with # are ignored. Decimal separator: dot or comma.\r\n\r\n\r\n# Current saved positions; moved together with their original houses.\r\nbuilding ; midka ; meadows-home ; auto ; auto ; 0 ; 4 ; step=5\r\ntrader   ; midka ; inside\r\nboard    ; midka ; door\r\ncaravan  ; midka ; file ; 3.61 ; 0 ; -5.64 ; 286.55\r\nskip     ; midka ; piece_chest_wood, piece_xmastree\r\nbuilding ; troldad ; arty-mtippi ; auto ; auto ; 0 ; 3 ; step=5.5 ; side=2.5\r\ntrader   ; troldad ; front\r\nboard    ; troldad ; file ; 9.13 ; 0 ; 15.34 ; 20.9\r\ncaravan  ; troldad ; file ; 16.94 ; 0 ; 6.35 ; 23.19\r\nbuilding ; encek ; death-mountain ; auto ; auto ; water ; none ; base=4 ; entrance=-9.5 5 ; step=3 ; side=2.5 ; board=2 6\r\ntrader   ; encek ; file ; -51.09 ; 21.67 ; 10.18 ; 296.19 ; base=front\nboard    ; encek ; file ; -8.08 ; 2.74 ; -13.25 ; 124.84\r\ncaravan  ; encek ; file ; -3.12 ; 3.75 ; 7.81 ; 160.39\r\naltitude ; encek ; 0.5 ; 3.5\r\narea     ; encek ; edge\r\nbuilding ; hrothgar ; forteca ; auto ; auto ; 0 ; 3 ; base=6.5 ; cut=5 ; foundation ; entrance=28.1 26.1 -1 0 ; step=6 ; side=2.5 ; board=10.5 3\r\ntrader   ; hrothgar ; file ; 30.95 ; 6.61 ; 26.13 ; 266.5\r\nboard    ; hrothgar ; file ; 26.18 ; 6.5 ; 14.87 ; 269.5\r\ncaravan  ; hrothgar ; file ; 23.06 ; 6.5 ; 38.3 ; 222.1\r\naltitude ; hrothgar ; 60 ; 100\r\nbuilding ; ylva_frost ; mountainfob ; auto ; auto ; 0 ; 3 ; step=5\r\ntrader   ; ylva_frost ; inside\r\nboard    ; ylva_frost ; door\r\nskip     ; ylva_frost ; piece_chest_wood, piece_chest_barrel\r\naltitude ; ylva_frost ; 110 ; 165\r\nbuilding ; bjarki_goldtooth ; fantasyhouse ; 1.1 ; -1.35 ; 0 ; 4\r\ntrader   ; bjarki_goldtooth ; seat ; piece_chair03 ; -16.95 ; 0.05 ; pull=0.4\r\nboard    ; bjarki_goldtooth ; file ; -1.17 ; 0 ; 9.42 ; 134.45\r\ncaravan  ; bjarki_goldtooth ; file ; -3.3 ; 0 ; -12.23 ; 68.75\r\nbuilding ; ragnar_turnipson ; plains-house ; auto ; auto ; 0 ; 4 ; step=5\r\ntrader   ; ragnar_turnipson ; seat ; piece_bathtub\r\nboard    ; ragnar_turnipson ; file ; 6.83 ; 0 ; -0.58 ; 315.55\r\ncaravan  ; ragnar_turnipson ; file ; 13.48 ; 0 ; 6.83 ; 245.55\r\nwagon    ; midka ; file ; -1.95 ; 0 ; 15.89 ; 188.35\r\nwagon    ; troldad ; file ; 15.11 ; 0.11 ; -3.58 ; 2.85\r\nwagon    ; bjarki_goldtooth ; file ; -9.91 ; 0 ; -18.12 ; 38.95\r\nwagon    ; ragnar_turnipson ; file ; 27.62 ; 0 ; 6.96 ; 283.8\r\nwagon    ; ylva_frost ; file ; -18.93 ; 0 ; -3.3 ; 131.1\r\nwagon    ; hrothgar ; file ; 17.45 ; 6.5 ; 16.07 ; 312.15\r\nskip ; hrothgar ; rune_floor_bal, stone_floor_triangle_bal, stone_platform_bal, vigvisir_floor_bal\r\nbuilding ; rudy_warg ; gumdrop-v2 ; auto ; auto ; 0 ; 8 ; foundation ; step=6\r\ntrader ; rudy_warg ; file ; -0.78 ; 4.22 ; 14.28 ; 187.35 ; base=front\nboard ; rudy_warg ; file ; -6.42 ; 0 ; -1.99 ; 214.45\r\nskip ; rudy_warg ; piece_chest_wood, piece_chest_barrel\r\nbuilding ; grelka ; sorcerer-tower ; auto ; auto ; 0 ; 4 ; foundation ; step=5\r\ntrader ; grelka ; file ; 0 ; 0.89 ; -1.35 ; 31.79\r\nboard ; grelka ; file ; 8.24 ; 0 ; 0.52 ; 43.39\r\nwagon ; grelka ; file ; -7.64 ; 0 ; 5.82 ; 70.84\r\ncaravan ; grelka ; file ; -8.39 ; 0 ; 12.42 ; 103.69\r\nwagon ; rudy_warg ; file ; 10.43 ; 0 ; -1.06 ; 241.15\r\ncaravan ; rudy_warg ; file ; -11.81 ; 0 ; 4.72 ; 175.95\r\n# camps coastal revision 20260929\r\nbuilding ; grimvald ; small-lighthouse ; 0 ; 0 ; water ; none ; entrance=3.15 -1.28 0.924 -0.383 ; step=3\r\ntrader ; grimvald ; file ; 0.89 ; 0.46 ; 1.24 ; 150.11 ; base=front\nboard ; grimvald ; file ; 0.23 ; -0.49 ; -6.03 ; 140.56\ncaravan ; grimvald ; file ; 4.85 ; -0.32 ; 5.34 ; 153.91\naltitude ; grimvald ; 1 ; 3\r\nbuilding ; cmok ; gjall-hunter-keep ; 0 ; 0 ; 0 ; 4 ; base=-0.1 ; entrance=-7.9 -4.49 0.924 0.383 ; step=5\ntrader ; cmok ; file ; -14.4 ; 11.29 ; -5.84 ; 258.69 ; base=front\nboard ; cmok ; door\r\nbuilding ; spalony_zenek ; ashen-spire ; 0 ; 0 ; 0 ; 4 ; base=-1.5 ; entrance=-12.6 5.5 -0.924 0.383 ; step=5\ntrader ; spalony_zenek ; inside\r\nboard ; spalony_zenek ; file ; -8.18 ; -2.29 ; 9.03 ; 325.41\ncaravan ; spalony_zenek ; file ; -13.68 ; -2.5 ; -5.01 ; 341.31\nbuilding ; skjold_cinderborn ; barber-shop ; 0 ; 0 ; 0 ; 4 ; base=-0.5 ; entrance=-0.33 -0.47 0.707 0.707\ntrader ; skjold_cinderborn ; file ; -7.12 ; 10.93 ; -3.98 ; 183.1 ; base=front\nboard ; skjold_cinderborn ; file ; 3 ; -0.5 ; -2 ; 45\r\ncaravan ; skjold_cinderborn ; file ; -9.46 ; -0.5 ; 4.09 ; 109.6\nskip ; skjold_cinderborn ; piece_barber, wood_floor, fire_pit_iron\nbuilding ; mokra_dzika ; home-on-stilts-dock ; 0 ; 0 ; 0 ; none ; base=0.8 ; entrance=6 0 0 -1\ntrader ; mokra_dzika ; seat ; piece_throne01\r\nboard ; mokra_dzika ; file ; 6.28 ; 1.05 ; -2.48 ; 151.3\ncaravan ; mokra_dzika ; file ; 20.77 ; 1.05 ; 3.36 ; 231.6\n# camps land-and-cellar revision 20260930\n# camps mokra-water revision 20260930\n");
				}
				lines = File.ReadAllLines(FilePath);
				lines = CampLayoutUpgrade.Apply(lines, FilePath);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Camp layout could not be read (" + FilePath + "): " + ex.Message));
				lines = "# ImmersiveTrader - trader camp layout (template to paste into)\r\n# format 15\r\n#\r\n# Everything is relative to the camp centre, in metres: x = right, z = forward, rotY in degrees.\r\n#\r\n#   building ; trader ; blueprint ; x ; z ; rotY ; margin\r\n#       blueprint = file name without extension from BepInEx/config/ImmersiveTrader/blueprints\r\n#                   (.vbuild from valheimians.com or .blueprint from PlanBuild)\r\n#       x, z      = where the CENTRE of the building's footprint goes, or auto = moved so that the\r\n#                   trader spot ('inside' / 'front') is the camp centre\r\n#       rotY      = turn in degrees, or water = entrance turned towards the nearest water\r\n#       margin    = flat dirt ground around the building before the terrain slopes gently away,\r\n#                   or none = leave the terrain as it is\r\n#       options (optional, any order):\r\n#         base=11         ground level in the blueprint file (default y 0; foundations go below)\r\n#         crop=60         keep only pieces within 60 m of the centre (very large builds)\r\n#         entrance=x z    the entrance in the blueprint file's coordinates (when it has no outer door);\r\n#         entrance=x z dx dz   ... and the direction out of it (x, z), e.g. -1 0 = out towards -x\r\n#         step=5 side=2   trader spot this far from the entrance / off to the side\r\n#         board=7.5 5     board this far out from the entrance / aside (default 7.5 5)\r\n#         floor=5         an inside trader stands on a floor this high above the ground\r\n#         cut=5           leave out everything below this height of the file (a cellar)\r\n#         exclude=x1 z1 x2 z2   leave out everything inside this rectangle of the file\r\n#         foundation      extend the lowest stone walls 4 m down into the ground\r\n#         fence=stake_wall      a fence around the building with a gap at the entrance\r\n#         fence=a@h+b@h   several pieces per fence segment at these heights (Midka-like yard fence)\r\n#         remove=x1 z1 x2 z2 y1 y2   leave out everything inside this box of the file (one roof, one bench)\r\n#         settle          every separate building goes down onto the ground (a village built on hills)\r\n#         paths           grass stays: dirt only under each building, paved paths between them\r\n#         greenery        vines on the outer walls of the building (a forest house)\r\n#         nature=none     leave out the blueprint's own trees, stones and bushes\r\n#         (crop keeps whole buildings: those whose centre lies within the distance)\r\n#   trader ; trader ; inside | front\r\n#       inside = on a floor 5+ m in from the entrance, facing it; front = step m out from the entrance\r\n#       (the entrance is the building's outer door, or its outermost stairs when it has no door)\r\n#   trader ; trader ; seat ; prefab\r\n#       seated on that seat piece of the building (a chair, a bench, piece_bathtub), with its animation\r\n#       optional: ; x ; z  (the seat of that prefab nearest to it) and ; pull=0.4 (the seat moved back first)\r\n#   decor ; trader ; prefab ; x ; z ; rotY [; scale]\r\n#       an extra piece (a rock, a bush, a tree) on the camp ground, in the blueprint file's coordinates\r\n#   caravan ; trader ; file ; x ; y ; z ; rotY      or      caravan ; trader ; x ; z ; rotY\r\n#       where the trader's Lox caravan stands: it camp caravan (stand there, look\n#       where its head should point); file = blueprint coordinates, else the camp's own frame\r\n#   board ; trader ; file ; x ; y ; z ; rotY\r\n#       exact board spot in file coordinates (it camp board: stand there, look where readers will stand)\r\n#   trader ; trader ; file ; x ; y ; z ; rotY\r\n#       exact spot in the blueprint file's coordinates, the same in every world: stand there and\r\n#       type  it camp trader  (it camp drop  leaves out the building you stand next to)\r\n#   trader ; trader ; x ; y ; z ; rotY\r\n#       exact spot; y = height above the camp ground, or auto = on the floor found there\r\n#   board ; trader ; door\r\n#       beside the path in front of the entrance, facing out\r\n#   board ; trader ; near\r\n#       beside the door nearest to the trader (a trader standing in a village square)\r\n#   altitude ; trader ; min ; max\r\n#       height above the water where the camp may be generated (e.g. low / high mountain slopes)\r\n#   area ; trader ; edge | median | everywhere\r\n#       where in its biome the camp may be generated (edge = the border of the biome)\r\n#   board ; trader ; x ; z ; rotY\r\n#       exact spot (default without a board line: in front of the trader)\r\n#   skip ; trader ; prefab, prefab, ...\r\n#       blueprint pieces left out of this trader's buildings (e.g. a stone floor: dirt instead)\r\n#\r\n# Buildings are local visuals: nothing is saved into the world, they cannot be damaged, built on\r\n# or looted, and doors are left out so doorways stay open. Terrain levelling, the no-build area and\r\n# the camp size apply to camps generated after the change (a new world shows everything).\r\n# Preview anywhere:  it blueprint build <name>     Rebuild camps after editing:  it camp reload\r\n# Lines starting with # are ignored. Decimal separator: dot or comma.\r\n\r\n\r\n# Current saved positions; moved together with their original houses.\r\nbuilding ; midka ; meadows-home ; auto ; auto ; 0 ; 4 ; step=5\r\ntrader   ; midka ; inside\r\nboard    ; midka ; door\r\ncaravan  ; midka ; file ; 3.61 ; 0 ; -5.64 ; 286.55\r\nskip     ; midka ; piece_chest_wood, piece_xmastree\r\nbuilding ; troldad ; arty-mtippi ; auto ; auto ; 0 ; 3 ; step=5.5 ; side=2.5\r\ntrader   ; troldad ; front\r\nboard    ; troldad ; file ; 9.13 ; 0 ; 15.34 ; 20.9\r\ncaravan  ; troldad ; file ; 16.94 ; 0 ; 6.35 ; 23.19\r\nbuilding ; encek ; death-mountain ; auto ; auto ; water ; none ; base=4 ; entrance=-9.5 5 ; step=3 ; side=2.5 ; board=2 6\r\ntrader   ; encek ; file ; -51.09 ; 21.67 ; 10.18 ; 296.19 ; base=front\nboard    ; encek ; file ; -8.08 ; 2.74 ; -13.25 ; 124.84\r\ncaravan  ; encek ; file ; -3.12 ; 3.75 ; 7.81 ; 160.39\r\nalti