Decompiled source of BoatTweaks v0.3.0

plugins/BoatTweaks.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Configuration;
using BoatTweaks.Logic;
using BoatTweaks.Scene;
using BoatTweaks.Settings;
using CatLib.Config;
using CatLib.Core;
using CatLib.DevTools;
using CatLib.Game.Events;
using CatLib.Il2Cpp;
using CatLib.Localization;
using CatLib.Logging;
using CatLib.Net;
using CatLib.UI;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Reflection;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CatLib contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+4162013e7c932bfa1cf0340558d83186f1fd5817")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("BoatTweaks")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.0.0")]
[module: UnverifiableCode]
[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;
		}
	}
}
namespace BoatTweaks
{
	public sealed class BoatController
	{
		private sealed record Pool(string Key, DelivererStoresData Data, Il2CppReferenceArray<EntityInteractableStore> OriginalArray, List<EntityInteractableStore> Prefabs);

		private sealed class PrefabState
		{
			public EntityInteractableStore Prefab { get; }

			public HashSet<(int Row, int Column)> Arriving { get; }

			public string Key { get; }

			public float Approved { get; }

			public float Maximum { get; }

			public List<(GameObject Object, bool WasActive)> Blockers { get; } = new List<(GameObject, bool)>();

			public List<(GameObject Object, bool WasActive)> Props { get; } = new List<(GameObject, bool)>();

			public int BlockerCount => Blockers.Count;

			public int PropCount => Props.Count;

			public int EntityObjects { get; set; }

			public PrefabState(EntityInteractableStore prefab, string key, float approved, float maximum, HashSet<(int Row, int Column)> arriving)
			{
				Arriving = arriving;
				Prefab = prefab;
				Key = key;
				Approved = approved;
				Maximum = maximum;
			}

			public void Restore()
			{
				if (!((Object)(object)Prefab == (Object)null) && !((Il2CppObjectBase)Prefab).WasCollected)
				{
					Prefab._StorageMaximumApprovedHeight_k__BackingField = Approved;
					Prefab._StorageMaximumHeight_k__BackingField = Maximum;
					SetDeckCleared(cleared: false);
				}
			}

			public void SetDeckCleared(bool cleared)
			{
				foreach (var (val, flag) in Blockers.Concat(Props))
				{
					if (!((Object)(object)val == (Object)null))
					{
						bool flag2 = !cleared && flag;
						if (val.activeSelf != flag2)
						{
							val.SetActive(flag2);
						}
					}
				}
			}
		}

		public const double ScanIntervalSeconds = 1.0;

		public const double DecisionDelaySeconds = 30.0;

		public const double BuildTimeoutSeconds = 20.0;

		public const string FallbackPoolKey = "fallback";

		private readonly CatLogger _log;

		private string _lastCatalog;

		private readonly TextCatalog _texts;

		private readonly PatternLibrary _library;

		private readonly DeckBuilder _builder;

		private readonly DeckEnforcer _enforcer;

		private readonly Random _random = new Random();

		private readonly Dictionary<IntPtr, Pool> _pools = new Dictionary<IntPtr, Pool>();

		private readonly Dictionary<IntPtr, PrefabState> _prefabs = new Dictionary<IntPtr, PrefabState>();

		private readonly Dictionary<string, int> _lastVariant = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, LayoutPlan> _plans = new Dictionary<string, LayoutPlan>(StringComparer.Ordinal);

		private readonly InstanceTracker _manager = new InstanceTracker();

		private bool _pruneAfterCollect;

		private bool _needDecision = true;

		private double _decideAt = double.PositiveInfinity;

		private int _sequence;

		private string _lastLayout;

		private string _lastNote;

		private string _planText;

		private DeckPlan _plan = DeckPlan.Game;

		private EntityInteractableStore _pendingStore;

		private DeckPlan _pendingPlan;

		private double _pendingUntil;

		private int _blockerLayer = -2;

		private IntPtr _lastBoatStore;

		private bool _dirty = true;

		private double _nextScan;

		private string _lastSummary;

		private bool _suspended;

		private bool _wasActive = true;

		private readonly HashSet<string> _freedReported = new HashSet<string>(StringComparer.Ordinal);

		public BoatSettings Settings { get; set; }

		public IReadOnlyDictionary<string, LayoutPlan> Plans => _plans;

		public bool IsAuthority
		{
			get
			{
				if (Settings != null)
				{
					return CatNetwork.IsAuthority;
				}
				return false;
			}
		}

		public bool IsActive => CatNetwork.IsActive("catlib.boattweaks");

		public LayoutMode EffectiveMode
		{
			get
			{
				if (!IsActive)
				{
					return LayoutMode.Game;
				}
				return Settings.Mode.Value;
			}
		}

		public float ApprovedScale
		{
			get
			{
				if (!IsActive)
				{
					return 1f;
				}
				return Settings.ApprovedHeightScale.Value;
			}
		}

		public float MaximumScale
		{
			get
			{
				if (!IsActive)
				{
					return 1f;
				}
				return Settings.MaximumHeightScale.Value;
			}
		}

		public DeckPlan CurrentPlan
		{
			get
			{
				if (!IsActive || (!IsAuthority && !Settings.Plan.IsOverridden))
				{
					return DeckPlan.Game;
				}
				string value = Settings.Plan.Value;
				if (value != _planText)
				{
					_planText = value;
					_plan = DeckPlan.Decode(value);
				}
				return _plan;
			}
		}

		private int EntityObjects => _prefabs.Values.Sum((PrefabState state) => state.EntityObjects);

		public BoatController(CatLogger log, TextCatalog texts, PatternLibrary library)
		{
			_log = log;
			_texts = texts;
			_library = library;
			_builder = new DeckBuilder(log);
			_enforcer = new DeckEnforcer(log);
		}

		public void RequestApply()
		{
			_dirty = true;
		}

		public void RequestDecision()
		{
			_needDecision = true;
			_dirty = true;
		}

		public void OnActiveModsChanged()
		{
			bool isActive = IsActive;
			if (isActive != _wasActive)
			{
				_wasActive = isActive;
				_log.Info(isActive ? "Boat Tweaks is active again in this session" : "Boat Tweaks is paused in this session because not every player has it, boats come like in the game");
				RequestDecision();
			}
		}

		public void SuspendForRestart()
		{
			_suspended = true;
			_pendingStore = null;
			_enforcer.Clear();
			_log.Info("The game is restarting, the boats are left alone until the next level");
		}

		public void OnLevelLoadStarted()
		{
			_suspended = false;
			_manager.Forget();
			_freedReported.Clear();
		}

		public void Update()
		{
			if (Settings == null || _suspended)
			{
				return;
			}
			try
			{
				TrackManager();
				_enforcer.Update();
				HandleHotkey();
				BuildPending();
				if (_needDecision || FrameLoop.Realtime >= _decideAt)
				{
					Decide();
				}
				if (_dirty || !(FrameLoop.Realtime < _nextScan))
				{
					_nextScan = FrameLoop.Realtime + 1.0;
					Scan();
				}
			}
			catch (Exception ex)
			{
				_log.Error("Updating the boat failed", ex);
				_nextScan = FrameLoop.Realtime + 10.0;
			}
		}

		private void TrackManager()
		{
			ParcelDeliveryManager val = (Singleton<ParcelDeliveryManager>.HasInstance() ? Singleton<ParcelDeliveryManager>.Instance : null);
			if (!_manager.Observe(((Object)(object)val == (Object)null) ? IntPtr.Zero : ((Il2CppObjectBase)val).Pointer))
			{
				return;
			}
			_pools.Clear();
			_lastBoatStore = IntPtr.Zero;
			_dirty = true;
			_pruneAfterCollect = true;
			_needDecision = true;
			_pendingStore = null;
			_enforcer.Clear();
			if (!((Object)(object)val == (Object)null))
			{
				EntityInteractableStore val2 = CurrentStore(val);
				if ((Object)(object)val2 != (Object)null && EffectiveMode != LayoutMode.Game)
				{
					_log.Warning("The boat " + KeyOf(val2) + " was chosen before Boat Tweaks could apply its settings to this level");
				}
				else
				{
					_log.Info("New level, applying boat settings before the first boat");
				}
			}
		}

		private void Scan()
		{
			if (!Singleton<ParcelDeliveryManager>.HasInstance())
			{
				_lastBoatStore = IntPtr.Zero;
				return;
			}
			ParcelDeliveryManager instance = Singleton<ParcelDeliveryManager>.Instance;
			List<Pool> list = CollectPools(instance);
			if (list.Count == 0)
			{
				return;
			}
			if (_pruneAfterCollect)
			{
				_pruneAfterCollect = false;
				Prune(list);
			}
			EntityInteractableStore val = CurrentStore(instance);
			IntPtr intPtr = (((Object)(object)val == (Object)null) ? IntPtr.Zero : ((Il2CppObjectBase)val).Pointer);
			if (intPtr != _lastBoatStore)
			{
				_lastBoatStore = intPtr;
				if ((Object)(object)val != (Object)null)
				{
					OnBoatArrived(val, list);
				}
				_dirty = true;
			}
			if (_dirty)
			{
				_dirty = false;
				Apply(list);
				ApplyHeightToScene();
			}
		}

		private void ApplyHeightToScene()
		{
			Dictionary<string, PrefabState> dictionary = (from state in _prefabs.Values
				group state by state.Key).ToDictionary<IGrouping<string, PrefabState>, string, PrefabState>((IGrouping<string, PrefabState> group) => group.Key, (IGrouping<string, PrefabState> group) => group.First(), StringComparer.Ordinal);
			foreach (EntityInteractableStore item in Object.FindObjectsOfType<EntityInteractableStore>())
			{
				if (!((Object)(object)item == (Object)null) && dictionary.TryGetValue(KeyOf(item), out var value))
				{
					float storageMaximumApprovedHeight_k__BackingField = item._StorageMaximumApprovedHeight_k__BackingField;
					ApplyHeight(item, value);
					MoveApprovedVisual(item, storageMaximumApprovedHeight_k__BackingField, item._StorageMaximumApprovedHeight_k__BackingField);
					item.UpdateApprovedHeight();
				}
			}
		}

		private static string DescribeVisual(EntityInteractableStore store)
		{
			//IL_0036: 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)
			GameObject approvedHeightVisual_k__BackingField = store._ApprovedHeightVisual_k__BackingField;
			if ((Object)(object)approvedHeightVisual_k__BackingField == (Object)null)
			{
				return "no approved height visual";
			}
			Transform transform = approvedHeightVisual_k__BackingField.transform;
			return $"approved height visual at local y {transform.localPosition.y:0.###}, scale y {transform.localScale.y:0.###}, parent {(((Object)(object)transform.parent == (Object)null) ? "none" : ((Object)transform.parent).name)}";
		}

		private void MoveApprovedVisual(EntityInteractableStore store, float before, float after)
		{
			//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_003e: 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_0053: 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_009a: Unknown result type (might be due to invalid IL or missing references)
			if (!(before <= 0f) && !(after <= 0f) && !(Math.Abs(before - after) < 0.001f))
			{
				GameObject approvedHeightVisual_k__BackingField = store._ApprovedHeightVisual_k__BackingField;
				if (!((Object)(object)approvedHeightVisual_k__BackingField == (Object)null))
				{
					Transform transform = approvedHeightVisual_k__BackingField.transform;
					Vector3 localPosition = transform.localPosition;
					float num = HeightRule.VisualHeight(localPosition.y, before, after);
					transform.localPosition = new Vector3(localPosition.x, num, localPosition.z);
					_log.Debug($"Approved height visual of {KeyOf(store)} moved from {localPosition.y:0.###} to {num:0.###} for approved height {before:0.##} -> {after:0.##}");
				}
			}
		}

		public string DescribeBoats()
		{
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>(_prefabs.Values.Select((PrefabState state) => state.Key), StringComparer.Ordinal);
			List<string> list = new List<string>();
			foreach (EntityInteractableStore item in Object.FindObjectsOfType<EntityInteractableStore>())
			{
				if (!((Object)(object)item == (Object)null) && hashSet.Contains(KeyOf(item)))
				{
					GameObject approvedHeightVisual_k__BackingField = item._ApprovedHeightVisual_k__BackingField;
					list.Add($"{KeyOf(item)}: approved {item._StorageMaximumApprovedHeight_k__BackingField:0.##}, maximum {item._StorageMaximumHeight_k__BackingField:0.##}, current {item.CurrentHeight:0.##}, approved now {item.IsHeightApproved}, " + (((Object)(object)approvedHeightVisual_k__BackingField == (Object)null) ? "no approved height visual" : $"visual {(approvedHeightVisual_k__BackingField.activeInHierarchy ? "shown" : "hidden")} at local height {approvedHeightVisual_k__BackingField.transform.localPosition.y:0.##}"));
				}
			}
			SessionRole role = CatNetwork.Role;
			_log.Info($"Boats in the scene as {role}: {((list.Count == 0) ? "none" : string.Join("; ", list))}; scales approved x{ApprovedScale:0.##}, maximum x{MaximumScale:0.##}{(IsActive ? string.Empty : ", paused in this session")}");
			if (list.Count != 0)
			{
				return $"{list.Count} boat(s), see the log";
			}
			return "no boat in the scene";
		}

		private void Apply(IReadOnlyList<Pool> pools)
		{
			LayoutMode effectiveMode = EffectiveMode;
			IReadOnlyList<int> allowedVariants = VariantList.Parse(Settings.AllowedVariants.Value);
			bool clearsGameDeck = CurrentPlan.ClearsGameDeck;
			foreach (Pool pool in pools)
			{
				int value;
				int lastVariantIndex = (_lastVariant.TryGetValue(pool.Key, out value) ? value : (-1));
				LayoutPlan value2 = LayoutPlanner.Choose(effectiveMode, pool.Prefabs.Count, allowedVariants, Settings.FixedVariant.Value, lastVariantIndex, Settings.AvoidRepeats.Value, _random);
				_plans[pool.Key] = value2;
				List<EntityInteractableStore> list = FittingPrefabs(pool);
				pool.Data._DelivererStores_k__BackingField = ((value2.Kind == LayoutPlanKind.Variant) ? new Il2CppReferenceArray<EntityInteractableStore>((EntityInteractableStore[])(object)new EntityInteractableStore[1] { pool.Prefabs[value2.VariantIndex] }) : ((list != null) ? new Il2CppReferenceArray<EntityInteractableStore>(list.ToArray()) : pool.OriginalArray));
				foreach (EntityInteractableStore prefab in pool.Prefabs)
				{
					PrefabState prefabState = _prefabs[((Il2CppObjectBase)prefab).Pointer];
					ApplyHeight(prefab, prefabState);
					prefabState.SetDeckCleared(clearsGameDeck);
				}
			}
			string text = $"Boat: mode {effectiveMode}, next deck {CurrentPlan.Describe()}, {pools.Count} pool(s), {_prefabs.Count} boat storage prefab(s), {_prefabs.Values.Sum((PrefabState state) => state.BlockerCount)} blocker(s) and {_prefabs.Values.Sum((PrefabState state) => state.PropCount)} prop(s) {(clearsGameDeck ? "hidden" : "shown")}, height x{ApprovedScale:0.##} approved, x{MaximumScale:0.##} maximum{(IsActive ? string.Empty : ", paused in this session")}" + ((EntityObjects > 0) ? $", {EntityObjects} object(s) with game entities left untouched" : string.Empty);
			string text2 = string.Join(", ", from pool in pools.Where((Pool pool) => _plans[pool.Key].Kind != LayoutPlanKind.KeepGame).Take(3)
				select pool.Key + ": " + _plans[pool.Key]);
			if (text != _lastSummary)
			{
				_lastSummary = text;
				_log.Info(text);
			}
			if (text2.Length > 0)
			{
				_log.Debug("Planned boat variants: " + text2);
			}
		}

		private List<EntityInteractableStore> FittingPrefabs(Pool pool)
		{
			DeckPlan currentPlan = CurrentPlan;
			if (currentPlan.Kind != DeckPlanKind.Pattern)
			{
				return null;
			}
			List<ISet<(int, int)>> arrivingPerVariant = ((IEnumerable<EntityInteractableStore>)pool.Prefabs).Select((Func<EntityInteractableStore, ISet<(int, int)>>)((EntityInteractableStore prefab) => _prefabs[((Il2CppObjectBase)prefab).Pointer].Arriving)).ToList();
			LayoutFitResult layoutFitResult = LayoutFit.ClosestVariants(currentPlan.Pattern, arrivingPerVariant);
			if (layoutFitResult.FreedCells > 0)
			{
				_log.Debug($"Layout \"{currentPlan.Name}\" meets arriving parcels on every boat of pool {pool.Key}; the closest one leaves {layoutFitResult.FreedCells} cell(s) free");
			}
			if (layoutFitResult.Variants.Count == 0 || layoutFitResult.Variants.Count == pool.Prefabs.Count)
			{
				return null;
			}
			return layoutFitResult.Variants.Select((int index) => pool.Prefabs[index]).ToList();
		}

		private void ApplyHeight(EntityInteractableStore store, PrefabState state)
		{
			if (state != null)
			{
				var (storageMaximumApprovedHeight_k__BackingField, storageMaximumHeight_k__BackingField) = HeightRule.Apply(state.Approved, state.Maximum, ApprovedScale, MaximumScale);
				store._StorageMaximumApprovedHeight_k__BackingField = storageMaximumApprovedHeight_k__BackingField;
				store._StorageMaximumHeight_k__BackingField = storageMaximumHeight_k__BackingField;
			}
		}

		private void OnBoatArrived(EntityInteractableStore store, IReadOnlyList<Pool> pools)
		{
			string key = KeyOf(store);
			foreach (Pool pool in pools)
			{
				int num = pool.Prefabs.FindIndex((EntityInteractableStore prefab) => KeyOf(prefab) == key);
				if (num >= 0)
				{
					LayoutPlan value2;
					LayoutPlan value = (_plans.TryGetValue(pool.Key, out value2) ? value2 : LayoutPlan.KeepGame);
					_lastVariant[pool.Key] = num;
					_log.Info($"Boat arrived: {key}, variant {num + 1} of pool {pool.Key}, planned {value}, deck {CurrentPlan.Describe()}, approved height {store._StorageMaximumApprovedHeight_k__BackingField:0.##}, maximum {store._StorageMaximumHeight_k__BackingField:0.##}, {DescribeVisual(store)}");
					StartBuild(store);
					return;
				}
			}
			_log.Warning("Boat arrived with " + key + ", which is not in any known boat pool");
			StartBuild(store);
		}

		private void StartBuild(EntityInteractableStore store)
		{
			if (IsAuthority)
			{
				_decideAt = FrameLoop.Realtime + 30.0;
			}
			if (CurrentPlan.BuildsDeck)
			{
				_pendingStore = store;
				_pendingPlan = CurrentPlan;
				_pendingUntil = FrameLoop.Realtime + 20.0;
			}
		}

		private void BuildPending()
		{
			if ((Object)(object)_pendingStore == (Object)null)
			{
				return;
			}
			if (((Il2CppObjectBase)_pendingStore).WasCollected || (Object)(object)_pendingStore == (Object)null)
			{
				_pendingStore = null;
				return;
			}
			List<(int, int)> takenCells;
			DeckBuildResult num = _builder.TryBuild(_pendingStore, KeyOf(_pendingStore), _pendingPlan, Settings.Decorate.Value ? DecorTemplates() : new DeckDecorTemplates(), FallbackBlocker(), BlockerLayer(), out takenCells);
			if (num == DeckBuildResult.Done && takenCells != null && takenCells.Count > 0)
			{
				_enforcer.Hold(_pendingStore, KeyOf(_pendingStore), takenCells);
			}
			if (num == DeckBuildResult.Done && _pendingPlan.Kind == DeckPlanKind.Pattern && takenCells != null)
			{
				ReportFreed(_pendingPlan, KeyOf(_pendingStore), _pendingPlan.Pattern.BlockedCount - takenCells.Count);
			}
			if (num != DeckBuildResult.NotReady)
			{
				_pendingStore = null;
			}
			else if (FrameLoop.Realtime > _pendingUntil)
			{
				_log.Warning($"The deck of {KeyOf(_pendingStore)} was not ready within {20.0} seconds; it stays empty");
				_pendingStore = null;
			}
		}

		private void ReportFreed(DeckPlan plan, string key, int freed)
		{
			if (freed > 0 && _freedReported.Add(plan.Name + "|" + key))
			{
				_log.Info($"Layout \"{plan.Name}\" on {key}: {freed} cell(s) stay free under arriving parcels");
				Notifications.Show(_texts.Format("message.layoutFreed", new object[1] { freed }), (string)null);
			}
		}

		private void Decide()
		{
			_needDecision = false;
			_decideAt = double.PositiveInfinity;
			if (!IsAuthority)
			{
				return;
			}
			DeckChoices deckChoices = Settings.Choices();
			IReadOnlyList<(string Name, DeckPattern Pattern)> readOnlyList2;
			if (deckChoices.Mode != LayoutMode.Custom && deckChoices.Mode != LayoutMode.Mixed)
			{
				IReadOnlyList<(string, DeckPattern)> readOnlyList = Array.Empty<(string, DeckPattern)>();
				readOnlyList2 = readOnlyList;
			}
			else
			{
				readOnlyList2 = _library.Load(delegate(string warning)
				{
					_log.Warning("Own layout " + warning);
				});
			}
			IReadOnlyList<(string, DeckPattern)> layouts = readOnlyList2;
			DeckDecision deckDecision = DeckDecider.Decide(deckChoices, layouts, _lastLayout, _sequence, _random);
			_sequence = deckDecision.NextSequence;
			if (deckDecision.Note != null && deckDecision.Note != _lastNote)
			{
				_log.Warning("Next deck: " + deckDecision.Note);
			}
			_lastNote = deckDecision.Note;
			if (deckDecision.Plan.Kind == DeckPlanKind.Pattern)
			{
				_lastLayout = deckDecision.Plan.Name;
			}
			string text = deckDecision.Plan.Encode();
			if (text != Settings.Plan.Value)
			{
				Settings.Plan.LocalValue = text;
				_log.Info("Next deck: " + deckDecision.Plan.Describe());
			}
		}

		private void HandleHotkey()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = Settings.SaveHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				ParcelDeliveryManager val = (Singleton<ParcelDeliveryManager>.HasInstance() ? Singleton<ParcelDeliveryManager>.Instance : null);
				EntityInteractableStore val2 = (((Object)(object)val == (Object)null) ? null : CurrentStore(val));
				if ((Object)(object)val2 == (Object)null)
				{
					Notifications.Show(_texts.Get("message.noBoat"), (string)null);
					return;
				}
				bool[,] available = default(bool[,]);
				if (!Il2CppArrays.TryRead2D<bool>(val2._availableCells, ref available))
				{
					Notifications.Show(_texts.Get("message.noGrid"), (string)null);
					return;
				}
				TextAsset prefilledStorageTextAsset_k__BackingField = val2._PrefilledStorageTextAsset_k__BackingField;
				int excluded;
				DeckPattern deckPattern = DeckPattern.FromAvailability(available, PrefillFootprint.Parse(((Object)(object)prefilledStorageTextAsset_k__BackingField == (Object)null) ? null : prefilledStorageTextAsset_k__BackingField.text), out excluded);
				string text = _library.Save(deckPattern, new string[2]
				{
					"Boat Tweaks layout",
					"Saved from " + KeyOf(val2)
				}, DateTime.Now);
				_log.Info($"Deck of {KeyOf(val2)} saved as {text} with {deckPattern.BlockedCount} taken cell(s), {excluded} cell(s) of arriving parcels left out, in {_library.Directory}");
				Notifications.Show(_texts.Format("message.saved", new object[1] { text }), (string)null);
			}
		}

		private static EntityInteractableStore CurrentStore(ParcelDeliveryManager manager)
		{
			EntityInteractableStore currentDelivererStore = manager.CurrentDelivererStore;
			if ((Object)(object)currentDelivererStore != (Object)null)
			{
				return currentDelivererStore;
			}
			ParcelDeliverer deliverer = manager.Deliverer;
			if (!((Object)(object)deliverer == (Object)null))
			{
				return deliverer._storage;
			}
			return null;
		}

		private int BlockerLayer()
		{
			if (_blockerLayer == -2)
			{
				_blockerLayer = LayerMask.NameToLayer("StorageBlocker");
			}
			return _blockerLayer;
		}

		private DeckDecorTemplates DecorTemplates()
		{
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			DeckDecorTemplates deckDecorTemplates = new DeckDecorTemplates();
			foreach (var item3 in from @group in (from prop in _prefabs.Values.SelectMany((PrefabState state) => state.Props.Select(((GameObject Object, bool WasActive) prop) => (Object: prop.Object, Cell: CellSize(state.Prefab))))
					where (Object)(object)prop.Object != (Object)null
					select prop).GroupBy<(GameObject, float), string>(((GameObject Object, float Cell) prop) => ((Object)prop.Object).name.Split(' ')[0], StringComparer.Ordinal).OrderBy<IGrouping<string, (GameObject, float)>, string>((IGrouping<string, (GameObject Object, float Cell)> @group) => @group.Key, StringComparer.Ordinal)
				select @group.First())
			{
				GameObject item = item3.Object;
				float item2 = item3.Cell;
				string text = ((Object)item).name.Split(' ')[0];
				(int, int)? tuple = DeckDecor.ByName(((Object)item).name);
				string text2 = string.Empty;
				if (DeckBuilder.TryMeasure(item, out var size))
				{
					(int, int) value = DeckDecor.Footprint(size.x, size.z, item2);
					bool flag = DeckDecor.IsFlat(size.y, item2);
					tuple = (flag ? (((int, int)?)null) : new(int, int)?(value));
					text2 = $" {value.Item1}x{value.Item2} h{size.y:0.##}{(flag ? " flat" : string.Empty)}";
				}
				if (!tuple.HasValue || DeckDecor.IsExcluded(((Object)item).name) || !DeckPieces.IsKnown(tuple.Value))
				{
					deckDecorTemplates.Unused.Add(text + text2);
				}
				else
				{
					deckDecorTemplates.Add(item, tuple.Value, text + text2);
				}
			}
			string text3 = deckDecorTemplates.Describe();
			if (text3 != _lastCatalog)
			{
				_lastCatalog = text3;
				_log.Info("Decoration of own and generated decks: " + text3);
			}
			return deckDecorTemplates;
		}

		private static float CellSize(EntityInteractableStore prefab)
		{
			float num = (((Object)(object)prefab == (Object)null) ? 0f : (prefab._Padding_k__BackingField * 2f));
			if (!(num > 0f))
			{
				return 0.25f;
			}
			return num;
		}

		private GameObject FallbackBlocker()
		{
			return (from blocker in _prefabs.Values.SelectMany((PrefabState state) => state.Blockers)
				select blocker.Object).FirstOrDefault((Func<GameObject, bool>)((GameObject blocker) => (Object)(object)blocker != (Object)null));
		}

		private List<Pool> CollectPools(ParcelDeliveryManager manager)
		{
			List<Pool> list = new List<Pool>();
			Il2CppReferenceArray<DelivererStoresDataPerProgressionLevel> delivererStoresPerProgressionLevelsPerPlayerCount = manager.DelivererStoresPerProgressionLevelsPerPlayerCount;
			int num = 0;
			while (delivererStoresPerProgressionLevelsPerPlayerCount != null && num < ((Il2CppArrayBase<DelivererStoresDataPerProgressionLevel>)(object)delivererStoresPerProgressionLevelsPerPlayerCount).Length)
			{
				DelivererStoresDataPerProgressionLevel obj = ((Il2CppArrayBase<DelivererStoresDataPerProgressionLevel>)(object)delivererStoresPerProgressionLevelsPerPlayerCount)[num];
				Il2CppReferenceArray<DelivererStoresData> val = ((obj != null) ? obj.DelivererStoreDataPerProgressionLevels : null);
				int num2 = 0;
				while (val != null && num2 < ((Il2CppArrayBase<DelivererStoresData>)(object)val).Length)
				{
					AddPool(list, $"players{num + 1}/level{num2 + 1}", ((Il2CppArrayBase<DelivererStoresData>)(object)val)[num2]);
					num2++;
				}
				num++;
			}
			AddPool(list, "fallback", manager.DelivererStoresFallback);
			return list;
		}

		private void AddPool(List<Pool> pools, string key, DelivererStoresData data)
		{
			if (data == null)
			{
				return;
			}
			if (!_pools.TryGetValue(((Il2CppObjectBase)data).Pointer, out var value))
			{
				Il2CppReferenceArray<EntityInteractableStore> delivererStores_k__BackingField = data._DelivererStores_k__BackingField;
				if (delivererStores_k__BackingField == null || ((Il2CppArrayBase<EntityInteractableStore>)(object)delivererStores_k__BackingField).Length == 0)
				{
					return;
				}
				List<EntityInteractableStore> list = new List<EntityInteractableStore>();
				foreach (EntityInteractableStore item in (Il2CppArrayBase<EntityInteractableStore>)(object)delivererStores_k__BackingField)
				{
					if ((Object)(object)item != (Object)null)
					{
						list.Add(item);
					}
				}
				value = new Pool(key, data, delivererStores_k__BackingField, list);
				_pools[((Il2CppObjectBase)data).Pointer] = value;
				foreach (EntityInteractableStore item2 in list)
				{
					Remember(item2);
				}
			}
			if (value.Prefabs.Count > 0)
			{
				pools.Add(value);
			}
		}

		private void Prune(IReadOnlyList<Pool> pools)
		{
			HashSet<IntPtr> live = new HashSet<IntPtr>(from prefab in pools.SelectMany((Pool pool) => pool.Prefabs)
				select ((Il2CppObjectBase)prefab).Pointer);
			List<KeyValuePair<IntPtr, PrefabState>> list = _prefabs.Where((KeyValuePair<IntPtr, PrefabState> pair) => !live.Contains(pair.Key)).ToList();
			foreach (var (key, prefabState2) in list)
			{
				prefabState2.Restore();
				_prefabs.Remove(key);
			}
			if (list.Count > 0)
			{
				_log.Debug($"Forgot {list.Count} boat storage prefab(s) that the new level no longer uses");
			}
		}

		private void Remember(EntityInteractableStore prefab)
		{
			if (_prefabs.ContainsKey(((Il2CppObjectBase)prefab).Pointer))
			{
				return;
			}
			BlockerLayer();
			TextAsset prefilledStorageTextAsset_k__BackingField = prefab._PrefilledStorageTextAsset_k__BackingField;
			PrefabState prefabState = new PrefabState(prefab, KeyOf(prefab), prefab._StorageMaximumApprovedHeight_k__BackingField, prefab._StorageMaximumHeight_k__BackingField, PrefillFootprint.Parse(((Object)(object)prefilledStorageTextAsset_k__BackingField == (Object)null) ? null : prefilledStorageTextAsset_k__BackingField.text));
			foreach (Transform componentsInChild in ((Component)prefab).GetComponentsInChildren<Transform>(true))
			{
				GameObject gameObject = ((Component)componentsInChild).gameObject;
				string parentName = (((Object)(object)componentsInChild.parent == (Object)null) ? null : ((Object)componentsInChild.parent).name);
				bool flag = _blockerLayer >= 0 && gameObject.layer == _blockerLayer;
				bool flag2 = !flag && DeckDecor.IsProp(parentName, ((Object)gameObject).name);
				if (flag || flag2)
				{
					if (gameObject.GetComponentsInChildren<Entity>(true).Length > 0)
					{
						prefabState.EntityObjects++;
					}
					else
					{
						(flag ? prefabState.Blockers : prefabState.Props).Add((gameObject, gameObject.activeSelf));
					}
				}
			}
			_prefabs[((Il2CppObjectBase)prefab).Pointer] = prefabState;
		}

		private static string KeyOf(EntityInteractableStore store)
		{
			Entity component = ((Component)store).GetComponent<Entity>();
			if (!((Object)(object)component == (Object)null))
			{
				return component.AddressableKey;
			}
			return ((Object)store).name;
		}
	}
	[BepInPlugin("catlib.boattweaks", "Boat Tweaks", "0.3.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class BoatTweaksPlugin : BasePlugin
	{
		public const string LanguageResourcePrefix = "BoatTweaks.Lang.";

		private BoatController _controller;

		public override void Load()
		{
			CatLogger val = CatLogger.From(((BasePlugin)this).Log);
			CatSettings val2 = CatSettings.For((BasePlugin)(object)this);
			int count = val2.Texts.Count;
			CatNetwork.Declare((BasePlugin)(object)this, (SessionPolicy)1, (VersionRule)2);
			PatternLibrary patternLibrary = new PatternLibrary(Path.Combine(Paths.ConfigPath, "BoatTweaks", "layouts"));
			_controller = new BoatController(val, val2.Texts, patternLibrary);
			_controller.Settings = new BoatSettings(val2, _controller.RequestDecision, _controller.RequestApply);
			FrameLoop.Update += _controller.Update;
			BootstrapEvents.GameRestartStarted += _controller.SuspendForRestart;
			BootstrapEvents.LevelLoadStarted += _controller.OnLevelLoadStarted;
			CatNetwork.ActiveModsChanged += _controller.OnActiveModsChanged;
			DevMenu.Command("Boat Tweaks", "Boat heights", (Func<string>)_controller.DescribeBoats, "Writes the height limits of every boat in the scene and whether its stack is approved.");
			val.Info($"Boat Tweaks {"0.3.0"} loaded with {count} translated text(s) in {string.Join(", ", val2.Texts.Languages)}, own layouts in {patternLibrary.Directory}");
		}
	}
	public static class PluginMeta
	{
		public const string Guid = "catlib.boattweaks";

		public const string Name = "Boat Tweaks";

		public const string Version = "0.3.0";
	}
}
namespace BoatTweaks.Settings
{
	public sealed class BoatSettings
	{
		private interface ISettingApply
		{
			void OnChange(Action changed);
		}

		private sealed class Hook<T> : ISettingApply
		{
			private readonly Setting<T> _setting;

			public Hook(Setting<T> setting)
			{
				_setting = setting;
			}

			public void OnChange(Action changed)
			{
				_setting.Apply((Action<T>)delegate
				{
					changed();
				});
			}
		}

		public Setting<LayoutMode> Mode { get; }

		public Setting<string> AllowedVariants { get; }

		public Setting<bool> AvoidRepeats { get; }

		public Setting<int> FixedVariant { get; }

		public Setting<CustomOrder> CustomOrder { get; }

		public Setting<string> CustomFile { get; }

		public Setting<bool> CustomAvoidRepeats { get; }

		public Setting<float> Density { get; }

		public Setting<bool> EdgesOnly { get; }

		public Setting<int> GameWeight { get; }

		public Setting<int> EmptyWeight { get; }

		public Setting<int> CustomWeight { get; }

		public Setting<int> GeneratedWeight { get; }

		public Setting<float> ApprovedHeightScale { get; }

		public Setting<float> MaximumHeightScale { get; }

		public Setting<KeyboardShortcut> SaveHotkey { get; }

		public Setting<bool> Decorate { get; }

		public Setting<string> Plan { get; }

		public BoatSettings(CatSettings settings, Action changed, Action planChanged)
		{
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0.5f, 4f);
			AcceptableValueRange<int> val2 = new AcceptableValueRange<int>(0, 100);
			Mode = settings.Session<LayoutMode>("Layout", "Mode", LayoutMode.Game, "How the fixed crates, baskets and other blockers on the boat deck are chosen. Arriving parcels never change. The sections below only work in their own mode.", (AcceptableValueBase)null);
			AllowedVariants = settings.Session<string>("GameVariants", "Allowed", "1,2,3,4", "Numbers of the game boat variants that may come, for example 1,2,4. Empty means all. Used in the Game layouts mode.", (AcceptableValueBase)null);
			AvoidRepeats = settings.Session<bool>("GameVariants", "AvoidRepeats", true, "The same boat variant never comes twice in a row. Used in the Game layouts mode.", (AcceptableValueBase)null);
			FixedVariant = settings.Session<int>("FixedVariant", "Variant", 1, "The game boat variant that comes every time. If a level has fewer variants, its last one is used. Used in the One game layout mode.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 9));
			CustomOrder = settings.Session<CustomOrder>("CustomLayouts", "Order", BoatTweaks.Logic.CustomOrder.Random, "How own layouts are picked: at random, one after another by file name, or always the file named below. Used in the Own layouts and Mixed modes.", (AcceptableValueBase)null);
			CustomFile = settings.Session<string>("CustomLayouts", "File", "", "File name of the layout used when the order is Always one file, without the folder. Used in the Own layouts mode.", (AcceptableValueBase)null);
			CustomAvoidRepeats = settings.Session<bool>("CustomLayouts", "AvoidRepeats", true, "The same own layout never comes twice in a row when picked at random.", (AcceptableValueBase)null);
			Density = settings.Session<float>("Generated", "Density", 0.2f, "Share of the deck taken by generated blockers. Used in the Generated and Mixed modes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.6f));
			EdgesOnly = settings.Session<bool>("Generated", "EdgesOnly", true, "Generated blockers stay near the rails, like in the game, and keep the middle of the deck free.", (AcceptableValueBase)null);
			GameWeight = settings.Session<int>("Mixed", "GameWeight", 40, "Chance weight of the game's own deck in the Mixed mode.", (AcceptableValueBase)(object)val2);
			EmptyWeight = settings.Session<int>("Mixed", "EmptyWeight", 10, "Chance weight of an empty deck in the Mixed mode.", (AcceptableValueBase)(object)val2);
			CustomWeight = settings.Session<int>("Mixed", "CustomWeight", 25, "Chance weight of own layouts in the Mixed mode. Ignored when there are none.", (AcceptableValueBase)(object)val2);
			GeneratedWeight = settings.Session<int>("Mixed", "GeneratedWeight", 25, "Chance weight of a generated deck in the Mixed mode.", (AcceptableValueBase)(object)val2);
			ApprovedHeightScale = settings.Session<float>("Height", "ApprovedHeightScale", 1f, "Multiplier for the height above which a stack on the boat turns red. 1 keeps the game value.", (AcceptableValueBase)(object)val);
			MaximumHeightScale = settings.Session<float>("Height", "MaximumHeightScale", 1f, "Multiplier for the absolute height limit of stacks on the boat. The approved height never exceeds it.", (AcceptableValueBase)(object)val);
			SaveHotkey = settings.Local<KeyboardShortcut>("Saving", "SaveHotkey", new KeyboardShortcut((KeyCode)98, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Saves the deck of the boat at the dock as an own layout. Cells of the parcels that arrived with the boat are left out.", (AcceptableValueBase)null);
			Decorate = settings.Local<bool>("Decoration", "Enabled", true, "Crates, bottles and lamps are placed on the blockers of own and generated decks. Off leaves the blocked cells bare. Only changes what you see.", (AcceptableValueBase)null);
			Plan = settings.Session<string>("Sync", "Plan", "game", "The deck planned by the host for the next boat. Written by the mod.", (AcceptableValueBase)null).HiddenInMenu();
			ISettingApply[] array = new ISettingApply[15]
			{
				new Hook<LayoutMode>(Mode),
				new Hook<string>(AllowedVariants),
				new Hook<bool>(AvoidRepeats),
				new Hook<int>(FixedVariant),
				new Hook<CustomOrder>(CustomOrder),
				new Hook<string>(CustomFile),
				new Hook<bool>(CustomAvoidRepeats),
				new Hook<float>(Density),
				new Hook<bool>(EdgesOnly),
				new Hook<int>(GameWeight),
				new Hook<int>(EmptyWeight),
				new Hook<int>(CustomWeight),
				new Hook<int>(GeneratedWeight),
				new Hook<float>(ApprovedHeightScale),
				new Hook<float>(MaximumHeightScale)
			};
			for (int i = 0; i < array.Length; i++)
			{
				array[i].OnChange(changed);
			}
			Plan.Apply((Action<string>)delegate
			{
				planChanged();
			});
		}

		public DeckChoices Choices()
		{
			return new DeckChoices
			{
				Mode = Mode.Value,
				Order = CustomOrder.Value,
				FixedFile = CustomFile.Value,
				AvoidRepeats = CustomAvoidRepeats.Value,
				Density = Density.Value,
				EdgesOnly = EdgesOnly.Value,
				GameWeight = GameWeight.Value,
				EmptyWeight = EmptyWeight.Value,
				CustomWeight = CustomWeight.Value,
				GeneratedWeight = GeneratedWeight.Value
			};
		}
	}
}
namespace BoatTweaks.Scene
{
	public enum DeckBuildResult
	{
		NotReady,
		Done,
		Skipped
	}
	public sealed class DeckDecorTemplates
	{
		private readonly Dictionary<(int Short, int Long), List<GameObject>> _byFootprint = new Dictionary<(int, int), List<GameObject>>();

		public List<string> Unused { get; } = new List<string>();

		public List<string> Used { get; } = new List<string>();

		public bool IsEmpty => _byFootprint.Count == 0;

		public void Add(GameObject prop, (int Short, int Long) footprint, string label)
		{
			if (!_byFootprint.TryGetValue(footprint, out var value))
			{
				value = new List<GameObject>();
				_byFootprint[footprint] = value;
			}
			value.Add(prop);
			Used.Add(label);
		}

		public IReadOnlyList<GameObject> For((int Short, int Long) footprint)
		{
			if (!_byFootprint.TryGetValue(footprint, out var value))
			{
				return Array.Empty<GameObject>();
			}
			return value;
		}

		public string Describe()
		{
			return "used: " + ((Used.Count == 0) ? "none" : string.Join(", ", Used)) + "; not used: " + ((Unused.Count == 0) ? "none" : string.Join(", ", Unused));
		}
	}
	public sealed class DeckBuilder
	{
		public const string ClonePrefix = "BoatTweaks ";

		public const float BlockerFill = 0.9f;

		public const float DefaultCellSize = 0.25f;

		private readonly CatLogger _log;

		private bool _describedTemplate;

		public DeckBuilder(CatLogger log)
		{
			_log = log;
		}

		public DeckBuildResult TryBuild(EntityInteractableStore store, string key, DeckPlan plan, DeckDecorTemplates decor, GameObject fallbackBlocker, int blockerLayer, out List<(int Row, int Column)> takenCells)
		{
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: 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_033b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_0389: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_040c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0422: Unknown result type (might be due to invalid IL or missing references)
			takenCells = null;
			Vector3[,] array = default(Vector3[,]);
			if (!Il2CppArrays.TryRead2D<Vector3>(store._grid, ref array))
			{
				return DeckBuildResult.NotReady;
			}
			int length = array.GetLength(0);
			int length2 = array.GetLength(1);
			TextAsset prefilledStorageTextAsset_k__BackingField = store._PrefilledStorageTextAsset_k__BackingField;
			HashSet<(int, int)> hashSet = PrefillFootprint.Parse(((Object)(object)prefilledStorageTextAsset_k__BackingField == (Object)null) ? null : prefilledStorageTextAsset_k__BackingField.text);
			DeckPattern deckPattern = ((plan.Kind == DeckPlanKind.Pattern) ? plan.Pattern : PatternGenerator.Generate(length, length2, plan.Density, plan.EdgesOnly, hashSet, plan.Seed));
			if (deckPattern.Rows != length || deckPattern.Columns != length2)
			{
				_log.Warning($"{plan.Describe()} is {deckPattern.Rows}x{deckPattern.Columns}, but the deck of {key} is {length}x{length2}; the deck stays empty");
				return DeckBuildResult.Skipped;
			}
			GameObject val = FindBlocker(store, blockerLayer) ?? fallbackBlocker;
			if ((Object)(object)val == (Object)null)
			{
				_log.Warning("No blocker to copy was found for " + key + "; the deck stays empty");
				return DeckBuildResult.Skipped;
			}
			float num = store._Padding_k__BackingField * 2f;
			if (num <= 0f)
			{
				num = 0.25f;
			}
			Transform transform = val.transform;
			Transform val2 = (transform.IsChildOf(((Component)store).transform) ? transform.parent : Child(((Component)store).transform, "Colliders"));
			Transform parent = Child(((Component)store).transform, "Visuals");
			BoxCollider component = val.GetComponent<BoxCollider>();
			if (!_describedTemplate)
			{
				_describedTemplate = true;
				_log.Info($"Blocker template {((Object)val).name} has {DescribeComponents(val)}; the mod places plain box colliders on its layer instead of copies");
			}
			float num2 = (((Object)(object)component == (Object)null) ? (num * 2f) : (component.size.y * transform.localScale.y));
			float num3 = (((Object)(object)component == (Object)null) ? num : (component.center.y * transform.localScale.y));
			float y = transform.localPosition.y;
			Random random = new Random(plan.Seed ^ (plan.Name ?? string.Empty).Length);
			IReadOnlyList<DeckPiece> readOnlyList = deckPattern.Pieces();
			List<DeckPiece> list = DeckPieces.Avoiding(readOnlyList, hashSet);
			int value = readOnlyList.Count - list.Count;
			List<(int, int)> list2 = new List<(int, int)>();
			foreach (var item3 in list.SelectMany((DeckPiece piece) => piece.Cells()))
			{
				int item = item3.Row;
				int item2 = item3.Column;
				Vector3 val3 = array[item, item2];
				GameObject val4 = new GameObject($"{"BoatTweaks "}Blocker {item},{item2}")
				{
					layer = val.layer
				};
				Transform transform2 = val4.transform;
				transform2.SetParent(val2, false);
				transform2.localPosition = new Vector3(val3.x, y, val3.z);
				transform2.localRotation = Quaternion.identity;
				transform2.localScale = Vector3.one;
				BoxCollider obj = val4.AddComponent<BoxCollider>();
				((Collider)obj).isTrigger = (Object)(object)component != (Object)null && ((Collider)component).isTrigger;
				obj.size = new Vector3(num * 0.9f, num2, num * 0.9f);
				obj.center = new Vector3(0f, num3, 0f);
				list2.Add((item, item2));
			}
			int num4 = 0;
			foreach (DeckPiece item4 in list)
			{
				num4 += Decorate(item4, array, decor, parent, random);
			}
			Physics.SyncTransforms();
			int value2 = deckPattern.BlockedCount - list2.Count;
			_log.Info($"Deck built for {key}: {plan.Describe()}, {list2.Count} cell(s) taken, {value2} cell(s) of {value} piece(s) left free for arriving parcels, pieces {string.Join(" ", list.Select((DeckPiece piece) => $"{piece.Rows}x{piece.Columns}@{piece.Row},{piece.Column}"))} with {num4} decoration object(s)");
			takenCells = list2;
			return DeckBuildResult.Done;
		}

		private static int Decorate(DeckPiece piece, Vector3[,] grid, DeckDecorTemplates decor, Transform parent, Random random)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: 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_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0255: Unknown result type (might be due to invalid IL or missing references)
			IReadOnlyList<GameObject> readOnlyList = decor.For(piece.Footprint);
			if (readOnlyList.Count == 0)
			{
				if (piece.CellCount != 1)
				{
					return DeckPieces.SplitPiece(piece).Sum((DeckPiece part) => Decorate(part, grid, decor, parent, random));
				}
				return 0;
			}
			Vector3 val = Vector3.zero;
			foreach (var item3 in piece.Cells())
			{
				int item = item3.Row;
				int item2 = item3.Column;
				val += grid[item, item2];
			}
			val /= (float)piece.CellCount;
			GameObject val2 = readOnlyList[random.Next(readOnlyList.Count)];
			GameObject val3 = Object.Instantiate<GameObject>(val2, parent);
			((Object)val3).name = $"{"BoatTweaks "}Prop {piece.Row},{piece.Column} {piece.Rows}x{piece.Columns}";
			val3.SetActive(true);
			val3.transform.localPosition = new Vector3(val.x, val2.transform.localPosition.y, val.z);
			val3.transform.localRotation = Quaternion.identity;
			float num2;
			if (piece.Rows != piece.Columns)
			{
				Vector3 val4 = LocalExtent(val3, parent);
				bool num = val4.x >= val4.z;
				bool flag = piece.Rows > piece.Columns;
				num2 = ((num == flag) ? 0f : 90f) + 180f * (float)random.Next(2);
			}
			else
			{
				num2 = 90f * (float)random.Next(4);
			}
			val3.transform.localRotation = Quaternion.Euler(0f, num2, 0f);
			return 1;
		}

		public static bool TryMeasure(GameObject prop, out Vector3 size)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			size = Vector3.zero;
			Transform parent = prop.transform.parent;
			if ((Object)(object)parent == (Object)null)
			{
				return false;
			}
			Matrix4x4 worldToLocalMatrix = parent.worldToLocalMatrix;
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(float.MaxValue, float.MaxValue, float.MaxValue);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(float.MinValue, float.MinValue, float.MinValue);
			bool flag = false;
			Vector3 val5 = default(Vector3);
			foreach (MeshFilter componentsInChild in prop.GetComponentsInChildren<MeshFilter>(true))
			{
				Mesh val3 = (((Object)(object)componentsInChild == (Object)null) ? null : componentsInChild.sharedMesh);
				if (!((Object)(object)val3 == (Object)null))
				{
					Matrix4x4 val4 = worldToLocalMatrix * ((Component)componentsInChild).transform.localToWorldMatrix;
					Bounds bounds = val3.bounds;
					for (int i = 0; i < 8; i++)
					{
						((Vector3)(ref val5))..ctor(((i & 1) == 0) ? ((Bounds)(ref bounds)).min.x : ((Bounds)(ref bounds)).max.x, ((i & 2) == 0) ? ((Bounds)(ref bounds)).min.y : ((Bounds)(ref bounds)).max.y, ((i & 4) == 0) ? ((Bounds)(ref bounds)).min.z : ((Bounds)(ref bounds)).max.z);
						Vector3 val6 = ((Matrix4x4)(ref val4)).MultiplyPoint3x4(val5);
						val = Vector3.Min(val, val6);
						val2 = Vector3.Max(val2, val6);
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			size = val2 - val;
			return true;
		}

		public static Vector3 LocalExtent(GameObject prop, Transform parent)
		{
			//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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			Il2CppArrayBase<Renderer> componentsInChildren = prop.GetComponentsInChildren<Renderer>(false);
			if (componentsInChildren.Length == 0)
			{
				return Vector3.one;
			}
			Bounds bounds = componentsInChildren[0].bounds;
			for (int i = 1; i < componentsInChildren.Length; i++)
			{
				((Bounds)(ref bounds)).Encapsulate(componentsInChildren[i].bounds);
			}
			Vector3 val = parent.InverseTransformVector(((Bounds)(ref bounds)).size);
			return new Vector3(Mathf.Abs(val.x), Mathf.Abs(val.y), Mathf.Abs(val.z));
		}

		private static string DescribeComponents(GameObject gameObject)
		{
			List<string> list = new List<string>();
			foreach (Component component in gameObject.GetComponents<Component>())
			{
				list.Add(((Object)(object)component == (Object)null) ? "missing" : ((MemberInfo)((Object)component).GetIl2CppType()).Name);
			}
			return string.Join(", ", list);
		}

		public static GameObject FindBlocker(EntityInteractableStore store, int blockerLayer)
		{
			if (blockerLayer < 0)
			{
				return null;
			}
			foreach (Transform componentsInChild in ((Component)store).GetComponentsInChildren<Transform>(true))
			{
				if (((Component)componentsInChild).gameObject.layer == blockerLayer && !((Object)componentsInChild).name.StartsWith("BoatTweaks ", StringComparison.Ordinal))
				{
					return ((Component)componentsInChild).gameObject;
				}
			}
			return null;
		}

		private static Transform Child(Transform parent, string name)
		{
			for (int i = 0; i < parent.childCount; i++)
			{
				Transform child = parent.GetChild(i);
				if (((Object)child).name == name)
				{
					return child;
				}
			}
			return parent;
		}
	}
	public sealed class DeckEnforcer
	{
		private sealed class Entry
		{
			public EntityInteractableStore Store { get; }

			public string Key { get; }

			public List<(int Row, int Column)> Cells { get; }

			public bool Reported { get; set; }

			public Entry(EntityInteractableStore store, string key, List<(int Row, int Column)> cells)
			{
				Store = store;
				Key = key;
				Cells = cells;
			}
		}

		private readonly CatLogger _log;

		private readonly Dictionary<IntPtr, Entry> _decks = new Dictionary<IntPtr, Entry>();

		public int Count => _decks.Count;

		public DeckEnforcer(CatLogger log)
		{
			_log = log;
		}

		public void Hold(EntityInteractableStore store, string key, IReadOnlyList<(int Row, int Column)> cells)
		{
			_decks[((Il2CppObjectBase)store).Pointer] = new Entry(store, key, cells.ToList());
		}

		public void Clear()
		{
			_decks.Clear();
		}

		public void Update()
		{
			if (_decks.Count == 0)
			{
				return;
			}
			List<IntPtr> list = null;
			foreach (KeyValuePair<IntPtr, Entry> deck in _decks)
			{
				deck.Deconstruct(out var key, out var value);
				IntPtr item = key;
				Entry entry = value;
				if ((Object)(object)entry.Store == (Object)null || ((Il2CppObjectBase)entry.Store).WasCollected)
				{
					(list ?? (list = new List<IntPtr>())).Add(item);
					continue;
				}
				Il2CppObjectBase array = entry.Store._availableCells;
				int num = entry.Cells.Count(((int Row, int Column) cell) => Il2CppArrays.TrySet<bool>(array, cell.Row, cell.Column, false));
				if (!entry.Reported)
				{
					entry.Reported = true;
					if (num == entry.Cells.Count)
					{
						_log.Info($"Deck of {entry.Key}: {num} cell(s) are held taken");
					}
					else
					{
						_log.Warning($"Deck of {entry.Key}: only {num} of {entry.Cells.Count} cell(s) could be held taken");
					}
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (IntPtr item2 in list)
			{
				_decks.Remove(item2);
			}
		}
	}
}
namespace BoatTweaks.Logic
{
	public sealed class DeckChoices
	{
		public LayoutMode Mode { get; init; }

		public CustomOrder Order { get; init; }

		public string FixedFile { get; init; }

		public bool AvoidRepeats { get; init; }

		public float Density { get; init; }

		public bool EdgesOnly { get; init; }

		public int GameWeight { get; init; }

		public int EmptyWeight { get; init; }

		public int CustomWeight { get; init; }

		public int GeneratedWeight { get; init; }
	}
	public sealed record DeckDecision(DeckPlan Plan, int NextSequence, string Note);
	public static class DeckDecider
	{
		public static DeckDecision Decide(DeckChoices choices, IReadOnlyList<(string Name, DeckPattern Pattern)> layouts, string lastLayout, int sequence, Random random)
		{
			LayoutMode layoutMode = choices.Mode;
			if (layoutMode == LayoutMode.Mixed)
			{
				layoutMode = PickSource(choices, layouts.Count > 0, random);
			}
			return layoutMode switch
			{
				LayoutMode.Empty => new DeckDecision(DeckPlan.Empty, sequence, null), 
				LayoutMode.Custom => ChooseLayout(choices, layouts, lastLayout, sequence, random), 
				LayoutMode.Generated => new DeckDecision(DeckPlan.FromGenerator(random.Next(1, int.MaxValue), choices.Density, choices.EdgesOnly), sequence, null), 
				_ => new DeckDecision(DeckPlan.Game, sequence, null), 
			};
		}

		public static LayoutMode PickSource(DeckChoices choices, bool hasLayouts, Random random)
		{
			List<(LayoutMode, int)> list = new List<(LayoutMode, int)>
			{
				(LayoutMode.Game, Math.Max(0, choices.GameWeight)),
				(LayoutMode.Empty, Math.Max(0, choices.EmptyWeight)),
				(LayoutMode.Custom, hasLayouts ? Math.Max(0, choices.CustomWeight) : 0),
				(LayoutMode.Generated, Math.Max(0, choices.GeneratedWeight))
			};
			int num = list.Sum<(LayoutMode, int)>(((LayoutMode Mode, int Weight) source) => source.Weight);
			if (num == 0)
			{
				return LayoutMode.Game;
			}
			int num2 = random.Next(num);
			foreach (var (result, num3) in list)
			{
				if (num2 < num3)
				{
					return result;
				}
				num2 -= num3;
			}
			return LayoutMode.Game;
		}

		private static DeckDecision ChooseLayout(DeckChoices choices, IReadOnlyList<(string Name, DeckPattern Pattern)> layouts, string lastLayout, int sequence, Random random)
		{
			if (layouts.Count == 0)
			{
				return new DeckDecision(DeckPlan.Game, sequence, "no own layouts were found, the game deck is kept");
			}
			switch (choices.Order)
			{
			case CustomOrder.Fixed:
			{
				string wanted = (choices.FixedFile ?? string.Empty).Trim();
				if (wanted.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
				{
					wanted = wanted.Substring(0, wanted.Length - 4);
				}
				(string, DeckPattern) tuple2 = layouts.FirstOrDefault(((string Name, DeckPattern Pattern) layout) => string.Equals(layout.Name, wanted, StringComparison.OrdinalIgnoreCase));
				if (tuple2.Item2 != null)
				{
					return new DeckDecision(DeckPlan.FromPattern(tuple2.Item1, tuple2.Item2), sequence, null);
				}
				return new DeckDecision(DeckPlan.Game, sequence, "the layout \"" + wanted + "\" was not found, the game deck is kept");
			}
			case CustomOrder.Sequence:
			{
				int index = (sequence % layouts.Count + layouts.Count) % layouts.Count;
				return new DeckDecision(DeckPlan.FromPattern(layouts[index].Name, layouts[index].Pattern), sequence + 1, null);
			}
			default:
			{
				List<(string, DeckPattern)> list = layouts.ToList();
				if (choices.AvoidRepeats && list.Count > 1)
				{
					list.RemoveAll(((string Name, DeckPattern Pattern) layout) => string.Equals(layout.Name, lastLayout, StringComparison.OrdinalIgnoreCase));
				}
				(string, DeckPattern) tuple = list[random.Next(list.Count)];
				return new DeckDecision(DeckPlan.FromPattern(tuple.Item1, tuple.Item2), sequence, null);
			}
			}
		}
	}
	public static class DeckDecor
	{
		public const string BlockerLayerName = "StorageBlocker";

		public const string VisualsName = "Visuals";

		public const string PropPrefix = "prop_";

		public static readonly string[] SmallPropMarkers = new string[2] { "Bottle", "Lamp" };

		public const string LargeCrateMarker = "Crate_Standard";

		public const string SmallCrateMarker = "Crate_Small";

		public const float FlatShare = 0.5f;

		public static readonly string[] UnusedMarkers = new string[2] { "Rope", "Paddle" };

		public static bool IsLargeCrate(string name)
		{
			if (name != null)
			{
				return name.IndexOf("Crate_Standard", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		public static bool IsSmallCrate(string name)
		{
			if (name != null)
			{
				return name.IndexOf("Crate_Small", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		public static bool IsSmallProp(string name)
		{
			if (name != null)
			{
				return Array.Exists(SmallPropMarkers, (string marker) => name.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0);
			}
			return false;
		}

		public static bool IsExcluded(string name)
		{
			if (name != null)
			{
				return Array.Exists(UnusedMarkers, (string marker) => name.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0);
			}
			return false;
		}

		public static int Cells(float size, float cell)
		{
			if (!(cell <= 0f))
			{
				return Math.Max(1, (int)Math.Round(size / cell, MidpointRounding.AwayFromZero));
			}
			return 1;
		}

		public static (int Short, int Long) Footprint(float width, float depth, float cell)
		{
			int val = Cells(width, cell);
			int val2 = Cells(depth, cell);
			return (Short: Math.Min(val, val2), Long: Math.Max(val, val2));
		}

		public static bool IsFlat(float height, float cell)
		{
			return height < cell * 0.5f;
		}

		public static (int Short, int Long)? ByName(string name)
		{
			if (!IsLargeCrate(name))
			{
				if (!IsSmallCrate(name))
				{
					if (!IsSmallProp(name))
					{
						return null;
					}
					return (1, 1);
				}
				return (1, 2);
			}
			return (2, 2);
		}

		public static bool IsProp(string parentName, string name)
		{
			if (string.Equals(parentName, "Visuals", StringComparison.Ordinal) && name != null)
			{
				return name.StartsWith("prop_", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}
	}
	public sealed class DeckPattern
	{
		public const string BlockedChars = "#xX";

		public const string FreeChars = ".o_";

		private readonly bool[,] _blocked;

		private readonly IReadOnlyList<DeckPiece> _pieces;

		public int Rows => _blocked.GetLength(0);

		public int Columns => _blocked.GetLength(1);

		public int BlockedCount => BlockedCells().Count();

		public DeckPattern(bool[,] blocked)
			: this(blocked, null)
		{
		}

		public DeckPattern(bool[,] blocked, IEnumerable<DeckPiece> pieces)
		{
			_blocked = (bool[,])blocked.Clone();
			_pieces = pieces?.ToList();
		}

		public bool IsBlocked(int row, int column)
		{
			return _blocked[row, column];
		}

		public IReadOnlyList<DeckPiece> Pieces()
		{
			return _pieces ?? DeckPieces.Split(Rows, Columns, new HashSet<(int, int)>(BlockedCells()));
		}

		public IEnumerable<(int Row, int Column)> BlockedCells()
		{
			for (int row = 0; row < Rows; row++)
			{
				for (int column = 0; column < Columns; column++)
				{
					if (_blocked[row, column])
					{
						yield return (Row: row, Column: column);
					}
				}
			}
		}

		public IReadOnlyList<string> RowTexts()
		{
			List<string> list = new List<string>();
			for (int i = 0; i < Rows; i++)
			{
				StringBuilder stringBuilder = new StringBuilder(Columns);
				for (int j = 0; j < Columns; j++)
				{
					stringBuilder.Append(_blocked[i, j] ? '#' : '.');
				}
				list.Add(stringBuilder.ToString());
			}
			return list;
		}

		public static DeckPattern FromAvailability(bool[,] available, ISet<(int Row, int Column)> arriving, out int excluded)
		{
			excluded = 0;
			bool[,] array = new bool[available.GetLength(0), available.GetLength(1)];
			for (int i = 0; i < array.GetLength(0); i++)
			{
				for (int j = 0; j < array.GetLength(1); j++)
				{
					if (!available[i, j])
					{
						if (arriving != null && arriving.Contains((i, j)))
						{
							excluded++;
						}
						else
						{
							array[i, j] = true;
						}
					}
				}
			}
			return new DeckPattern(array);
		}

		public static bool IsGridLine(string line)
		{
			if (!string.IsNullOrEmpty(line))
			{
				return line.All((char character) => "#xX".IndexOf(character) >= 0 || ".o_".IndexOf(character) >= 0);
			}
			return false;
		}

		public static bool TryParse(string text, out DeckPattern pattern, out string error)
		{
			pattern = null;
			List<string> rows = (from line in (text ?? string.Empty).Split('\n')
				select line.Trim()).Where(IsGridLine).ToList();
			if (rows.Count == 0)
			{
				error = "no rows made of # and .";
				return false;
			}
			if (rows.Any((string row) => row.Length != rows[0].Length))
			{
				error = "rows have different lengths";
				return false;
			}
			bool[,] array = new bool[rows.Count, rows[0].Length];
			for (int num = 0; num < rows.Count; num++)
			{
				for (int num2 = 0; num2 < rows[num].Length; num2++)
				{
					array[num, num2] = "#xX".IndexOf(rows[num][num2]) >= 0;
				}
			}
			pattern = new DeckPattern(array);
			error = null;
			return true;
		}

		public override string ToString()
		{
			return string.Join("/", RowTexts());
		}
	}
	public readonly record struct DeckPiece(int Row, int Column, int Rows, int Columns)
	{
		public int CellCount => Rows * Columns;

		public (int Short, int Long) Footprint => (Short: Math.Min(Rows, Columns), Long: Math.Max(Rows, Columns));

		public IEnumerable<(int Row, int Column)> Cells()
		{
			for (int row = Row; row < Row + Rows; row++)
			{
				for (int column = Column; column < Column + Columns; column++)
				{
					yield return (Row: row, Column: column);
				}
			}
		}
	}
	public static class DeckPieces
	{
		public static readonly (int Short, int Long)[] Footprints = new(int, int)[7]
		{
			(4, 5),
			(4, 4),
			(2, 5),
			(3, 3),
			(2, 2),
			(1, 2),
			(1, 1)
		};

		private static readonly (int Rows, int Columns)[] Shapes = Footprints.SelectMany(((int Short, int Long) footprint) => (footprint.Short == footprint.Long) ? new(int, int)[1] { (footprint.Short, footprint.Long) } : new(int, int)[2]
		{
			(footprint.Short, footprint.Long),
			(footprint.Long, footprint.Short)
		}).ToArray();

		public static bool IsKnown((int Short, int Long) footprint)
		{
			return Array.IndexOf(Footprints, footprint) >= 0;
		}

		public static List<DeckPiece> Avoiding(IEnumerable<DeckPiece> pieces, ISet<(int Row, int Column)> cells)
		{
			return pieces.Where((DeckPiece piece) => cells == null || !piece.Cells().Any(cells.Contains)).ToList();
		}

		public static HashSet<(int Row, int Column)> FreedBy(IEnumerable<DeckPiece> pieces, ISet<(int Row, int Column)> cells)
		{
			return new HashSet<(int, int)>(pieces.Where((DeckPiece piece) => cells != null && piece.Cells().Any(cells.Contains)).SelectMany((DeckPiece piece) => piece.Cells()));
		}

		public static List<DeckPiece> Split(int rows, int columns, ISet<(int Row, int Column)> cells)
		{
			return Split(rows, columns, cells, int.MaxValue);
		}

		public static List<DeckPiece> SplitPiece(DeckPiece piece)
		{
			HashSet<(int, int)> cells = new HashSet<(int, int)>(piece.Cells());
			return Split(piece.Row + piece.Rows, piece.Column + piece.Columns, cells, piece.CellCount - 1);
		}

		public static List<DeckPiece> Split(int rows, int columns, ISet<(int Row, int Column)> cells, int maxCells)
		{
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>(cells);
			List<DeckPiece> list = new List<DeckPiece>();
			(int, int)[] shapes = Shapes;
			for (int i = 0; i < shapes.Length; i++)
			{
				var (num, num2) = shapes[i];
				if (num * num2 > maxCells)
				{
					continue;
				}
				for (int j = 0; j < rows; j++)
				{
					for (int k = 0; k < columns; k++)
					{
						DeckPiece item = new DeckPiece(j, k, num, num2);
						bool flag = true;
						foreach (var item2 in item.Cells())
						{
							if (!hashSet.Contains(item2))
							{
								flag = false;
								break;
							}
						}
						if (!flag)
						{
							continue;
						}
						foreach (var item3 in item.Cells())
						{
							hashSet.Remove(item3);
						}
						list.Add(item);
					}
				}
			}
			return list;
		}
	}
	public enum DeckPlanKind
	{
		Game,
		Empty,
		Pattern,
		Generated
	}
	public sealed record DeckPlan(DeckPlanKind Kind, string Name = null, DeckPattern Pattern = null, int Seed = 0, float Density = 0f, bool EdgesOnly = false)
	{
		public static DeckPlan Game { get; } = new DeckPlan(DeckPlanKind.Game);

		public static DeckPlan Empty { get; } = new DeckPlan(DeckPlanKind.Empty);

		public bool ClearsGameDeck => Kind != DeckPlanKind.Game;

		public bool BuildsDeck
		{
			get
			{
				if (Kind != DeckPlanKind.Pattern)
				{
					return Kind == DeckPlanKind.Generated;
				}
				return true;
			}
		}

		public const char Separator = '|';

		public static DeckPlan FromPattern(string name, DeckPattern pattern)
		{
			return new DeckPlan(DeckPlanKind.Pattern, Clean(name), pattern);
		}

		public static DeckPlan FromGenerator(int seed, float density, bool edgesOnly)
		{
			return new DeckPlan(DeckPlanKind.Generated, "generated", null, seed, density, edgesOnly);
		}

		public string Encode()
		{
			return Kind switch
			{
				DeckPlanKind.Empty => "empty", 
				DeckPlanKind.Pattern => string.Join('|'.ToString(), "pattern", Clean(Name), Pattern.ToString()), 
				DeckPlanKind.Generated => string.Join('|'.ToString(), "generated", Seed.ToString(CultureInfo.InvariantCulture), Density.ToString("0.###", CultureInfo.InvariantCulture), EdgesOnly ? "1" : "0"), 
				_ => "game", 
			};
		}

		public static DeckPlan Decode(string text)
		{
			string[] array = (text ?? string.Empty).Split('|');
			switch (array[0])
			{
			case "empty":
				return Empty;
			case "pattern":
			{
				if (array.Length == 3 && DeckPattern.TryParse(array[2].Replace('/', '\n'), out var pattern, out var _))
				{
					return FromPattern(array[1], pattern);
				}
				break;
			}
			case "generated":
			{
				if (array.Length == 4 && int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					return FromGenerator(result, result2, array[3] == "1");
				}
				break;
			}
			}
			return Game;
		}

		public string Describe()
		{
			return Kind switch
			{
				DeckPlanKind.Pattern => $"layout \"{Name}\" ({Pattern.BlockedCount} blocked cell(s))", 
				DeckPlanKind.Generated => $"generated deck (seed {Seed}, density {Density:0.##}, {(EdgesOnly ? "edges only" : "anywhere")})", 
				_ => Kind.ToString(), 
			};
		}

		private static string Clean(string name)
		{
			if (!string.IsNullOrWhiteSpace(name))
			{
				return name.Replace('|', '_').Trim();
			}
			return "layout";
		}

		[CompilerGenerated]
		private DeckPlan(DeckPlan original)
		{
			Kind = original.Kind;
			Name = original.Name;
			Pattern = original.Pattern;
			Seed = original.Seed;
			Density = original.Density;
			EdgesOnly = original.EdgesOnly;
		}
	}
	public static class HeightRule
	{
		public const float MinimumScale = 0.5f;

		public const float MaximumScale = 4f;

		public static (float Approved, float Maximum) Apply(float approved, float maximum, float approvedScale, float maximumScale)
		{
			float num = ((maximum > 0f) ? (maximum * Clamp(maximumScale)) : maximum);
			if (approved <= 0f)
			{
				return (Approved: approved, Maximum: num);
			}
			float num2 = approved * Clamp(approvedScale);
			if (num > 0f)
			{
				num2 = Math.Min(num2, num);
			}
			return (Approved: num2, Maximum: num);
		}

		public static float VisualHeight(float visualY, float approvedBefore, float approvedAfter)
		{
			if (!(approvedBefore > 0f) || !(approvedAfter > 0f))
			{
				return visualY;
			}
			return visualY + approvedAfter - approvedBefore;
		}

		private static float Clamp(float scale)
		{
			return Math.Min(Math.Max(scale, 0.5f), 4f);
		}
	}
	public sealed class InstanceTracker
	{
		public IntPtr Current { get; private set; }

		public bool Observe(IntPtr instance)
		{
			if (instance == Current)
			{
				return false;
			}
			Current = instance;
			return true;
		}

		public void Forget()
		{
			Current = IntPtr.Zero;
		}
	}
	public sealed record LayoutFitResult(List<int> Variants, int FreedCells);
	public static class LayoutFit
	{
		public static bool Fits(DeckPattern pattern, ISet<(int Row, int Column)> arriving)
		{
			if (arriving != null)
			{
				return !pattern.BlockedCells().Any(arriving.Contains);
			}
			return true;
		}

		public static List<int> FittingVariants(DeckPattern pattern, IReadOnlyList<ISet<(int Row, int Column)>> arrivingPerVariant)
		{
			return (from index in Enumerable.Range(0, arrivingPerVariant.Count)
				where Fits(pattern, arrivingPerVariant[index])
				select index).ToList();
		}

		public static LayoutFitResult ClosestVariants(DeckPattern pattern, IReadOnlyList<ISet<(int Row, int Column)>> arrivingPerVariant)
		{
			IReadOnlyList<DeckPiece> pieces = pattern.Pieces();
			List<HashSet<(int Row, int Column)>> overlaps = arrivingPerVariant.Select((ISet<(int Row, int Column)> arriving) => DeckPieces.FreedBy(pieces, arriving)).ToList();
			if (overlaps.Count == 0)
			{
				return new LayoutFitResult(new List<int>(), 0);
			}
			int fewest = overlaps.Min((HashSet<(int Row, int Column)> overlap) => overlap.Count);
			HashSet<(int Row, int Column)> best = overlaps.First((HashSet<(int Row, int Column)> overlap) => overlap.Count == fewest);
			return new LayoutFitResult((from index in Enumerable.Range(0, overlaps.Count)
				where overlaps[index].SetEquals(best)
				select index).ToList(), fewest);
		}
	}
	public enum LayoutMode
	{
		Game,
		Empty,
		GameVariants,
		FixedVariant,
		Custom,
		Generated,
		Mixed
	}
	public enum CustomOrder
	{
		Random,
		Sequence,
		Fixed
	}
	public readonly struct LayoutPlan
	{
		public LayoutPlanKind Kind { get; }

		public int VariantIndex { get; }

		public static LayoutPlan KeepGame => new LayoutPlan(LayoutPlanKind.KeepGame, -1);

		public static LayoutPlan Empty => new LayoutPlan(LayoutPlanKind.Empty, -1);

		private LayoutPlan(LayoutPlanKind kind, int variantIndex)
		{
			Kind = kind;
			VariantIndex = variantIndex;
		}

		public static LayoutPlan Variant(int index)
		{
			return new LayoutPlan(LayoutPlanKind.Variant, index);
		}

		public override string ToString()
		{
			if (Kind != LayoutPlanKind.Variant)
			{
				return Kind.ToString();
			}
			return "variant " + (VariantIndex + 1);
		}
	}
	public enum LayoutPlanKind
	{
		KeepGame,
		Empty,
		Variant
	}
	public static class LayoutPlanner
	{
		public static LayoutPlan Choose(LayoutMode mode, int poolSize, IReadOnlyList<int> allowedVariants, int fixedVariant, int lastVariantIndex, bool avoidRepeats, Random random)
		{
			if (poolSize <= 0)
			{
				return LayoutPlan.KeepGame;
			}
			switch (mode)
			{
			case LayoutMode.Empty:
				return LayoutPlan.Empty;
			case LayoutMode.FixedVariant:
				return LayoutPlan.Variant(Math.Min(Math.Max(fixedVariant, 1), poolSize) - 1);
			case LayoutMode.GameVariants:
			{
				List<int> list = Candidates(poolSize, allowedVariants);
				if (avoidRepeats && list.Count > 1)
				{
					list.Remove(lastVariantIndex);
				}
				return LayoutPlan.Variant(list[random.Next(list.Count)]);
			}
			default:
				return LayoutPlan.KeepGame;
			}
		}

		public static List<int> Candidates(int poolSize, IReadOnlyList<int> allowedVariants)
		{
			List<int> list = (from variant in allowedVariants
				where variant >= 1 && variant <= poolSize
				select variant - 1).ToList();
			if (list.Count <= 0)
			{
				return Enumerable.Range(0, poolSize).ToList();
			}
			return list;
		}
	}
	public static class PatternGenerator
	{
		public const float MinimumDensity = 0.05f;

		public const float MaximumDensity = 0.6f;

		public const int EdgeWidth = 2;

		private static readonly (int Rows, int Columns, double Weight)[] Shapes = new(int, int, double)[7]
		{
			(3, 3, 1.0),
			(2, 5, 0.5),
			(5, 2, 0.5),
			(2, 2, 4.0),
			(2, 1, 2.0),
			(1, 2, 2.0),
			(1, 1, 1.0)
		};

		public static DeckPattern Generate(int rows, int columns, float density, bool edgesOnly, ISet<(int Row, int Column)> reserved, int seed)
		{
			Random random = new Random(seed);
			bool[,] blocked = new bool[rows, columns];
			int num = (int)Math.Round(Math.Min(Math.Max(density, 0.05f), 0.6f) * (float)rows * (float)columns);
			int num2 = 0;
			List<DeckPiece> list = new List<DeckPiece>();
			foreach (var (row, column) in (from index in Enumerable.Range(0, rows * columns)
				select (Row: index / columns, Column: index % columns) into _
				orderby random.Next()
				select _).ToList())
			{
				if (num2 >= num)
				{
					break;
				}
				foreach (var shape in Shapes.OrderByDescending(((int Rows, int Columns, double Weight) tuple2) => Math.Pow(random.NextDouble(), 1.0 / tuple2.Weight)))
				{
					List<(int, int)> list2 = Enumerable.Range(0, shape.Rows).SelectMany((int dr) => from dc in Enumerable.Range(0, shape.Columns)
						select (Row: row + dr, Column: column + dc)).ToList();
					if (num2 + list2.Count > num + 1 || !list2.All<(int, int)>(((int Row, int Column) cell) => Allowed(cell.Row, cell.Column)))
					{
						continue;
					}
					foreach (var item in list2)
					{
						blocked[item.Item1, item.Item2] = true;
					}
					num2 += list2.Count;
					list.Add(new DeckPiece(row, column, shape.Rows, shape.Columns));
					break;
				}
			}
			return new DeckPattern(blocked, list);
			bool Allowed(int num3, int num4)
			{
				if (num3 >= 0 && num4 >= 0 && num3 < rows && num4 < columns && !blocked[num3, num4] && (reserved == null || !reserved.Contains((num3, num4))))
				{
					if (edgesOnly)
					{
						return IsEdge(num3, num4, rows, columns);
					}
					return true;
				}
				return false;
			}
		}

		public static bool IsEdge(int row, int column, int rows, int columns)
		{
			if (row >= 2 && column >= 2 && row < rows - 2)
			{
				return column >= columns - 2;
			}
			return true;
		}
	}
	public sealed class PatternLibrary
	{
		public const string Extension = ".txt";

		public string Directory { get; }

		public PatternLibrary(string directory)
		{
			Directory = directory;
		}

		public IReadOnlyList<(string Name, DeckPattern Pattern)> Load(Action<string> warn)
		{
			System.IO.Directory.CreateDirectory(Directory);
			List<(string, DeckPattern)> list = new List<(string, DeckPattern)>();
			foreach (string item in System.IO.Directory.GetFiles(Directory, "*.txt").OrderBy<string, string>((string path) => path, StringComparer.OrdinalIgnoreCase))
			{
				string text;
				try
				{
					text = File.ReadAllText(item);
				}
				catch (IOException ex)
				{
					warn?.Invoke(Path.GetFileName(item) + " could not be read: " + ex.Message);
					continue;
				}
				if (DeckPattern.TryParse(text, out var pattern, out var error))
				{
					list.Add((Path.GetFileNameWithoutExtension(item), pattern));
				}
				else
				{
					warn?.Invoke(Path.GetFileName(item) + " is skipped: " + error);
				}
			}
			return list;
		}

		public string Save(DeckPattern pattern, IEnumerable<string> notes, DateTime now)
		{
			System.IO.Directory.CreateDirectory(Directory);
			string text = "deck_" + now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
			string text2 = text;
			int num = 2;
			while (File.Exists(Path.Combine(Directory, text2 + ".txt")))
			{
				text2 = text + "_" + num.ToString(CultureInfo.InvariantCulture);
				num++;
			}
			IEnumerable<string> contents = (notes ?? Enumerable.Empty<string>()).Where((string note) => !DeckPattern.IsGridLine(note)).Concat(pattern.RowTexts());
			File.WriteAllLines(Path.Combine(Directory, text2 + ".txt"), contents);
			return text2;
		}
	}
	public static class PrefillFootprint
	{
		public static HashSet<(int Row, int Column)> Parse(string json)
		{
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			if (string.IsNullOrWhiteSpace(json))
			{
				return hashSet;
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(json);
				if (!jsonDocument.RootElement.TryGetProperty("storedEntities", out var value) || value.ValueKind != JsonValueKind.Array)
				{
					return hashSet;
				}
				foreach (JsonElement item in value.EnumerateArray())
				{
					if (!item.TryGetProperty("footprintCells", out var value2) || value2.ValueKind != JsonValueKind.Array)
					{
						continue;
					}
					foreach (JsonElement item2 in value2.EnumerateArray())
					{
						if (item2.TryGetProperty("x", out var value3) && item2.TryGetProperty("y", out var value4))
						{
							hashSet.Add((value3.GetInt32(), value4.GetInt32()));
						}
					}
				}
			}
			catch (JsonException)
			{
				hashSet.Clear();
			}
			return hashSet;
		}
	}
	public static class VariantList
	{
		public static IReadOnlyList<int> Parse(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return Array.Empty<int>();
			}
			int result;
			return (from number in (from part in text.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)
					select int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out result) ? result : 0 into number
					where number > 0
					select number).Distinct()
				orderby number
				select number).ToList();
		}
	}
}