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();
}
}
}