Decompiled source of ShelfLabels v0.2.0

plugins/ShelfLabels.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Configuration;
using CatLib.Config;
using CatLib.Core;
using CatLib.Game.Events;
using CatLib.Localization;
using CatLib.Logging;
using CatLib.Net;
using CatLib.Saves;
using CatLib.UI;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Microsoft.CodeAnalysis;
using ShelfLabels.Data;
using ShelfLabels.Logic;
using ShelfLabels.Patches;
using ShelfLabels.Scene;
using ShelfLabels.Settings;
using ShelfLabels.Sync;
using UnityEngine;
using UnityEngine.Rendering;

[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.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+4162013e7c932bfa1cf0340558d83186f1fd5817")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("ShelfLabels")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
	[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 ShelfLabels
{
	public sealed class LabelsController
	{
		public const float ScanIntervalSeconds = 2f;

		public const float LookDistance = 6f;

		public const float NoticeSeconds = 3f;

		public const int MaxParentSteps = 8;

		private readonly Dictionary<IntPtr, LabelView> _views = new Dictionary<IntPtr, LabelView>();

		private readonly Dictionary<IntPtr, (LabelView View, int Slot)> _actions = new Dictionary<IntPtr, (LabelView, int)>();

		private readonly Dictionary<IntPtr, LabelView> _roots = new Dictionary<IntPtr, LabelView>();

		private readonly CatLogger _log;

		private readonly ModChannel _channel;

		private readonly LabelBoard _board;

		private readonly PlacementBoard _placements;

		private readonly StandBoard _stands;

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

		private readonly TextCatalog _texts;

		private readonly LabelSound _sound;

		private float _nextScan;

		private bool _layoutDirty = true;

		private SessionRole _lastRole;

		private int _shownSlots = -1;

		private bool _inLevel;

		private int _noticeLabel;

		private float _noticeUntil;

		private bool _noticeStand;

		public LabelSettings Settings { get; set; }

		public LabelSync Sync { get; set; }

		private bool IsPaused => !CatNetwork.IsActive("catlib.shelflabels");

		public LabelsController(CatLogger log, ModChannel channel, LabelBoard board, PlacementBoard placements, StandBoard stands, TextCatalog texts)
		{
			_log = log;
			_channel = channel;
			_board = board;
			_placements = placements;
			_stands = stands;
			_texts = texts;
			_sound = new LabelSound(log);
			_board.Changed += OnBoardChanged;
			_placements.Changed += delegate(IReadOnlyList<int> ids)
			{
				OnLayoutChanged(ids, stand: false);
			};
			_stands.Changed += delegate(IReadOnlyList<int> ids)
			{
				OnLayoutChanged(ids, stand: true);
			};
			BootstrapEvents.LevelLoadStarted += Forget;
			BootstrapEvents.GameRestartStarted += Forget;
			BootstrapEvents.LevelLoadFinalized += delegate
			{
				_inLevel = true;
				_nextScan = 0f;
			};
		}

		public void RequestLayout()
		{
			_layoutDirty = true;
			_nextScan = 0f;
		}

		public int SpriteCount(int labelId)
		{
			return _views.Values.FirstOrDefault((LabelView view) => view.LabelId == labelId && view.IsAlive)?.SpriteCount ?? 0;
		}

		public bool HandleClick(IntPtr action, int step, bool fromNetwork)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Invalid comparison between Unknown and I4
			if (!_actions.TryGetValue(action, out (LabelView, int) value))
			{
				return false;
			}
			if (fromNetwork)
			{
				return true;
			}
			LabelSlot labelSlot = new LabelSlot(value.Item1.LabelId, value.Item2);
			LabelSync sync = Sync;
			if (sync != null && sync.Click(labelSlot, step))
			{
				_sound.Play(action, step);
			}
			else
			{
				_log.Info($"The click on extra label {labelSlot} was not sent: {(((int)CatNetwork.Role == 2) ? "the host does not have Shelf Labels" : "the mod is not ready")}");
			}
			return true;
		}

		public void Update()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			if (Settings == null)
			{
				return;
			}
			SessionRole role = CatNetwork.Role;
			if (role != _lastRole)
			{
				if ((int)role == 2)
				{
					_board.Clear();
					_placements.Clear();
					_stands.Clear();
				}
				_lastRole = role;
				_nextScan = 0f;
			}
			if (!_inLevel)
			{
				return;
			}
			if (!IsPaused)
			{
				HandleHotkey();
			}
			if (Time.unscaledTime < _nextScan)
			{
				return;
			}
			_nextScan = Time.unscaledTime + 2f;
			int num = VisibleSlots();
			if (num != _shownSlots)
			{
				_log.Info((num == Settings.Slots.Value) ? $"Showing {num} extra label(s) per shelf label" : (IsPaused ? "Extra labels are hidden: Shelf Labels is paused in this session because not every player has it" : "Extra labels are hidden: the host does not have Shelf Labels"));
				_shownSlots = num;
			}
			Scan();
			bool layoutDirty = _layoutDirty;
			_layoutDirty = false;
			foreach (LabelView item in _views.Values.ToList())
			{
				if (!item.IsAlive)
				{
					Drop(item);
					continue;
				}
				if (layoutDirty)
				{
					item.RemoveAll();
				}
				Refresh(item, num);
			}
			RebuildLookups();
		}

		private int VisibleSlots()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			if (!IsPaused && ((int)CatNetwork.Role != 2 || _channel.CanSendToHost))
			{
				return Settings.Slots.Value;
			}
			return 0;
		}

		private Placement Effective(int labelId)
		{
			return _placements.Get(labelId, Settings.Placement.Value);
		}

		private bool StandHidden(int labelId)
		{
			return _stands.IsHidden(labelId, Settings.HideStands.Value);
		}

		private void Refresh(LabelView view, int slots)
		{
			view.Sync(slots, Effective(view.LabelId), StandHidden(view.LabelId), Settings.Spacing.Value, _log);
			view.Show(_board.Get);
			string standSummary = view.StandSummary;
			if (standSummary != null && _reportedStands.Add(TypeName(((Object)view.Holder).name)))
			{
				_log.Info("Stand of extra labels on " + standSummary);
			}
		}

		private static string TypeName(string name)
		{
			int num = name.LastIndexOf(" (", StringComparison.Ordinal);
			if (num <= 0 || !name.EndsWith(")", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, num);
		}

		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)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = Settings.PlacementHotkey.Value;
			bool flag = ((KeyboardShortcut)(ref value)).IsDown();
			int num;
			if (!flag)
			{
				value = Settings.StandHotkey.Value;
				num = (((KeyboardShortcut)(ref value)).IsDown() ? 1 : 0);
			}
			else
			{
				num = 0;
			}
			bool flag2 = (byte)num != 0;
			if (!flag && !flag2)
			{
				return;
			}
			LabelView labelView = LookedAt();
			if (labelView == null)
			{
				Notifications.Show(_texts.Get("message.lookAtLabel"), (string)null);
				return;
			}
			bool num2;
			if (!flag)
			{
				LabelSync sync = Sync;
				if (sync != null)
				{
					num2 = sync.ToggleStand(labelView.LabelId);
					goto IL_0095;
				}
			}
			else
			{
				LabelSync sync2 = Sync;
				if (sync2 != null)
				{
					num2 = sync2.CyclePlacement(labelView.LabelId);
					goto IL_0095;
				}
			}
			goto IL_0097;
			IL_0097:
			Notifications.Show(_texts.Get("message.noHost"), (string)null);
			return;
			IL_0095:
			if (num2)
			{
				_noticeLabel = labelView.LabelId;
				_noticeStand = flag2;
				_noticeUntil = Time.unscaledTime + 3f;
				return;
			}
			goto IL_0097;
		}

		private LabelView LookedAt()
		{
			//IL_0017: 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_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: 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)
			Camera val = FindCamera();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Il2CppStructArray<RaycastHit> obj = Physics.RaycastAll(((Component)val).transform.position, ((Component)val).transform.forward, 6f, -1, (QueryTriggerInteraction)2);
			List<RaycastHit> list = new List<RaycastHit>();
			foreach (RaycastHit item in (Il2CppArrayBase<RaycastHit>)(object)obj)
			{
				list.Add(item);
			}
			foreach (RaycastHit item2 in list.OrderBy((RaycastHit hit) => ((RaycastHit)(ref hit)).distance))
			{
				RaycastHit current2 = item2;
				Collider collider = ((RaycastHit)(ref current2)).collider;
				if ((Object)(object)collider == (Object)null || (Object)(object)((Component)collider).GetComponentInParent<PlayerEntityBase>() != (Object)null)
				{
					continue;
				}
				Transform val2 = ((Component)collider).transform;
				for (int num = 0; num < 8; num++)
				{
					if (!((Object)(object)val2 != (Object)null))
					{
						break;
					}
					if (_roots.TryGetValue(((Il2CppObjectBase)val2).Pointer, out var value))
					{
						return value;
					}
					val2 = val2.parent;
				}
				if (!collider.isTrigger)
				{
					return null;
				}
			}
			return null;
		}

		private static Camera FindCamera()
		{
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null)
			{
				return main;
			}
			Camera val = null;
			foreach (Camera item in (Il2CppArrayBase<Camera>)(object)Camera.allCameras)
			{
				if ((Object)(object)item != (Object)null && ((Behaviour)item).isActiveAndEnabled && (Object)(object)item.targetTexture == (Object)null && ((Object)(object)val == (Object)null || item.depth > val.depth))
				{
					val = item;
				}
			}
			return val;
		}

		private void Scan()
		{
			Il2CppReferenceArray<Object> obj = Object.FindObjectsByType(Il2CppType.Of<EntityStorageLabel>(), (FindObjectsInactive)0, (FindObjectsSortMode)0);
			int num = 0;
			foreach (Object item in (Il2CppArrayBase<Object>)(object)obj)
			{
				EntityStorageLabel val = ((Il2CppObjectBase)item).TryCast<EntityStorageLabel>();
				if (!((Object)(object)val == (Object)null) && !_views.ContainsKey(((Il2CppObjectBase)val).Pointer) && ((Behaviour)val).enabled && !((Object)(object)((Component)val).transform.parent == (Object)null) && !((Object)((Component)val).transform.parent).name.EndsWith(" [ShelfLabels]", StringComparison.Ordinal))
				{
					EntityPermanentIdentifier component = ((Component)val).GetComponent<EntityPermanentIdentifier>();
					int num2 = ((!((Object)(object)component == (Object)null)) ? component.PermanentIdentifier : 0);
					if (num2 > 0)
					{
						_views[((Il2CppObjectBase)val).Pointer] = new LabelView(val, num2);
						num++;
					}
				}
			}
			if (num > 0)
			{
				_log.Info($"Found {num} new shelf label(s), {_views.Count} in total");
			}
		}

		private void RebuildLookups()
		{
			_actions.Clear();
			_roots.Clear();
			foreach (LabelView value in _views.Values)
			{
				foreach (IntPtr action in value.Actions)
				{
					if (value.TryGetSlot(action, out var slot))
					{
						_actions[action] = (value, slot);
					}
				}
				foreach (IntPtr root in value.Roots)
				{
					_roots[root] = value;
				}
			}
		}

		private void OnBoardChanged(IReadOnlyList<LabelSlot> changed)
		{
			HashSet<int> ids = new HashSet<int>(changed.Select((LabelSlot slot) => slot.LabelId));
			foreach (LabelView item in _views.Values.Where((LabelView view) => ids.Contains(view.LabelId)))
			{
				item.Show(_board.Get);
			}
		}

		private void OnLayoutChanged(IReadOnlyList<int> changed, bool stand)
		{
			if (Settings == null)
			{
				return;
			}
			HashSet<int> ids = new HashSet<int>(changed);
			if (_inLevel)
			{
				int slots = VisibleSlots();
				foreach (LabelView item in _views.Values.Where((LabelView view) => ids.Contains(view.LabelId) && view.IsAlive))
				{
					Refresh(item, slots);
				}
				RebuildLookups();
			}
			if (_noticeLabel == 0 || _noticeStand != stand || !ids.Contains(_noticeLabel) || Time.unscaledTime > _noticeUntil)
			{
				return;
			}
			if (!stand)
			{
				Notifications.Show(_texts.Format("message.placement", new object[1] { _texts.Get(SettingTexts.EnumKey((object)Effective(_noticeLabel))) }), (string)null);
			}
			else if (!StandHidden(_noticeLabel))
			{
				Notifications.Show(_texts.Get("message.standShown"), (string)null);
			}
			else
			{
				string text = _views.Values.FirstOrDefault((LabelView view) => view.LabelId == _noticeLabel)?.StandProblem;
				if (text != null)
				{
					_log.Warning($"The stand of label {_noticeLabel} cannot be removed: {text}");
				}
				Notifications.Show(_texts.Get((text == null) ? "message.standHidden" : "message.standUnsupported"), (string)null);
			}
			_noticeLabel = 0;
		}

		private void Drop(LabelView view)
		{
			view.RemoveAll();
			_views.Remove(view.Pointer);
		}

		private void Forget()
		{
			_views.Clear();
			_actions.Clear();
			_roots.Clear();
			_inLevel = false;
			_nextScan = 0f;
		}
	}
	[BepInPlugin("catlib.shelflabels", "Shelf Labels", "0.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class ShelfLabelsPlugin : BasePlugin
	{
		public const string LanguageResourcePrefix = "ShelfLabels.Lang.";

		public const int DataVersion = 1;

		private LabelsController _controller;

		private LabelStore _store;

		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);
			LabelBoard board = new LabelBoard();
			PlacementBoard placements = new PlacementBoard();
			StandBoard stands = new StandBoard();
			ModChannel channel = CatNetwork.Channel((BasePlugin)(object)this);
			_controller = new LabelsController(val, channel, board, placements, stands, val2.Texts);
			_controller.Settings = new LabelSettings(val2, _controller.RequestLayout);
			_controller.Sync = new LabelSync(channel, board, placements, stands, _controller.SpriteCount, () => _controller.Settings.Slots.Value, () => _controller.Settings.Placement.Value, () => _controller.Settings.HideStands.Value, val);
			_store = new LabelStore(CatSaves.For((BasePlugin)(object)this, 1), board, placements, stands, val);
			bool flag = LabelClickPatch.Install("catlib.shelflabels", _controller.HandleClick, val);
			FrameLoop.Update += _controller.Update;
			CatNetwork.ActiveModsChanged += _controller.RequestLayout;
			val.Info($"Shelf Labels {"0.2.0"} loaded with {count} translated text(s), clicks {(flag ? "patched" : "not patched")}");
		}
	}
	public static class PluginMeta
	{
		public const string Guid = "catlib.shelflabels";

		public const string Name = "Shelf Labels";

		public const string Version = "0.2.0";
	}
}
namespace ShelfLabels.Sync
{
	public sealed class LabelSync
	{
		public const string StepMessage = "step";

		public const string SetMessage = "set";

		public const string ResetMessage = "reset";

		public const string PlaceMessage = "place";

		public const string LayoutMessage = "layout";

		public const string StandMessage = "stand";

		public const string StandsMessage = "stands";

		public const int ChunkBytes = 12288;

		private readonly ModChannel _channel;

		private readonly LabelBoard _board;

		private readonly PlacementBoard _placements;

		private readonly StandBoard _stands;

		private readonly Func<bool> _hideStandsByDefault;

		private readonly Func<int, int> _spriteCount;

		private readonly Func<int> _slots;

		private readonly Func<Placement> _defaultPlacement;

		private readonly CatLogger _log;

		public int Rejected { get; private set; }

		public LabelSync(ModChannel channel, LabelBoard board, PlacementBoard placements, StandBoard stands, Func<int, int> spriteCount, Func<int> slots, Func<Placement> defaultPlacement, Func<bool> hideStandsByDefault, CatLogger log)
		{
			_channel = channel;
			_board = board;
			_placements = placements;
			_stands = stands;
			_hideStandsByDefault = hideStandsByDefault;
			_spriteCount = spriteCount;
			_slots = slots;
			_defaultPlacement = defaultPlacement;
			_log = log;
			_channel.Received += OnReceived;
			_channel.PeerJoined += delegate(ulong peer)
			{
				SendAll(peer);
			};
		}

		public bool Click(LabelSlot slot, int step)
		{
			if (!slot.IsValid || (step != 1 && step != -1))
			{
				return false;
			}
			return _channel.SendToHost("step", slot.Text + "/" + step.ToString("+0;-0", CultureInfo.InvariantCulture));
		}

		public bool CyclePlacement(int labelId)
		{
			if (labelId > 0)
			{
				return _channel.SendToHost("place", labelId.ToString(CultureInfo.InvariantCulture) + "/+1");
			}
			return false;
		}

		public bool ToggleStand(int labelId)
		{
			if (labelId > 0)
			{
				return _channel.SendToHost("stand", labelId.ToString(CultureInfo.InvariantCulture) + "/toggle");
			}
			return false;
		}

		public int SendAll(ulong peer)
		{
			int num = (_channel.SendTo(peer, "reset", string.Empty) ? 1 : 0);
			foreach (string item in _board.Chunks(12288))
			{
				num += (_channel.SendTo(peer, "set", item) ? 1 : 0);
			}
			foreach (string item2 in _placements.Chunks(12288))
			{
				num += (_channel.SendTo(peer, "layout", item2) ? 1 : 0);
			}
			foreach (string item3 in _stands.Chunks(12288))
			{
				num += (_channel.SendTo(peer, "stands", item3) ? 1 : 0);
			}
			CatLogger log = _log;
			if (log != null)
			{
				log.Info($"Sent {_board.Count} label picture(s), {_placements.Count} placement(s) and {_stands.Count} stand choice(s) to player {peer} in {num} message(s)");
			}
			return num;
		}

		private void OnReceived(ModMessage message)
		{
			if (!message.FromHost)
			{
				if (message.Name == "place")
				{
					HandlePlace(message);
				}
				else if (message.Name == "stand")
				{
					HandleStand(message);
				}
				else
				{
					HandleStep(message);
				}
				return;
			}
			switch (message.Name)
			{
			case "reset":
				_board.Clear();
				_placements.Clear();
				_stands.Clear();
				return;
			case "stands":
				_stands.Apply(StandBoard.Parse(message.Text));
				return;
			case "set":
				_board.Apply(LabelBoard.Parse(message.Text));
				return;
			case "layout":
				_placements.Apply(PlacementBoard.Parse(message.Text));
				return;
			}
			CatLogger log = _log;
			if (log != null)
			{
				log.Debug("Ignored an unknown label message " + message.Name);
			}
		}

		private void HandleStep(ModMessage message)
		{
			if (message.Name != "step" || !TryParseStep(message.Text, out var slot, out var step))
			{
				Reject(message, "malformed request");
				return;
			}
			if (slot.Slot > _slots())
			{
				Reject(message, $"slot {slot.Slot} is not shown, {_slots()} extra label(s) per shelf");
				return;
			}
			int num = _spriteCount(slot.LabelId);
			if (num <= 0)
			{
				Reject(message, $"label {slot.LabelId} is unknown here");
			}
			else
			{
				int num2 = _board.Step(slot, step, num);
				_channel.Broadcast("set", slot.Text + "=" + num2.ToString(CultureInfo.InvariantCulture));
			}
		}

		private void HandlePlace(ModMessage message)
		{
			string text = message.Text;
			int num = text.IndexOf('/');
			if (num <= 0 || text.Substring(num) != "/+1" || !int.TryParse(text.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0)
			{
				Reject(message, "malformed placement request");
			}
			else if (_spriteCount(result) <= 0)
			{
				Reject(message, $"label {result} is unknown here");
			}
			else
			{
				Placement placement = PlacementBoard.Next(_placements.Get(result, _defaultPlacement()));
				_placements.Set(result, placement);
				_channel.Broadcast("layout", PlacementBoard.Format(result, placement));
			}
		}

		private void HandleStand(ModMessage message)
		{
			string text = message.Text;
			int num = text.IndexOf('/');
			if (num <= 0 || text.Substring(num) != "/toggle" || !int.TryParse(text.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0)
			{
				Reject(message, "malformed stand request");
			}
			else if (_spriteCount(result) <= 0)
			{
				Reject(message, $"label {result} is unknown here");
			}
			else
			{
				bool hidden = !_stands.IsHidden(result, _hideStandsByDefault());
				_stands.Set(result, hidden);
				_channel.Broadcast("stands", StandBoard.Format(result, hidden));
			}
		}

		private void Reject(ModMessage message, string reason)
		{
			Rejected++;
			CatLogger log = _log;
			if (log != null)
			{
				log.Warning($"Ignored a label request \"{message.Text}\" from {message.Sender}: {reason}");
			}
		}

		public static bool TryParseStep(string text, out LabelSlot slot, out int step)
		{
			slot = default(LabelSlot);
			step = 0;
			int num = text?.LastIndexOf('/') ?? (-1);
			if (num <= 0 || !LabelSlot.TryParse(text.Substring(0, num), out slot))
			{
				return false;
			}
			string text2 = text.Substring(num + 1);
			int num2 = ((text2 == "+1") ? 1 : ((text2 == "-1") ? (-1) : 0));
			step = num2;
			return step != 0;
		}
	}
}
namespace ShelfLabels.Settings
{
	public sealed class LabelSettings
	{
		public Setting<int> Slots { get; }

		public Setting<Placement> Placement { get; }

		public Setting<float> Spacing { get; }

		public Setting<KeyboardShortcut> PlacementHotkey { get; }

		public Setting<bool> HideStands { get; }

		public Setting<KeyboardShortcut> StandHotkey { get; }

		public LabelSettings(CatSettings settings, Action changed)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			Slots = settings.Session<int>("Labels", "ExtraSlots", 1, "How many extra labels each shelf label gets. Pictures of hidden labels are kept in the save and come back when they are shown again.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3));
			Placement = settings.Session<Placement>("Labels", "DefaultPlacement", ShelfLabels.Logic.Placement.Auto, "Where extra labels go on shelves that have no placement of their own. Each shelf label can get its own placement with the hotkey.", (AcceptableValueBase)null);
			HideStands = settings.Session<bool>("Labels", "HideStands", false, "Extra labels on shelves without a choice of their own are shown without the wooden stand under the frame. The game's own label always keeps it.", (AcceptableValueBase)null);
			Spacing = settings.Local<float>("Look", "Spacing", 0.01f, "Gap between the labels in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.1f));
			PlacementHotkey = settings.Local<KeyboardShortcut>("Look", "PlacementHotkey", new KeyboardShortcut((KeyCode)108, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Look at a shelf label or its extra label and press this to switch where its extra labels go. Every player can do it, the host keeps it in the save.", (AcceptableValueBase)null);
			StandHotkey = settings.Local<KeyboardShortcut>("Look", "StandHotkey", new KeyboardShortcut((KeyCode)107, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Look at a shelf label or its extra label and press this to show or hide the wooden stand of its extra labels. Every player can do it, the host keeps it in the save.", (AcceptableValueBase)null);
			Slots.Apply((Action<int>)delegate
			{
				changed();
			});
			Placement.Apply((Action<Placement>)delegate
			{
				changed();
			});
			HideStands.Apply((Action<bool>)delegate
			{
				changed();
			});
			Spacing.Apply((Action<float>)delegate
			{
				changed();
			});
		}
	}
}
namespace ShelfLabels.Scene
{
	public sealed class LabelSound
	{
		private readonly CatLogger _log;

		private bool _reported;

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

		public void Play(IntPtr actionPointer, int step)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!(actionPointer == IntPtr.Zero) && Singleton<AudioManager>.HasInstance())
				{
					EntityInteractableAction val = new EntityInteractableAction(actionPointer);
					AudioAsset_ScriptableObject val2 = ((step < 0) ? (val.SecondaryInteractionAudioAsset ?? val.PrimaryInteractionAudioAsset) : val.PrimaryInteractionAudioAsset);
					if (!((Object)(object)val2 == (Object)null))
					{
						Singleton<AudioManager>.Instance.PlayOneShot(val2, ((Component)val).transform.position, 1f, 1f, -1);
					}
				}
			}
			catch (Exception ex)
			{
				if (!_reported)
				{
					_reported = true;
					_log.Warning("Playing the click sound of an extra label failed, extra labels stay silent: " + ex.Message);
				}
			}
		}
	}
	public sealed class LabelView
	{
		private sealed class StandPlan
		{
			public List<string> Hide { get; } = new List<string>();

			public List<(string Path, Mesh Mesh)> Replace { get; } = new List<(string, Mesh)>();

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

			public string Problem { get; set; }

			public string Summary { get; set; }

			public void Apply(Transform root)
			{
				if (Problem != null)
				{
					return;
				}
				foreach (string item3 in Hide)
				{
					Transform val = (string.IsNullOrEmpty(item3) ? root : root.Find(item3));
					Renderer val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent<Renderer>());
					if ((Object)(object)val2 != (Object)null)
					{
						val2.enabled = false;
					}
				}
				foreach (var item4 in Replace)
				{
					string item = item4.Path;
					Mesh item2 = item4.Mesh;
					Transform val3 = (string.IsNullOrEmpty(item) ? root : root.Find(item));
					MeshFilter val4 = (((Object)(object)val3 == (Object)null) ? null : ((Component)val3).GetComponent<MeshFilter>());
					if ((Object)(object)val4 != (Object)null && (Object)(object)item2 != (Object)null && !((Il2CppObjectBase)item2).WasCollected)
					{
						val4.sharedMesh = item2;
					}
				}
			}
		}

		private sealed class Copy
		{
			public GameObject Root { get; }

			public SpriteRenderer Renderer { get; }

			public IntPtr Action { get; }

			public Copy(GameObject root, SpriteRenderer renderer, IntPtr action)
			{
				Root = root;
				Renderer = renderer;
				Action = action;
			}
		}

		public const string CopySuffix = " [ShelfLabels]";

		public const float ProbeShrink = 0.4f;

		public const float StandMargin = 0.002f;

		private readonly SortedDictionary<int, Copy> _copies = new SortedDictionary<int, Copy>();

		private Placement _resolved;

		private float _width;

		private float _height;

		private Vector3 _right;

		private Vector3 _up;

		private Vector3 _normal;

		private float _standCut = float.NaN;

		private StandPlan _standPlan;

		public EntityStorageLabel Original { get; }

		public IntPtr Pointer { get; }

		public Transform Holder { get; }

		public int LabelId { get; }

		public int CopyCount => _copies.Count;

		public Placement Resolved => _resolved;

		public Placement Applied { get; private set; } = (Placement)(-1);

		public bool? AppliedStandHidden { get; private set; }

		public string StandProblem => _standPlan?.Problem;

		public string StandSummary => _standPlan?.Summary;

		public IEnumerable<IntPtr> Roots
		{
			get
			{
				yield return ((Il2CppObjectBase)Holder).Pointer;
				foreach (Copy value in _copies.Values)
				{
					if ((Object)(object)value.Root != (Object)null && !((Il2CppObjectBase)value.Root).WasCollected)
					{
						yield return ((Il2CppObjectBase)value.Root.transform).Pointer;
					}
				}
			}
		}

		public bool IsAlive
		{
			get
			{
				if ((Object)(object)Original != (Object)null && !((Il2CppObjectBase)Original).WasCollected && (Object)(object)Holder != (Object)null)
				{
					return !((Il2CppObjectBase)Holder).WasCollected;
				}
				return false;
			}
		}

		public int SpriteCount => ((Il2CppArrayBase<Sprite>)(object)(IsAlive ? Original.PossibleSprites : null))?.Length ?? 0;

		public IEnumerable<IntPtr> Actions
		{
			get
			{
				foreach (Copy value in _copies.Values)
				{
					yield return value.Action;
				}
			}
		}

		public LabelView(EntityStorageLabel original, int labelId)
		{
			Original = original;
			Pointer = ((Il2CppObjectBase)original).Pointer;
			Holder = ((Component)original).transform.parent;
			LabelId = labelId;
		}

		public bool TryGetSlot(IntPtr action, out int slot)
		{
			foreach (KeyValuePair<int, Copy> copy in _copies)
			{
				if (copy.Value.Action == action)
				{
					slot = copy.Key;
					return true;
				}
			}
			slot = 0;
			return false;
		}

		public void Sync(int count, Placement placement, bool hideStand, float spacing, CatLogger log)
		{
			if (!IsAlive)
			{
				return;
			}
			if (placement != Applied || hideStand != AppliedStandHidden)
			{
				RemoveAll();
				Applied = placement;
				AppliedStandHidden = hideStand;
			}
			if (placement == Placement.None)
			{
				count = 0;
			}
			for (int num = 3; num > count; num--)
			{
				Remove(num);
			}
			if (count == 0)
			{
				return;
			}
			if (_copies.Count == 0)
			{
				Measure();
				_resolved = Resolve(placement, spacing);
			}
			if (hideStand && _standPlan == null)
			{
				_standPlan = PlanStand(log);
			}
			for (int i = 1; i <= count; i++)
			{
				if (!_copies.ContainsKey(i))
				{
					Create(i, spacing, hideStand ? _standPlan : null, log);
				}
			}
		}

		public void Show(Func<LabelSlot, int> picture)
		{
			if (!IsAlive)
			{
				return;
			}
			Il2CppReferenceArray<Sprite> possibleSprites = Original.PossibleSprites;
			foreach (KeyValuePair<int, Copy> copy in _copies)
			{
				SpriteRenderer renderer = copy.Value.Renderer;
				if (!((Object)(object)renderer == (Object)null) && !((Il2CppObjectBase)renderer).WasCollected && possibleSprites != null && ((Il2CppArrayBase<Sprite>)(object)possibleSprites).Length != 0)
				{
					int num = picture(new LabelSlot(LabelId, copy.Key));
					renderer.sprite = ((Il2CppArrayBase<Sprite>)(object)possibleSprites)[(num >= 0 && num < ((Il2CppArrayBase<Sprite>)(object)possibleSprites).Length) ? num : 0];
				}
			}
		}

		public void RemoveAll()
		{
			foreach (int item in new List<int>(_copies.Keys))
			{
				Remove(item);
			}
		}

		private void Remove(int slot)
		{
			if (_copies.TryGetValue(slot, out var value))
			{
				_copies.Remove(slot);
				if ((Object)(object)value.Root != (Object)null && !((Il2CppObjectBase)value.Root).WasCollected)
				{
					Object.Destroy((Object)(object)value.Root);
				}
			}
		}

		private void Create(int slot, float spacing, StandPlan stand, CatLogger log)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("ShelfLabels holder");
			val.SetActive(false);
			val.transform.SetParent(Holder.parent, false);
			GameObject val2 = null;
			try
			{
				val2 = Object.Instantiate<GameObject>(((Component)Holder).gameObject, val.transform);
				((Object)val2).name = ((Object)Holder).name + " " + slot + " [ShelfLabels]";
				Strip(val2);
				EntityStorageLabel componentInChildren = val2.GetComponentInChildren<EntityStorageLabel>(true);
				if ((Object)(object)componentInChildren == (Object)null)
				{
					throw new InvalidOperationException("the copy has no label component");
				}
				((Behaviour)componentInChildren).enabled = false;
				SpriteRenderer illustrationSpriteRenderer = componentInChildren.IllustrationSpriteRenderer;
				EntityInteractableAction componentInChildren2 = val2.GetComponentInChildren<EntityInteractableAction>(true);
				if ((Object)(object)illustrationSpriteRenderer == (Object)null || (Object)(object)componentInChildren2 == (Object)null)
				{
					throw new InvalidOperationException("the copy has no picture or no interaction");
				}
				stand?.Apply(val2.transform);
				val2.transform.SetParent(Holder.parent, false);
				val2.transform.localPosition = Holder.localPosition;
				val2.transform.localRotation = Holder.localRotation;
				val2.transform.localScale = Holder.localScale;
				var (num, num2) = SlotLayout.Offset(slot, _resolved, _width, _height, spacing);
				val2.transform.position = Holder.position + _right * num + _up * num2;
				_copies[slot] = new Copy(val2, illustrationSpriteRenderer, ((Il2CppObjectBase)componentInChildren2).Pointer);
				val2 = null;
			}
			catch (Exception ex)
			{
				log.Error($"Creating extra label {slot} for label {LabelId} ({((Object)Holder).name}) failed", ex);
			}
			finally
			{
				if ((Object)(object)val2 != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val2);
				}
				Object.DestroyImmediate((Object)(object)val);
			}
		}

		private static void Strip(GameObject clone)
		{
			List<Component> list = new List<Component>();
			foreach (EntityPermanentIdentifier componentsInChild in clone.GetComponentsInChildren<EntityPermanentIdentifier>(true))
			{
				list.Add((Component)(object)componentsInChild);
			}
			foreach (EntityNetwork componentsInChild2 in clone.GetComponentsInChildren<EntityNetwork>(true))
			{
				list.Add((Component)(object)componentsInChild2);
			}
			foreach (Component item in list)
			{
				Object.DestroyImmediate((Object)(object)item);
			}
		}

		private void Measure()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0043: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: 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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			SpriteRenderer illustrationSpriteRenderer = Original.IllustrationSpriteRenderer;
			Transform val = (((Object)(object)illustrationSpriteRenderer == (Object)null) ? Holder : ((Component)illustrationSpriteRenderer).transform);
			_right = val.right;
			_up = val.up;
			_normal = val.forward;
			float num = float.MaxValue;
			float num2 = float.MinValue;
			float num3 = float.MaxValue;
			float num4 = float.MinValue;
			Vector3 val2 = default(Vector3);
			foreach (Renderer componentsInChild in ((Component)Holder).GetComponentsInChildren<Renderer>(false))
			{
				if (!((Object)(object)componentsInChild == (Object)null) && componentsInChild.enabled)
				{
					Bounds localBounds = componentsInChild.localBounds;
					Vector3 min = ((Bounds)(ref localBounds)).min;
					Vector3 max = ((Bounds)(ref localBounds)).max;
					for (int i = 0; i < 8; i++)
					{
						((Vector3)(ref val2))..ctor(((i & 1) == 0) ? min.x : max.x, ((i & 2) == 0) ? min.y : max.y, ((i & 4) == 0) ? min.z : max.z);
						Vector3 val3 = ((Component)componentsInChild).transform.TransformPoint(val2) - Holder.position;
						float num5 = Vector3.Dot(val3, _right);
						float num6 = Vector3.Dot(val3, _up);
						num = Mathf.Min(num, num5);
						num2 = Mathf.Max(num2, num5);
						num3 = Mathf.Min(num3, num6);
						num4 = Mathf.Max(num4, num6);
					}
				}
			}
			if (num > num2)
			{
				_width = 0.3f;
				_height = 0.15f;
			}
			else
			{
				_width = num2 - num;
				_height = num4 - num3;
				MeasurePicture(val);
			}
		}

		private void MeasurePicture(Transform pictureTransform)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: 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_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: 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_012a: Unknown result type (might be due to invalid IL or missing references)
			_standCut = float.NaN;
			Sprite val = (((Object)(object)Original.IllustrationSpriteRenderer == (Object)null) ? null : Original.IllustrationSpriteRenderer.sprite);
			Il2CppReferenceArray<Sprite> possibleSprites = Original.PossibleSprites;
			int num = 0;
			while ((Object)(object)val == (Object)null && possibleSprites != null && num < ((Il2CppArrayBase<Sprite>)(object)possibleSprites).Length)
			{
				val = ((Il2CppArrayBase<Sprite>)(object)possibleSprites)[num];
				num++;
			}
			if (!((Object)(object)val == (Object)null))
			{
				Bounds bounds = val.bounds;
				float num2 = float.MaxValue;
				float num3 = float.MinValue;
				float num4 = float.MaxValue;
				Vector3 val2 = default(Vector3);
				for (int i = 0; i < 4; i++)
				{
					((Vector3)(ref val2))..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, 0f);
					Vector3 val3 = pictureTransform.TransformPoint(val2) - Holder.position;
					num2 = Mathf.Min(num2, Vector3.Dot(val3, _right));
					num3 = Mathf.Max(num3, Vector3.Dot(val3, _right));
					num4 = Mathf.Min(num4, Vector3.Dot(val3, _up));
				}
				float num5 = Mathf.Max(0.005f, (_width - (num3 - num2)) * 0.5f);
				_standCut = num4 - num5 * 1.25f - 0.002f;
			}
		}

		private StandPlan PlanStand(CatLogger log)
		{
			//IL_0066: 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_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: 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_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			StandPlan standPlan = new StandPlan();
			if (float.IsNaN(_standCut))
			{
				standPlan.Problem = "the picture of the label could not be measured";
				return standPlan;
			}
			Vector3 val2 = default(Vector3);
			foreach (MeshFilter componentsInChild in ((Component)Holder).GetComponentsInChildren<MeshFilter>(true))
			{
				Mesh val = (((Object)(object)componentsInChild == (Object)null) ? null : componentsInChild.sharedMesh);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Transform transform = ((Component)componentsInChild).transform;
				Bounds bounds = val.bounds;
				float num = float.MaxValue;
				float num2 = float.MinValue;
				for (int i = 0; i < 8; i++)
				{
					((Vector3)(ref val2))..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);
					float num3 = Vector3.Dot(transform.TransformPoint(val2) - Holder.position, _up);
					num = Mathf.Min(num, num3);
					num2 = Mathf.Max(num2, num3);
				}
				string text = PathOf(transform);
				if (num2 < _standCut)
				{
					standPlan.Hide.Add(text);
					standPlan.Notes.Add(text + ": hidden as a separate part");
				}
				else if (!(num >= _standCut))
				{
					Mesh val3;
					string result;
					try
					{
						val3 = StandCutter.Cut(val, transform, Holder.position, _up, _standCut, out result);
					}
					catch (Exception ex)
					{
						val3 = null;
						result = $"{(val.isReadable ? "readable" : "not readable")} mesh with {val.vertexCount} vertices failed: {ex.GetType().Name}: {ex.Message}";
					}
					standPlan.Notes.Add($"{text}: mesh {((Object)val).name}, {result}");
					if ((Object)(object)val3 != (Object)null)
					{
						standPlan.Replace.Add((text, val3));
					}
				}
			}
			if (standPlan.Hide.Count == 0 && standPlan.Replace.Count == 0)
			{
				standPlan.Problem = ((standPlan.Notes.Count == 0) ? "no part of the label reaches below the frame" : string.Join("; ", standPlan.Notes));
			}
			standPlan.Summary = $"{((Object)Holder).name}: cut {_standCut:0.000} m under the holder, {string.Join("; ", standPlan.Notes)}";
			return standPlan;
		}

		private string PathOf(Transform transform)
		{
			List<string> list = new List<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && ((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)Holder).Pointer)
			{
				list.Insert(0, ((Object)val).name);
				val = val.parent;
			}
			return string.Join("/", list);
		}

		private Placement Resolve(Placement placement, float spacing)
		{
			if (placement != Placement.Auto)
			{
				return placement;
			}
			Placement placement2 = SlotLayout.NameSide(((Object)Holder).name);
			Placement side = ((placement2 == Placement.Left) ? Placement.Right : Placement.Left);
			return SlotLayout.ChooseSide(placement2, Blocked(placement2, spacing), Blocked(side, spacing));
		}

		private int Blocked(Placement side, float spacing)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: 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)
			(float Right, float Up) tuple = SlotLayout.Offset(1, side, _width, _height, spacing);
			float item = tuple.Right;
			float item2 = tuple.Up;
			Vector3 val = Holder.position + _right * item + _up * item2;
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(_width * 0.4f, _height * 0.4f, Mathf.Max(_width, _height) * 0.4f * 0.5f);
			Il2CppReferenceArray<Collider> obj = Physics.OverlapBox(val, val2, Quaternion.LookRotation(_normal, _up), -1, (QueryTriggerInteraction)1);
			int num = 0;
			foreach (Collider item3 in (Il2CppArrayBase<Collider>)(object)obj)
			{
				if ((Object)(object)item3 != (Object)null && !((Component)item3).transform.IsChildOf(Holder))
				{
					num++;
				}
			}
			return num;
		}
	}
	public static class StandCutter
	{
		private static readonly Dictionary<IntPtr, Mesh> Cache = new Dictionary<IntPtr, Mesh>();

		public const float BoundsTolerance = 0.01f;

		public static Mesh Cut(Mesh source, Transform transform, Vector3 holderPosition, Vector3 up, float threshold, out string result)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (Cache.TryGetValue(((Il2CppObjectBase)source).Pointer, out var value) && (Object)(object)value != (Object)null && !((Il2CppObjectBase)value).WasCollected)
			{
				result = "cached";
				return value;
			}
			Mesh val = (source.isReadable ? Clip(source, Height, threshold, out result) : FlattenFromGpu(source, Height, Lift, threshold, out result));
			if ((Object)(object)val != (Object)null)
			{
				Cache[((Il2CppObjectBase)source).Pointer] = val;
			}
			return val;
			float Height(Vector3 vertex)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				return Vector3.Dot(transform.TransformPoint(vertex) - holderPosition, up);
			}
			Vector3 Lift(Vector3 vertex, float height)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				return vertex + transform.InverseTransformVector(up * (threshold - height));
			}
		}

		private static Mesh Clip(Mesh source, Func<Vector3, float> height, float threshold, out string result)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			Il2CppStructArray<Vector3> vertices = source.vertices;
			int length = ((Il2CppArrayBase<Vector3>)(object)vertices).Length;
			Vector3[] array = (Vector3[])(object)new Vector3[length];
			float[] array2 = new float[length];
			for (int i = 0; i < length; i++)
			{
				array[i] = ((Il2CppArrayBase<Vector3>)(object)vertices)[i];
				array2[i] = height(array[i]);
			}
			List<int[]> list = new List<int[]>();
			for (int j = 0; j < source.subMeshCount; j++)
			{
				Il2CppStructArray<int> triangles = source.GetTriangles(j);
				int[] array3 = new int[((Il2CppArrayBase<int>)(object)triangles).Length];
				for (int k = 0; k < array3.Length; k++)
				{
					array3[k] = ((Il2CppArrayBase<int>)(object)triangles)[k];
				}
				list.Add(array3);
			}
			MeshClip meshClip = MeshClip.KeepAbove(array2, list, threshold);
			if (!meshClip.Changed)
			{
				result = "nothing below the frame";
				return null;
			}
			int num = length + meshClip.Added.Count;
			Mesh val = new Mesh
			{
				name = ((Object)source).name + " (ShelfLabels, no stand)"
			};
			if (num > 65000)
			{
				val.indexFormat = (IndexFormat)1;
			}
			val.vertices = Il2CppStructArray<Vector3>.op_Implicit(Extend(array, meshClip, (Vector3 a, Vector3 b, float t) => Vector3.Lerp(a, b, t)));
			Vector3[] array4 = Read<Vector3>(source.normals);
			if (array4.Length == length)
			{
				val.normals = Il2CppStructArray<Vector3>.op_Implicit(Extend(array4, meshClip, delegate(Vector3 a, Vector3 b, float t)
				{
					//IL_0000: Unknown result type (might be due to invalid IL or missing references)
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0003: Unknown result type (might be due to invalid IL or missing references)
					//IL_0008: Unknown result type (might be due to invalid IL or missing references)
					//IL_000b: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val2 = Vector3.Lerp(a, b, t);
					return ((Vector3)(ref val2)).normalized;
				}));
			}
			Vector4[] array5 = Read<Vector4>(source.tangents);
			if (array5.Length == length)
			{
				val.tangents = Il2CppStructArray<Vector4>.op_Implicit(Extend<Vector4>(array5, meshClip, delegate(Vector4 a, Vector4 b, float t)
				{
					//IL_0000: 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_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0012: Unknown result type (might be due to invalid IL or missing references)
					//IL_0017: Unknown result type (might be due to invalid IL or missing references)
					//IL_001d: 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_0029: Unknown result type (might be due to invalid IL or missing references)
					//IL_002f: Unknown result type (might be due to invalid IL or missing references)
					//IL_0034: Unknown result type (might be due to invalid IL or missing references)
					//IL_0037: Unknown result type (might be due to invalid IL or missing references)
					//IL_003c: Unknown result type (might be due to invalid IL or missing references)
					//IL_003d: Unknown result type (might be due to invalid IL or missing references)
					//IL_0043: Unknown result type (might be due to invalid IL or missing references)
					//IL_0049: 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_0055: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val2 = Vector3.Lerp(new Vector3(a.x, a.y, a.z), new Vector3(b.x, b.y, b.z), t);
					Vector3 normalized = ((Vector3)(ref val2)).normalized;
					return new Vector4(normalized.x, normalized.y, normalized.z, a.w);
				}));
			}
			Vector2[] array6 = Read<Vector2>(source.uv);
			if (array6.Length == length)
			{
				val.uv = Il2CppStructArray<Vector2>.op_Implicit(Extend(array6, meshClip, (Vector2 a, Vector2 b, float t) => Vector2.Lerp(a, b, t)));
			}
			Vector2[] array7 = Read<Vector2>(source.uv2);
			if (array7.Length == length)
			{
				val.uv2 = Il2CppStructArray<Vector2>.op_Implicit(Extend(array7, meshClip, (Vector2 a, Vector2 b, float t) => Vector2.Lerp(a, b, t)));
			}
			Color[] array8 = Read<Color>(source.colors);
			if (array8.Length == length)
			{
				val.colors = Il2CppStructArray<Color>.op_Implicit(Extend(array8, meshClip, (Color a, Color b, float t) => Color.Lerp(a, b, t)));
			}
			val.subMeshCount = meshClip.Triangles.Length;
			for (int num2 = 0; num2 < meshClip.Triangles.Length; num2++)
			{
				val.SetTriangles(Il2CppStructArray<int>.op_Implicit(meshClip.Triangles[num2]), num2);
			}
			val.RecalculateBounds();
			result = $"removed {meshClip.Removed} and split {meshClip.Split} triangle(s)";
			return val;
		}

		private static Mesh FlattenFromGpu(Mesh source, Func<Vector3, float> height, Func<Vector3, float, Vector3> lift, float threshold, out string result)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: 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_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: 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_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: 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_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Expected O, but got Unknown
			//IL_037f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			int vertexCount = source.vertexCount;
			if ((int)source.GetVertexAttributeFormat((VertexAttribute)0) != 0 || source.GetVertexAttributeDimension((VertexAttribute)0) < 3)
			{
				result = $"positions are stored as {source.GetVertexAttributeFormat((VertexAttribute)0)} x{source.GetVertexAttributeDimension((VertexAttribute)0)}";
				return null;
			}
			int vertexAttributeStream = source.GetVertexAttributeStream((VertexAttribute)0);
			int vertexAttributeOffset = source.GetVertexAttributeOffset((VertexAttribute)0);
			List<byte[]> streams = new List<byte[]>();
			for (int i = 0; i < source.vertexBufferCount; i++)
			{
				int vertexBufferStride = source.GetVertexBufferStride(i);
				streams.Add(ReadBuffer(source.GetVertexBufferImpl(i), vertexBufferStride * vertexCount));
			}
			GraphicsBuffer indexBufferImpl = source.GetIndexBufferImpl();
			int indexSize = (((int)source.indexFormat == 0) ? 2 : 4);
			int indexCount = indexBufferImpl.count * indexBufferImpl.stride / indexSize;
			byte[] data = ReadBuffer(indexBufferImpl, indexCount * indexSize);
			byte[] array = streams[vertexAttributeStream];
			int vertexBufferStride2 = source.GetVertexBufferStride(vertexAttributeStream);
			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);
			int num = 0;
			Vector3 val3 = default(Vector3);
			for (int j = 0; j < vertexCount; j++)
			{
				int num2 = j * vertexBufferStride2 + vertexAttributeOffset;
				((Vector3)(ref val3))..ctor(BitConverter.ToSingle(array, num2), BitConverter.ToSingle(array, num2 + 4), BitConverter.ToSingle(array, num2 + 8));
				val = Vector3.Min(val, val3);
				val2 = Vector3.Max(val2, val3);
				float num3 = height(val3);
				if (!(num3 >= threshold))
				{
					Vector3 val4 = lift(val3, num3);
					BitConverter.GetBytes(val4.x).CopyTo(array, num2);
					BitConverter.GetBytes(val4.y).CopyTo(array, num2 + 4);
					BitConverter.GetBytes(val4.z).CopyTo(array, num2 + 8);
					num++;
				}
			}
			Bounds bounds = source.bounds;
			if (Vector3.Distance(val, ((Bounds)(ref bounds)).min) > 0.01f || Vector3.Distance(val2, ((Bounds)(ref bounds)).max) > 0.01f)
			{
				result = $"the data read from the video card does not match the mesh (bounds {val}..{val2}, expected {((Bounds)(ref bounds)).min}..{((Bounds)(ref bounds)).max})";
				return null;
			}
			if (num == 0)
			{
				result = "nothing below the frame";
				return null;
			}
			Mesh mesh = new Mesh
			{
				name = ((Object)source).name + " (ShelfLabels, no stand)"
			};
			mesh.SetVertexBufferParams(vertexCount, source.GetVertexAttributes());
			int stream;
			for (stream = 0; stream < streams.Count; stream++)
			{
				Upload(streams[stream], delegate(IntPtr pointer)
				{
					mesh.InternalSetVertexBufferData(stream, pointer, 0, 0, streams[stream].Length, 1, (MeshUpdateFlags)1);
				});
			}
			mesh.SetIndexBufferParams(indexCount, source.indexFormat);
			Upload(data, delegate(IntPtr pointer)
			{
				mesh.InternalSetIndexBufferData(pointer, 0, 0, indexCount, indexSize, (MeshUpdateFlags)1);
			});
			mesh.subMeshCount = source.subMeshCount;
			for (int num4 = 0; num4 < source.subMeshCount; num4++)
			{
				mesh.SetSubMesh(num4, source.GetSubMesh(num4), (MeshUpdateFlags)0);
			}
			mesh.RecalculateBounds();
			result = $"lifted {num} of {vertexCount} vertices to the cut, read from the video card";
			return mesh;
		}

		private static byte[] ReadBuffer(GraphicsBuffer buffer, int bytes)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			try
			{
				Il2CppStructArray<byte> val = new Il2CppStructArray<byte>((long)bytes);
				buffer.InternalGetData(new Array(((Il2CppObjectBase)val).Pointer), 0, 0, bytes, 1);
				byte[] array = new byte[bytes];
				for (int i = 0; i < bytes; i++)
				{
					array[i] = ((Il2CppArrayBase<byte>)(object)val)[i];
				}
				return array;
			}
			finally
			{
				buffer.Release();
			}
		}

		private static void Upload(byte[] data, Action<IntPtr> upload)
		{
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				upload(gCHandle.AddrOfPinnedObject());
			}
			finally
			{
				gCHandle.Free();
			}
		}

		private static T[] Read<T>(Il2CppStructArray<T> source) where T : unmanaged
		{
			if (source == null)
			{
				return Array.Empty<T>();
			}
			T[] array = new T[((Il2CppArrayBase<T>)(object)source).Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = ((Il2CppArrayBase<T>)(object)source)[i];
			}
			return array;
		}

		private static T[] Extend<T>(T[] source, MeshClip clip, Func<T, T, float, T> lerp)
		{
			T[] array = new T[source.Length + clip.Added.Count];
			Array.Copy(source, array, source.Length);
			for (int i = 0; i < clip.Added.Count; i++)
			{
				var (num, num2, arg) = clip.Added[i];
				array[source.Length + i] = lerp(source[num], source[num2], arg);
			}
			return array;
		}
	}
}
namespace ShelfLabels.Patches
{
	public static class LabelClickPatch
	{
		private static Func<IntPtr, int, bool, bool> _handler;

		private static CatLogger _log;

		public static bool Install(string harmonyId, Func<IntPtr, int, bool, bool> handler, CatLogger log)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			_handler = handler;
			_log = log;
			try
			{
				Harmony val = new Harmony(harmonyId);
				val.Patch((MethodBase)AccessTools.Method(typeof(EntityInteractableAction), "Interact", (Type[])null, (Type[])null), new HarmonyMethod(typeof(LabelClickPatch), "InteractPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)AccessTools.Method(typeof(EntityInteractableAction), "InteractSecondary", (Type[])null, (Type[])null), new HarmonyMethod(typeof(LabelClickPatch), "InteractSecondaryPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception ex)
			{
				log.Error("Patching label clicks failed, extra labels cannot be changed", ex);
				return false;
			}
		}

		private static bool InteractPrefix(EntityInteractableAction __instance, bool is_from_network)
		{
			return Handle(__instance, 1, is_from_network);
		}

		private static bool InteractSecondaryPrefix(EntityInteractableAction __instance, bool is_from_network)
		{
			return Handle(__instance, -1, is_from_network);
		}

		private static bool Handle(EntityInteractableAction action, int step, bool fromNetwork)
		{
			try
			{
				return (Object)(object)action == (Object)null || _handler == null || !_handler(((Il2CppObjectBase)action).Pointer, step, fromNetwork);
			}
			catch (Exception ex)
			{
				CatLogger log = _log;
				if (log != null)
				{
					log.Error("Handling a click on an extra label failed", ex);
				}
				return true;
			}
		}
	}
}
namespace ShelfLabels.Logic
{
	public sealed class LabelBoard
	{
		public const int MaxSpriteIndex = 255;

		private readonly Dictionary<LabelSlot, int> _pictures = new Dictionary<LabelSlot, int>();

		public int Count => _pictures.Count;

		public IReadOnlyList<KeyValuePair<LabelSlot, int>> Entries => (from pair in _pictures
			orderby pair.Key.LabelId, pair.Key.Slot
			select pair).ToList();

		public event Action<IReadOnlyList<LabelSlot>> Changed;

		public int Get(LabelSlot slot)
		{
			if (!_pictures.TryGetValue(slot, out var value))
			{
				return 0;
			}
			return value;
		}

		public bool Has(LabelSlot slot)
		{
			return _pictures.ContainsKey(slot);
		}

		public bool Set(LabelSlot slot, int index)
		{
			return Apply(new KeyValuePair<LabelSlot, int>[1]
			{
				new KeyValuePair<LabelSlot, int>(slot, index)
			}) > 0;
		}

		public int Apply(IEnumerable<KeyValuePair<LabelSlot, int>> entries)
		{
			List<LabelSlot> list = new List<LabelSlot>();
			foreach (var (labelSlot2, num2) in entries)
			{
				if (labelSlot2.IsValid && num2 >= 0 && num2 <= 255 && (!_pictures.TryGetValue(labelSlot2, out var value) || value != num2))
				{
					_pictures[labelSlot2] = num2;
					list.Add(labelSlot2);
				}
			}
			if (list.Count > 0)
			{
				this.Changed?.Invoke(list);
			}
			return list.Count;
		}

		public void Clear()
		{
			if (_pictures.Count != 0)
			{
				List<LabelSlot> obj = _pictures.Keys.ToList();
				_pictures.Clear();
				this.Changed?.Invoke(obj);
			}
		}

		public int Step(LabelSlot slot, int step, int spriteCount)
		{
			if (spriteCount <= 0)
			{
				return -1;
			}
			int num = ((Get(slot) + step) % spriteCount + spriteCount) % spriteCount;
			Set(slot, num);
			return num;
		}

		public static string Format(IEnumerable<KeyValuePair<LabelSlot, int>> entries)
		{
			return string.Join(";", entries.Select((KeyValuePair<LabelSlot, int> pair) => pair.Key.Text + "=" + pair.Value.ToString(CultureInfo.InvariantCulture)));
		}

		public static List<KeyValuePair<LabelSlot, int>> Parse(string text)
		{
			List<KeyValuePair<LabelSlot, int>> list = new List<KeyValuePair<LabelSlot, int>>();
			if (string.IsNullOrEmpty(text))
			{
				return list;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('=');
				if (array2.Length == 2 && LabelSlot.TryParse(array2[0], out var slot) && int.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result <= 255)
				{
					list.Add(new KeyValuePair<LabelSlot, int>(slot, result));
				}
			}
			return list;
		}

		public List<string> Chunks(int maxBytes)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<LabelSlot, int> entry in Entries)
			{
				string text = entry.Key.Text + "=" + entry.Value.ToString(CultureInfo.InvariantCulture);
				if (stringBuilder.Length > 0 && stringBuilder.Length + 1 + text.Length > maxBytes)
				{
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
				}
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(text);
			}
			if (stringBuilder.Length > 0)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}
	}
	public readonly record struct LabelSlot(int LabelId, int Slot)
	{
		public bool IsValid
		{
			get
			{
				if (LabelId > 0 && Slot >= 1)
				{
					return Slot <= 3;
				}
				return false;
			}
		}

		public string SaveKey => "label/" + Text;

		public string Text => LabelId.ToString(CultureInfo.InvariantCulture) + "/" + Slot.ToString(CultureInfo.InvariantCulture);

		public const int MaxSlots = 3;

		public const string SaveKeyPrefix = "label/";

		public static bool TryParse(string text, out LabelSlot slot)
		{
			slot = default(LabelSlot);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			string[] array = text.Split('/');
			if (array.Length != 2 || !int.TryParse(array[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2))
			{
				return false;
			}
			slot = new LabelSlot(result, result2);
			return slot.IsValid;
		}

		public static bool TryParseSaveKey(string key, out LabelSlot slot)
		{
			slot = default(LabelSlot);
			if (key != null && key.StartsWith("label/", StringComparison.Ordinal))
			{
				return TryParse(key.Substring("label/".Length), out slot);
			}
			return false;
		}

		public override string ToString()
		{
			return Text;
		}
	}
	public sealed class MeshClip
	{
		public int[][] Triangles { get; }

		public IReadOnlyList<(int A, int B, float T)> Added { get; }

		public int Removed { get; }

		public int Split { get; }

		public bool Changed
		{
			get
			{
				if (Removed <= 0)
				{
					return Split > 0;
				}
				return true;
			}
		}

		private MeshClip(int[][] triangles, List<(int A, int B, float T)> added, int removed, int split)
		{
			Triangles = triangles;
			Added = added;
			Removed = removed;
			Split = split;
		}

		public static MeshClip KeepAbove(IReadOnlyList<float> heights, IReadOnlyList<int[]> submeshes, float threshold)
		{
			if (heights == null || submeshes == null)
			{
				throw new ArgumentNullException((heights == null) ? "heights" : "submeshes");
			}
			List<(int A, int B, float T)> added = new List<(int, int, float)>();
			Dictionary<(int, int), int> edges = new Dictionary<(int, int), int>();
			int[][] array = new int[submeshes.Count][];
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < submeshes.Count; i++)
			{
				int[] array2 = submeshes[i] ?? Array.Empty<int>();
				List<int> list = new List<int>(array2.Length);
				for (int j = 0; j + 2 < array2.Length; j += 3)
				{
					int[] array3 = new int[3]
					{
						array2[j],
						array2[j + 1],
						array2[j + 2]
					};
					bool[] array4 = new bool[3];
					int num3 = 0;
					for (int k = 0; k < 3; k++)
					{
						array4[k] = heights[array3[k]] >= threshold;
						num3 += (array4[k] ? 1 : 0);
					}
					switch (num3)
					{
					case 3:
						list.Add(array3[0]);
						list.Add(array3[1]);
						list.Add(array3[2]);
						continue;
					case 0:
						num++;
						continue;
					}
					num2++;
					if (num3 == 1)
					{
						int num4 = Array.IndexOf(array4, value: true);
						int num5 = array3[num4];
						int b = array3[(num4 + 1) % 3];
						int b2 = array3[(num4 + 2) % 3];
						list.Add(num5);
						list.Add(Cross(num5, b));
						list.Add(Cross(num5, b2));
					}
					else
					{
						int num6 = Array.IndexOf(array4, value: false);
						int num7 = array3[num6];
						int num8 = array3[(num6 + 1) % 3];
						int num9 = array3[(num6 + 2) % 3];
						int item = Cross(num9, num7);
						int item2 = Cross(num7, num8);
						list.Add(num8);
						list.Add(num9);
						list.Add(item);
						list.Add(num8);
						list.Add(item);
						list.Add(item2);
					}
				}
				array[i] = list.ToArray();
			}
			return new MeshClip(array, added, num, num2);
			int Cross(int a, int num10)
			{
				(int, int) key = ((a < num10) ? (a, num10) : (num10, a));
				if (edges.TryGetValue(key, out var value))
				{
					return value;
				}
				int item3 = key.Item1;
				int item4 = key.Item2;
				float num11 = heights[item4] - heights[item3];
				float value2 = ((Math.Abs(num11) < 1E-09f) ? 0.5f : ((threshold - heights[item3]) / num11));
				value2 = Math.Clamp(value2, 0f, 1f);
				int num12 = heights.Count + added.Count;
				added.Add((item3, item4, value2));
				edges[key] = num12;
				return num12;
			}
		}
	}
	public enum Placement
	{
		Auto,
		Right,
		Left,
		Below,
		None
	}
	public sealed class PlacementBoard
	{
		public const string SaveKeyPrefix = "place/";

		private readonly Dictionary<int, Placement> _placements = new Dictionary<int, Placement>();

		public int Count => _placements.Count;

		public IReadOnlyList<KeyValuePair<int, Placement>> Entries => _placements.OrderBy((KeyValuePair<int, Placement> pair) => pair.Key).ToList();

		public event Action<IReadOnlyList<int>> Changed;

		public Placement Get(int labelId, Placement fallback)
		{
			if (!_placements.TryGetValue(labelId, out var value))
			{
				return fallback;
			}
			return value;
		}

		public bool Has(int labelId)
		{
			return _placements.ContainsKey(labelId);
		}

		public bool Set(int labelId, Placement placement)
		{
			return Apply(new KeyValuePair<int, Placement>[1]
			{
				new KeyValuePair<int, Placement>(labelId, placement)
			}) > 0;
		}

		public int Apply(IEnumerable<KeyValuePair<int, Placement>> entries)
		{
			List<int> list = new List<int>();
			foreach (var (num2, placement2) in entries)
			{
				if (num2 > 0 && IsDefined(placement2) && (!_placements.TryGetValue(num2, out var value) || value != placement2))
				{
					_placements[num2] = placement2;
					list.Add(num2);
				}
			}
			if (list.Count > 0)
			{
				this.Changed?.Invoke(list);
			}
			return list.Count;
		}

		public void Clear()
		{
			if (_placements.Count != 0)
			{
				List<int> obj = _placements.Keys.ToList();
				_placements.Clear();
				this.Changed?.Invoke(obj);
			}
		}

		public static Placement Next(Placement placement)
		{
			return placement switch
			{
				Placement.Auto => Placement.Right, 
				Placement.Right => Placement.Left, 
				Placement.Left => Placement.Below, 
				Placement.Below => Placement.None, 
				_ => Placement.Auto, 
			};
		}

		public static bool IsDefined(Placement placement)
		{
			if (placement >= Placement.Auto)
			{
				return placement <= Placement.None;
			}
			return false;
		}

		public static string SaveKey(int labelId)
		{
			return "place/" + labelId.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryParseSaveKey(string key, out int labelId)
		{
			labelId = 0;
			if (key != null && key.StartsWith("place/", StringComparison.Ordinal) && int.TryParse(key.Substring("place/".Length), NumberStyles.None, CultureInfo.InvariantCulture, out labelId))
			{
				return labelId > 0;
			}
			return false;
		}

		public static List<KeyValuePair<int, Placement>> Parse(string text)
		{
			List<KeyValuePair<int, Placement>> list = new List<KeyValuePair<int, Placement>>();
			if (string.IsNullOrEmpty(text))
			{
				return list;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('=');
				if (array2.Length == 2 && int.TryParse(array2[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) && int.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) && result > 0 && IsDefined((Placement)result2))
				{
					list.Add(new KeyValuePair<int, Placement>(result, (Placement)result2));
				}
			}
			return list;
		}

		public static string Format(int labelId, Placement placement)
		{
			string text = labelId.ToString(CultureInfo.InvariantCulture);
			int num = (int)placement;
			return text + "=" + num.ToString(CultureInfo.InvariantCulture);
		}

		public List<string> Chunks(int maxBytes)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<int, Placement> entry in Entries)
			{
				entry.Deconstruct(out var key, out var value);
				int labelId = key;
				Placement placement = value;
				string text = Format(labelId, placement);
				if (stringBuilder.Length > 0 && stringBuilder.Length + 1 + text.Length > maxBytes)
				{
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
				}
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(text);
			}
			if (stringBuilder.Length > 0)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}
	}
	public static class SlotLayout
	{
		public static (float Right, float Up) Offset(int slot, Placement placement, float width, float height, float spacing)
		{
			if (slot < 1)
			{
				throw new ArgumentOutOfRangeException("slot");
			}
			return placement switch
			{
				Placement.Below => (Right: 0f, Up: (float)(-slot) * (height + spacing)), 
				Placement.Left => (Right: (float)(-slot) * (width + spacing), Up: 0f), 
				_ => (Right: (float)slot * (width + spacing), Up: 0f), 
			};
		}

		public static Placement NameSide(string holderName)
		{
			if (holderName == null || holderName.IndexOf("_Left", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return Placement.Right;
			}
			return Placement.Left;
		}

		public static Placement ChooseSide(Placement preferred, int blockedPreferred, int blockedOther)
		{
			Placement result = ((preferred == Placement.Left) ? Placement.Right : Placement.Left);
			if (blockedOther >= blockedPreferred)
			{
				return preferred;
			}
			return result;
		}
	}
	public sealed class StandBoard
	{
		public const string SaveKeyPrefix = "stand/";

		private readonly Dictionary<int, bool> _hidden = new Dictionary<int, bool>();

		public int Count => _hidden.Count;

		public IReadOnlyList<KeyValuePair<int, bool>> Entries => _hidden.OrderBy((KeyValuePair<int, bool> pair) => pair.Key).ToList();

		public event Action<IReadOnlyList<int>> Changed;

		public bool IsHidden(int labelId, bool fallback)
		{
			if (!_hidden.TryGetValue(labelId, out var value))
			{
				return fallback;
			}
			return value;
		}

		public bool Set(int labelId, bool hidden)
		{
			return Apply(new KeyValuePair<int, bool>[1]
			{
				new KeyValuePair<int, bool>(labelId, hidden)
			}) > 0;
		}

		public int Apply(IEnumerable<KeyValuePair<int, bool>> entries)
		{
			List<int> list = new List<int>();
			foreach (var (num2, flag2) in entries)
			{
				if (num2 > 0 && (!_hidden.TryGetValue(num2, out var value) || value != flag2))
				{
					_hidden[num2] = flag2;
					list.Add(num2);
				}
			}
			if (list.Count > 0)
			{
				this.Changed?.Invoke(list);
			}
			return list.Count;
		}

		public void Clear()
		{
			if (_hidden.Count != 0)
			{
				List<int> obj = _hidden.Keys.ToList();
				_hidden.Clear();
				this.Changed?.Invoke(obj);
			}
		}

		public static string SaveKey(int labelId)
		{
			return "stand/" + labelId.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryParseSaveKey(string key, out int labelId)
		{
			labelId = 0;
			if (key != null && key.StartsWith("stand/", StringComparison.Ordinal) && int.TryParse(key.Substring("stand/".Length), NumberStyles.None, CultureInfo.InvariantCulture, out labelId))
			{
				return labelId > 0;
			}
			return false;
		}

		public static string Format(int labelId, bool hidden)
		{
			return labelId.ToString(CultureInfo.InvariantCulture) + "=" + (hidden ? "1" : "0");
		}

		public static List<KeyValuePair<int, bool>> Parse(string text)
		{
			List<KeyValuePair<int, bool>> list = new List<KeyValuePair<int, bool>>();
			if (string.IsNullOrEmpty(text))
			{
				return list;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('=');
				if (array2.Length == 2 && int.TryParse(array2[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result > 0 && (array2[1] == "0" || array2[1] == "1"))
				{
					list.Add(new KeyValuePair<int, bool>(result, array2[1] == "1"));
				}
			}
			return list;
		}

		public List<string> Chunks(int maxBytes)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<int, bool> entry in Entries)
			{
				entry.Deconstruct(out var key, out var value);
				int labelId = key;
				bool hidden = value;
				string text = Format(labelId, hidden);
				if (stringBuilder.Length > 0 && stringBuilder.Length + 1 + text.Length > maxBytes)
				{
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
				}
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(text);
			}
			if (stringBuilder.Length > 0)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}
	}
}
namespace ShelfLabels.Data
{
	public sealed class LabelStore
	{
		private readonly ModSave _save;

		private readonly LabelBoard _board;

		private readonly PlacementBoard _placements;

		private readonly StandBoard _stands;

		private readonly CatLogger _log;

		public LabelStore(ModSave save, LabelBoard board, PlacementBoard placements, StandBoard stands, CatLogger log)
		{
			_save = save;
			_board = board;
			_placements = placements;
			_stands = stands;
			_log = log;
			_save.Loaded += Load;
			_save.Saving += delegate
			{
				Store();
			};
		}

		public void Load()
		{
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			_board.Clear();
			_placements.Clear();
			_stands.Clear();
			List<KeyValuePair<LabelSlot, int>> list = new List<KeyValuePair<LabelSlot, int>>();
			List<KeyValuePair<int, Placement>> list2 = new List<KeyValuePair<int, Placement>>();
			List<KeyValuePair<int, bool>> list3 = new List<KeyValuePair<int, bool>>();
			foreach (string key in _save.Keys)
			{
				int labelId;
				int labelId2;
				if (LabelSlot.TryParseSaveKey(key, out var slot))
				{
					list.Add(new KeyValuePair<LabelSlot, int>(slot, _save.Get<int>(key, 0)));
				}
				else if (PlacementBoard.TryParseSaveKey(key, out labelId))
				{
					list2.Add(new KeyValuePair<int, Placement>(labelId, (Placement)_save.Get<int>(key, -1)));
				}
				else if (StandBoard.TryParseSaveKey(key, out labelId2))
				{
					list3.Add(new KeyValuePair<int, bool>(labelId2, _save.Get<bool>(key, false)));
				}
			}
			_board.Apply(list);
			_placements.Apply(list2);
			_stands.Apply(list3);
			CatLogger log = _log;
			if (log != null)
			{
				log.Info($"Loaded {_board.Count} label picture(s), {_placements.Count} placement(s) and {_stands.Count} stand choice(s) from {_save.SaveName} ({_save.State}, {_save.LastRead})");
			}
		}

		public int Store()
		{
			if (!_save.IsReady)
			{
				return 0;
			}
			int num = 0;
			int key2;
			foreach (KeyValuePair<LabelSlot, int> entry in _board.Entries)
			{
				entry.Deconstruct(out var key, out key2);
				LabelSlot labelSlot = key;
				int num2 = key2;
				if (_save.Get<int>(labelSlot.SaveKey, -1) != num2 && _save.Set<int>(labelSlot.SaveKey, num2))
				{
					num++;
				}
			}
			foreach (KeyValuePair<int, Placement> entry2 in _placements.Entries)
			{
				entry2.Deconstruct(out key2, out var value);
				int labelId = key2;
				Placement placement = value;
				string text = PlacementBoard.SaveKey(labelId);
				if (_save.Get<int>(text, -1) != (int)placement && _save.Set<int>(text, (int)placement))
				{
					num++;
				}
			}
			foreach (KeyValuePair<int, bool> entry3 in _stands.Entries)
			{
				entry3.Deconstruct(out key2, out var value2);
				int labelId2 = key2;
				bool flag = value2;
				string text2 = StandBoard.SaveKey(labelId2);
				if ((!_save.Has(text2) || _save.Get<bool>(text2, false) != flag) && _save.Set<bool>(text2, flag))
				{
					num++;
				}
			}
			return num;
		}
	}
}