Decompiled source of RumbleParkPen v1.1.0

Mods\RumbleParkPen.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using HarmonyLib;
using Il2CppExitGames.Client.Photon;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppPhoton.Pun;
using Il2CppPhoton.Realtime;
using Il2CppRUMBLE.Players;
using Il2CppRUMBLE.Players.Subsystems;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using RumbleModdingAPI.RMAPI;
using RumbleParkPen;
using RumbleParkPen.Patches;
using RumbleParkPen.Services.Drawing;
using RumbleParkPen.Services.Input;
using RumbleParkPen.Services.Persistence;
using RumbleParkPen.Services.Sync;
using RumbleParkPen.Unity;
using RumbleParkPen.Unity.Input;
using RumbleParkPen.Unity.Rendering;
using UIFramework;
using UIFramework.UiExtensions;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(PenMod), "RumbleParkPen", "1.1.0", "ACutiePi", null)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: MelonAdditionalDependencies(new string[] { "UIFramework", "RumbleModdingAPI" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("RumbleParkPen")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.0+f645f5656586cd08423a550c31b778619ffff62b")]
[assembly: AssemblyProduct("RumbleParkPen")]
[assembly: AssemblyTitle("RumbleParkPen")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	int ICollection.Count => _items.Length;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object? IList.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => _items.Length;

	T IReadOnlyList<T>.this[int index] => _items[index];

	int ICollection<T>.Count => _items.Length;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			return _items[index];
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlyArray(T[] items)
	{
		_items = items;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return ((IEnumerable)_items).GetEnumerator();
	}

	void ICollection.CopyTo(Array array, int index)
	{
		((ICollection)_items).CopyTo(array, index);
	}

	int IList.Add(object? value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object? value)
	{
		return ((IList)_items).Contains(value);
	}

	int IList.IndexOf(object? value)
	{
		return ((IList)_items).IndexOf(value);
	}

	void IList.Insert(int index, object? value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object? value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return ((IEnumerable<T>)_items).GetEnumerator();
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return ((ICollection<T>)_items).Contains(item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		((ICollection<T>)_items).CopyTo(array, arrayIndex);
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		return ((IList<T>)_items).IndexOf(item);
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object? IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object? value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object? value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object? value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object? value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object? value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
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 RumbleParkPen
{
	public sealed class PenMod : RumbleMod
	{
		private PenSession _session;

		private InputService _inputs;

		public static Instance Logger { get; private set; }

		public override void OnInitializeMelon()
		{
			Logger = ((MelonBase)this).LoggerInstance;
			_inputs = new InputService((MelonBase)(object)this, "RumbleParkPen_Controls", "Controls", Settings.ConfigPath);
			_session = new PenSession(this, _inputs);
			Settings.Initialize((MelonBase)(object)this, new Settings.Actions
			{
				TogglePen = delegate
				{
					_session.TogglePen();
				},
				Save = delegate
				{
					_session.TrySave();
				},
				Load = delegate
				{
					_session.TryLoad();
				},
				Clear = delegate
				{
					_session.ClearDrawing();
				},
				DeleteSave = delegate
				{
					_session.DeleteSave();
				}
			}, _inputs.Category);
			try
			{
				RmapiLogFilter.Apply(((MelonBase)this).HarmonyInstance);
			}
			catch (Exception ex)
			{
				Logger.Warning("Could not quieten RMAPI network logging: " + ex.Message);
			}
			Logger.Msg("RumbleParkPen loaded.");
		}

		public override void OnSceneWasLoaded(int buildIndex, string sceneName)
		{
			try
			{
				_session.OnSceneChanged(sceneName);
			}
			catch (Exception value)
			{
				Logger.Error($"Scene change to '{sceneName}' failed: {value}");
			}
		}

		public override void OnUpdate()
		{
			try
			{
				_inputs.RefreshVisibility();
				_session.Tick(Time.deltaTime);
			}
			catch (Exception value)
			{
				Logger.Error($"Update failed: {value}");
			}
		}

		public override void OnEvent(List<Object> data)
		{
			try
			{
				PenMessage penMessage = PenNetwork.Decode(data);
				if (penMessage.Kind != PenMessageKind.Unknown)
				{
					_session.OnNetworkMessage(penMessage);
				}
			}
			catch (Exception value)
			{
				Logger.Error($"Handling a network message failed: {value}");
			}
		}

		public override void OnApplicationQuit()
		{
			try
			{
				if (Settings.AutoSave.Value)
				{
					_session.TrySave();
				}
			}
			catch (Exception value)
			{
				Logger.Error($"Save on quit failed: {value}");
			}
		}
	}
	public enum PenHand
	{
		Right,
		Left
	}
	public static class Settings
	{
		public sealed class Actions
		{
			public Action TogglePen { get; init; } = delegate
			{
			};

			public Action Save { get; init; } = delegate
			{
			};

			public Action Load { get; init; } = delegate
			{
			};

			public Action Clear { get; init; } = delegate
			{
			};

			public Action DeleteSave { get; init; } = delegate
			{
			};
		}

		private const string ConfigFolder = "UserData/RumbleParkPen";

		private const string ConfigFile = "config.cfg";

		public static string ConfigPath { get; } = Path.Combine("UserData/RumbleParkPen", "config.cfg");

		public static MelonPreferences_Entry<PenHand> Hand { get; private set; } = null;

		public static MelonPreferences_Entry<Color> InkColor { get; private set; } = null;

		public static MelonPreferences_Entry<float> LineWidth { get; private set; } = null;

		public static MelonPreferences_Entry<bool> PressureSensitivity { get; private set; } = null;

		public static MelonPreferences_Entry<float> PointSpacing { get; private set; } = null;

		public static MelonPreferences_Entry<float> EraserRadius { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenScale { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenPitch { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenYaw { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenOffsetForward { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenOffsetUp { get; private set; } = null;

		public static MelonPreferences_Entry<float> PenOffsetSide { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoSave { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoLoad { get; private set; } = null;

		public static MelonPreferences_Entry<bool> ShareDrawings { get; private set; } = null;

		public static MelonPreferences_Entry<bool> ShowOthersDrawings { get; private set; } = null;

		public static MelonPreferences_Entry<bool> SyncOnJoin { get; private set; } = null;

		public static MelonPreferences_Entry<bool> QuietNetworkLog { get; private set; } = null;

		public static void Initialize(MelonBase melon, Actions actions, MelonPreferences_Category controls)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_00ff: 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_010f: 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_0127: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: 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_016b: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Expected O, but got Unknown
			//IL_01e2: 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_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Expected O, but got Unknown
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Expected O, but got Unknown
			//IL_026a: 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_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Expected O, but got Unknown
			//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Expected O, but got Unknown
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			//IL_031a: Expected O, but got Unknown
			Directory.CreateDirectory("UserData/RumbleParkPen");
			string configPath = ConfigPath;
			MelonPreferences_Category val = MelonPreferences.CreateCategory("RumbleParkPen_Pen", "Pen");
			val.SetFilePath(configPath);
			UI.CreateButtonEntry(val, "Spawn / Put Away", "Pen", "Spawns the pen in your hand", actions.TogglePen);
			Hand = val.CreateEntry<PenHand>("Hand", PenHand.Right, "Pen Hand", "Which hand holds the pen", false, false, (ValueValidator)null, (string)null);
			InkColor = val.CreateEntry<Color>("InkColor", Color.white, "Ink Colour", "Colour of new strokes, cycle with primary button on pen hand", false, false, (ValueValidator)null, (string)null);
			LineWidth = val.CreateEntry<float>("LineWidth", 0.01f, "Line Width", "Stroke thickness in (metres)", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.002f,
				Max = 0.05f,
				DecimalPlaces = 3
			}, (string)null);
			PressureSensitivity = val.CreateEntry<bool>("PressureSensitivity", true, "Pressure Sensitivity", "Squeeze the trigger harder for a thicker line", false, false, (ValueValidator)null, (string)null);
			PointSpacing = val.CreateEntry<float>("PointSpacing", 0.006f, "Detail", "How far each saved point of a line is (metres)", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.002f,
				Max = 0.05f,
				DecimalPlaces = 3
			}, (string)null);
			EraserRadius = val.CreateEntry<float>("EraserRadius", 0.1f, "Eraser Radius", "How close the nib must get to a stroke to rub it out (metres)", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.02f,
				Max = 0.5f,
				DecimalPlaces = 3
			}, (string)null);
			MelonPreferences_Category val2 = MelonPreferences.CreateCategory("RumbleParkPen_Fit", "Pen Fit");
			val2.SetFilePath(configPath);
			PenScale = val2.CreateEntry<float>("PenScale", 1f, "Pen Size", "Pen model scale", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.5f,
				Max = 2f,
				DecimalPlaces = 2
			}, (string)null);
			PenPitch = val2.CreateEntry<float>("PenPitch", 40f, "Pen Pitch", "Tilts pen up/down", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = -90f,
				Max = 90f,
				DecimalPlaces = 0
			}, (string)null);
			PenYaw = val2.CreateEntry<float>("PenYaw", 0f, "Pen Yaw", "Tilts pen lef/right", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = -90f,
				Max = 90f,
				DecimalPlaces = 0
			}, (string)null);
			PenOffsetForward = val2.CreateEntry<float>("PenOffsetForward", 0.032f, "Forward Offset", "Pen position forward/back", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = -0.15f,
				Max = 0.15f,
				DecimalPlaces = 3
			}, (string)null);
			PenOffsetUp = val2.CreateEntry<float>("PenOffsetUp", 0.1f, "Vertical Offset", "Pen position up/down", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = -0.15f,
				Max = 0.15f,
				DecimalPlaces = 3
			}, (string)null);
			PenOffsetSide = val2.CreateEntry<float>("PenOffsetSide", 0f, "Sideways Offset", "Pen posision left/right", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = -0.15f,
				Max = 0.15f,
				DecimalPlaces = 3
			}, (string)null);
			MelonPreferences_Category val3 = MelonPreferences.CreateCategory("RumbleParkPen_Drawing", "Drawing");
			val3.SetFilePath(configPath);
			UI.CreateButtonEntry(val3, "Save", "Save Drawing", "Save current drawing to a file", actions.Save);
			UI.CreateButtonEntry(val3, "Load", "Load Drawing", "Replace the current drawn scene with a saved one", actions.Load);
			UI.CreateButtonEntry(val3, "Clear", "Clear Drawing", "Clear all drawings in the scene", actions.Clear);
			UI.CreateButtonEntry(val3, "Delete", "Delete Save File", "Deletes this scene's save file", actions.DeleteSave);
			AutoLoad = val3.CreateEntry<bool>("AutoLoad", true, "Load On Arrival", "Restores scenes' saved drawings automatically when you enter Park or Gym", false, false, (ValueValidator)null, (string)null);
			AutoSave = val3.CreateEntry<bool>("AutoSave", true, "Save On Leaving", "Saves automatically when you leave a Park or Gym", false, false, (ValueValidator)null, (string)null);
			MelonPreferences_Category val4 = MelonPreferences.CreateCategory("RumbleParkPen_Multiplayer", "Multiplayer");
			val4.SetFilePath(configPath);
			ShareDrawings = val4.CreateEntry<bool>("ShareDrawings", true, "Share Your Drawings", "Sends your strokes to other players (with this mod) in the room", false, false, (ValueValidator)null, (string)null);
			ShowOthersDrawings = val4.CreateEntry<bool>("ShowOthersDrawings", true, "Show Others' Drawings", "Shows drawings from other players in the room", false, false, (ValueValidator)null, (string)null);
			SyncOnJoin = val4.CreateEntry<bool>("SyncOnJoin", true, "Sync On Join", "Sync drawings when others arrive to a room that already has drawings, or you arrive to other rooms with drawings", false, false, (ValueValidator)null, (string)null);
			QuietNetworkLog = val4.CreateEntry<bool>("QuietNetworkLog", true, "Quiet Network Logging", "Hides the two lines RUMBLE Modding API writes for every stroke message received (reduces network log spam)", false, false, (ValueValidator)null, (string)null);
			UI.RegisterMelon(melon, (MelonPreferences_Category[])(object)new MelonPreferences_Category[5] { val, val2, val3, val4, controls });
			val.SaveToFile(false);
			val2.SaveToFile(false);
			val3.SaveToFile(false);
			val4.SaveToFile(false);
			controls.SaveToFile(false);
		}
	}
}
namespace RumbleParkPen.Unity
{
	public sealed class PenNetwork(PenMod mod)
	{
		private readonly struct PendingSend
		{
			public string Payload { get; init; }

			public int TargetActor { get; init; }
		}

		private const int ReplayMessagesPerTick = 3;

		private readonly PenMod _mod = mod;

		private readonly Queue<PendingSend> _replayQueue = new Queue<PendingSend>();

		private readonly HashSet<int> _peers = new HashSet<int>();

		private readonly HashSet<int> _present = new HashSet<int>();

		private readonly HashSet<int> _warnedMismatch = new HashSet<int>();

		private readonly HashSet<int> _liveActors = new HashSet<int>();

		private static readonly Object PresenceKey = (Object)(object)String.op_Implicit("RumbleParkPen.protocol");

		private static readonly string ProtocolVersionText = 1.ToString(CultureInfo.InvariantCulture);

		private int _strokeSequence;

		public static bool CanTransmit
		{
			get
			{
				try
				{
					return PhotonNetwork.InRoom && PhotonNetwork.PlayerList != null && ((Il2CppArrayBase<Player>)(object)PhotonNetwork.PlayerList).Length > 1;
				}
				catch (Exception)
				{
					return false;
				}
			}
		}

		public static bool NetworkingEnabled
		{
			get
			{
				if (!Settings.ShareDrawings.Value)
				{
					return Settings.ShowOthersDrawings.Value;
				}
				return true;
			}
		}

		public int PeerCount => _peers.Count;

		public IReadOnlyCollection<int> PresentActors => _present;

		public static int LocalActorNumber
		{
			get
			{
				try
				{
					Player localPlayer = PhotonNetwork.LocalPlayer;
					return (localPlayer != null) ? localPlayer.ActorNumber : 0;
				}
				catch (Exception)
				{
					return 0;
				}
			}
		}

		public static void PublishPresence()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			if (!NetworkingEnabled)
			{
				return;
			}
			try
			{
				Player localPlayer = PhotonNetwork.LocalPlayer;
				if (localPlayer != null)
				{
					Hashtable val = new Hashtable();
					val[PresenceKey] = (Object)(object)String.op_Implicit(ProtocolVersionText);
					localPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				}
			}
			catch (Exception ex)
			{
				PenMod.Logger.Warning("Could not publish pen presence: " + ex.Message);
			}
		}

		public List<int> RefreshPeers()
		{
			List<int> list = new List<int>();
			_present.Clear();
			try
			{
				if (!PhotonNetwork.InRoom || PhotonNetwork.PlayerList == null)
				{
					_peers.Clear();
					return list;
				}
				int localActorNumber = LocalActorNumber;
				foreach (Player item in (Il2CppArrayBase<Player>)(object)PhotonNetwork.PlayerList)
				{
					int actorNumber = item.ActorNumber;
					_present.Add(actorNumber);
					if (actorNumber == localActorNumber)
					{
						continue;
					}
					string text = ReadPresence(item);
					if (text == null)
					{
						_peers.Remove(actorNumber);
					}
					else if (string.Equals(text, ProtocolVersionText, StringComparison.Ordinal))
					{
						if (_peers.Add(actorNumber))
						{
							list.Add(actorNumber);
						}
					}
					else if (_warnedMismatch.Add(actorNumber))
					{
						_peers.Remove(actorNumber);
						PenMod.Logger.Warning($"'{item.NickName}' is running a different RumbleParkPen protocol (theirs: {text}, ours: {ProtocolVersionText}); drawings will not be shared " + "with them. One of you needs to update.");
					}
				}
				_peers.IntersectWith(_present);
				_warnedMismatch.IntersectWith(_present);
			}
			catch (Exception)
			{
			}
			return list;
		}

		private static string? ReadPresence(Player player)
		{
			try
			{
				Hashtable customProperties = player.CustomProperties;
				if (customProperties == null || !((Dictionary<Object, Object>)(object)customProperties).ContainsKey(PresenceKey))
				{
					return null;
				}
				Object obj = customProperties[PresenceKey];
				return (obj != null) ? obj.ToString() : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		public void AddPeer(int actor)
		{
			if (actor > 0 && actor != LocalActorNumber)
			{
				_peers.Add(actor);
			}
		}

		public void ForgetPeers()
		{
			_peers.Clear();
			_present.Clear();
			_warnedMismatch.Clear();
		}

		public string NextStrokeId()
		{
			return StrokeId.Create(LocalActorNumber, ++_strokeSequence);
		}

		public void EnsureRegistered()
		{
			RmapiRegistration.Ensure(mod);
		}

		public void SendStrokeStart(string strokeId, float r, float g, float b)
		{
			Send(PenSyncFormat.StrokeStart(strokeId, r, g, b));
		}

		public void SendStrokePoints(string strokeId, IReadOnlyList<PenPoint> points)
		{
			if (points.Count != 0)
			{
				Send(PenSyncFormat.StrokePoints(strokeId, points));
			}
		}

		public void SendStrokeEnd(string strokeId)
		{
			Send(PenSyncFormat.StrokeEnd(strokeId));
		}

		public void SendErase(IReadOnlyList<string> strokeIds)
		{
			if (strokeIds.Count != 0)
			{
				Send(PenSyncFormat.Erase(strokeIds));
			}
		}

		public void SendClear()
		{
			Send(PenSyncFormat.Clear(LocalActorNumber));
		}

		public void SendSyncRequest(int targetActor, bool wantsReplay)
		{
			if (CanTransmit && NetworkingEnabled)
			{
				Transmit(PenSyncFormat.SyncRequest(LocalActorNumber, wantsReplay), targetActor);
			}
		}

		public void QueueReplay(IEnumerable<StrokeData> strokes, int targetActor, int pointsPerMessage = 60)
		{
			foreach (StrokeData stroke in strokes)
			{
				if (stroke.IsRenderable && !string.IsNullOrEmpty(stroke.Id))
				{
					Enqueue(PenSyncFormat.StrokeStart(stroke.Id, stroke.ColorR, stroke.ColorG, stroke.ColorB), targetActor);
					for (int i = 0; i < stroke.Points.Count; i += pointsPerMessage)
					{
						int count = Math.Min(pointsPerMessage, stroke.Points.Count - i);
						Enqueue(PenSyncFormat.StrokePoints(stroke.Id, stroke.Points.GetRange(i, count)), targetActor);
					}
					Enqueue(PenSyncFormat.StrokeEnd(stroke.Id), targetActor);
				}
			}
		}

		public void PumpReplayQueue()
		{
			for (int i = 0; i < 3; i++)
			{
				if (_replayQueue.Count <= 0)
				{
					break;
				}
				PendingSend pendingSend = _replayQueue.Dequeue();
				Transmit(pendingSend.Payload, pendingSend.TargetActor);
			}
		}

		public void DiscardQueuedReplays()
		{
			_replayQueue.Clear();
		}

		private void Enqueue(string payload, int targetActor)
		{
			_replayQueue.Enqueue(new PendingSend
			{
				Payload = payload,
				TargetActor = targetActor
			});
		}

		private void Send(string payload)
		{
			if (CanTransmit && Settings.ShareDrawings.Value)
			{
				int[] array = LivePeers();
				if (array.Length != 0)
				{
					Transmit(payload, array);
				}
			}
		}

		private int[] LivePeers()
		{
			if (_peers.Count == 0)
			{
				return Array.Empty<int>();
			}
			try
			{
				_liveActors.Clear();
				foreach (Player item in (Il2CppArrayBase<Player>)(object)PhotonNetwork.PlayerList)
				{
					_liveActors.Add(item.ActorNumber);
				}
				_peers.IntersectWith(_liveActors);
			}
			catch (Exception)
			{
				return Array.Empty<int>();
			}
			if (_peers.Count == 0)
			{
				return Array.Empty<int>();
			}
			int[] array = new int[_peers.Count];
			_peers.CopyTo(array);
			return array;
		}

		private void Transmit(string payload, int targetActor)
		{
			if (targetActor > 0)
			{
				Transmit(payload, new int[1] { targetActor });
			}
		}

		private void Transmit(string payload, int[] targetActors)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!CanTransmit || targetActors.Length == 0)
			{
				return;
			}
			try
			{
				List<Object> list = new List<Object> { Object.op_Implicit(payload) };
				Il2CppStructArray<int> val = new Il2CppStructArray<int>((long)targetActors.Length);
				for (int i = 0; i < targetActors.Length; i++)
				{
					((Il2CppArrayBase<int>)(object)val)[i] = targetActors[i];
				}
				RaiseEventOptions val2 = new RaiseEventOptions
				{
					TargetActors = val
				};
				((RumbleMod)mod).RaiseEvent(list, val2, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				PenMod.Logger.Warning("Pen network send failed: " + ex.Message);
			}
		}

		public static PenMessage Decode(List<Object> data)
		{
			try
			{
				if (data == null || data.Count == 0)
				{
					return new PenMessage
					{
						Kind = PenMessageKind.Unknown
					};
				}
				Object val = data[0];
				return (val == null) ? new PenMessage
				{
					Kind = PenMessageKind.Unknown
				} : PenSyncFormat.Decode(val.ToString());
			}
			catch (Exception)
			{
				return new PenMessage
				{
					Kind = PenMessageKind.Unknown
				};
			}
		}
	}
	public readonly record struct PenFit(Vector3 Offset, Vector3 Rotation, float Scale)
	{
		public static PenFit FromSettings()
		{
			//IL_001e: 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)
			return new PenFit(new Vector3(Settings.PenOffsetSide.Value, Settings.PenOffsetUp.Value, Settings.PenOffsetForward.Value), new Vector3(Settings.PenPitch.Value, Settings.PenYaw.Value, 0f), Settings.PenScale.Value);
		}
	}
	public sealed class PenRig
	{
		private const float BodyRadius = 0.008f;

		private const float BodyLength = 0.11f;

		private const float TaperRadius = 0.005f;

		private const float TaperLength = 0.03f;

		private const float NibDiameter = 0.007f;

		private const float TipDistance = 0.15f;

		private GameObject? _root;

		private Renderer? _taperRenderer;

		private Renderer? _nibRenderer;

		private PenFit _fit;

		private bool _fitApplied;

		public Transform? Tip { get; private set; }

		public bool IsAlive
		{
			get
			{
				if ((Object)(object)_root != (Object)null)
				{
					return (Object)(object)Tip != (Object)null;
				}
				return false;
			}
		}

		public Vector3? TipPosition
		{
			get
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				Transform? tip = Tip;
				if (tip == null)
				{
					return null;
				}
				return tip.position;
			}
		}

		public bool Build(Transform anchor, in PenFit fit)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0148: 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)
			if ((Object)(object)anchor == (Object)null)
			{
				return false;
			}
			Destroy();
			_root = new GameObject("RumbleParkPen.Pen");
			_root.transform.SetParent(anchor, false);
			ApplyFit(in fit);
			CreatePart((PrimitiveType)2, "Body", new Vector3(0f, 0f, 0.055f), new Vector3(0.016f, 0.055f, 0.016f), 0.12f, 0.12f, 0.13f, out Renderer _);
			CreatePart((PrimitiveType)2, "Taper", new Vector3(0f, 0f, 0.125f), new Vector3(0.01f, 0.015f, 0.01f), 1f, 1f, 1f, out _taperRenderer);
			CreatePart((PrimitiveType)0, "Nib", new Vector3(0f, 0f, 0.1465f), new Vector3(0.007f, 0.007f, 0.007f), 1f, 1f, 1f, out _nibRenderer);
			GameObject val = new GameObject("Tip");
			val.transform.SetParent(_root.transform, false);
			val.transform.localPosition = new Vector3(0f, 0f, 0.15f);
			val.transform.localRotation = Quaternion.identity;
			Tip = val.transform;
			return true;
		}

		private void CreatePart(PrimitiveType type, string name, Vector3 localPosition, Vector3 localScale, float r, float g, float b, out Renderer? renderer)
		{
			//IL_0000: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = GameObject.CreatePrimitive(type);
			((Object)val).name = name;
			val.transform.SetParent(_root.transform, false);
			val.transform.localPosition = localPosition;
			val.transform.localRotation = (((int)type == 2) ? Quaternion.Euler(90f, 0f, 0f) : Quaternion.identity);
			val.transform.localScale = localScale;
			Collider component = val.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			renderer = val.GetComponent<Renderer>();
			Renderer? obj = renderer;
			if (obj != null)
			{
				obj.sharedMaterial = PenMaterials.GetLineMaterial(r, g, b);
			}
		}

		public void ApplyFit(in PenFit fit)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root == (Object)null) && (!_fitApplied || !(_fit == fit)))
			{
				_fit = fit;
				_fitApplied = true;
				Transform transform = _root.transform;
				transform.localPosition = fit.Offset;
				transform.localRotation = Quaternion.Euler(fit.Rotation);
				transform.localScale = Vector3.one * fit.Scale;
			}
		}

		public void SetInkColor(float r, float g, float b)
		{
			Material lineMaterial = PenMaterials.GetLineMaterial(r, g, b);
			Renderer? taperRenderer = _taperRenderer;
			if (taperRenderer != null)
			{
				taperRenderer.sharedMaterial = lineMaterial;
			}
			Renderer? nibRenderer = _nibRenderer;
			if (nibRenderer != null)
			{
				nibRenderer.sharedMaterial = lineMaterial;
			}
		}

		public void Destroy()
		{
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
			_root = null;
			Tip = null;
			_taperRenderer = null;
			_nibRenderer = null;
			_fitApplied = false;
		}

		public void ForgetSceneObjects()
		{
			_root = null;
			Tip = null;
			_taperRenderer = null;
			_nibRenderer = null;
			_fitApplied = false;
		}
	}
	public sealed class PenSession
	{
		private const float PressThreshold = 0.5f;

		private const float MinPressureScale = 0.3f;

		private readonly StrokeStore _store;

		private readonly StrokeRenderer _renderer = new StrokeRenderer();

		private readonly PenRig _rig = new PenRig();

		private readonly PenPalette _palette = new PenPalette();

		private readonly DrawingRepository _repository;

		private readonly PenNetwork _network;

		private readonly RemoteStrokes _remote = new RemoteStrokes();

		private readonly PointBatcher _outgoing = new PointBatcher();

		private StrokeData? _sharedStroke;

		private const float PresenceIntervalSeconds = 2f;

		private bool _presencePublished;

		private float _presenceTimer;

		private readonly InputService _inputs;

		private Vector3? _tip;

		private readonly List<StrokeData> _erased = new List<StrokeData>();

		private bool _penRequested;

		private Color _lastInkColor = Color.white;

		private bool _warnedNoAnchor;

		private bool PenReady
		{
			get
			{
				if (_penRequested)
				{
					Vector3? tip = _tip;
					return tip.HasValue;
				}
				return false;
			}
		}

		public bool IsPenOut => _rig.IsAlive;

		public int StrokeCount => _store.Count;

		public string CurrentScene { get; private set; } = string.Empty;

		public PenSession(PenMod mod, InputService inputs, DrawingRepository? repository = null)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			_repository = repository ?? new DrawingRepository();
			_network = new PenNetwork(mod);
			_inputs = inputs;
			_store = new StrokeStore(_network.NextStrokeId);
			inputs.Register("Draw", "Draw", "Lay down ink while these are held.", BindingPreset.DominantTrigger, ActivationMode.WhileHeld, activationIsFixed: true).OnState = HandleDrawing;
			inputs.Register("Erase", "Erase", "Rub out whole strokes the nib touches while these are held.", BindingPreset.DominantGrip, ActivationMode.WhileHeld, activationIsFixed: true).OnState = HandleErasing;
			inputs.Register("CycleColour", "Cycle Colour", "Step the ink through the palette.", BindingPreset.DominantPrimary, ActivationMode.Tap).OnFired = CycleColour;
			inputs.Register("Undo", "Undo", "Remove your most recent stroke.", BindingPreset.DominantSecondary, ActivationMode.Tap).OnFired = Undo;
			inputs.Register("Clear", "Clear", "Wipe your own drawing. Other players' strokes are left alone.", BindingPreset.DominantSecondary, ActivationMode.Hold).OnFired = delegate
			{
				if (PenReady)
				{
					ClearDrawing();
				}
			};
			inputs.Register("TogglePen", "Spawn / Put Away", "Spawn/remove the pen from your hand.", BindingPreset.NonDominantPrimaryAndSecondary, ActivationMode.Hold, activationIsFixed: false, 0.35f).OnFired = TogglePen;
			inputs.DeleteSettings("HoldToClearSeconds", "ToggleCombo", "ToggleHoldSeconds", "DesktopHotkey");
		}

		public static bool IsDrawableScene(string scene)
		{
			if (scene == "Park" || scene == "Gym")
			{
				return true;
			}
			return false;
		}

		public void OnSceneChanged(string newScene)
		{
			if (IsDrawableScene(CurrentScene) && Settings.AutoSave.Value && _store.Count > 0)
			{
				TrySave();
			}
			_renderer.ForgetSceneObjects();
			_rig.ForgetSceneObjects();
			PenMaterials.ResetCache();
			_store.Clear();
			_remote.ClearAll();
			_outgoing.Discard();
			_network.DiscardQueuedReplays();
			_network.ForgetPeers();
			_sharedStroke = null;
			_presencePublished = false;
			_presenceTimer = 0f;
			_inputs.Reset();
			_tip = null;
			_warnedNoAnchor = false;
			CurrentScene = newScene;
			if (!IsDrawableScene(newScene))
			{
				_penRequested = false;
			}
			else if (Settings.AutoLoad.Value && _repository.HasSave(newScene))
			{
				TryLoad();
			}
		}

		public void TogglePen()
		{
			if (!IsDrawableScene(CurrentScene))
			{
				PenMod.Logger.Msg("The pen only works in the Park and the Gym (currently in '" + CurrentScene + "').");
				return;
			}
			if (_penRequested)
			{
				PutPenAway();
				return;
			}
			_penRequested = true;
			_warnedNoAnchor = false;
			TryBuildRig();
		}

		private void PutPenAway()
		{
			_penRequested = false;
			StrokeData activeStroke = _store.ActiveStroke;
			if (activeStroke != null)
			{
				StrokeData strokeData = _store.EndStroke();
				if (strokeData != null)
				{
					_renderer.Sync(strokeData);
				}
				else
				{
					_renderer.Remove(activeStroke);
				}
			}
			_rig.Destroy();
		}

		public void Tick(float deltaTime)
		{
			if (IsDrawableScene(CurrentScene))
			{
				PumpNetwork(deltaTime);
				SyncInkColorFromSettings();
				PositionPen();
				_inputs.Update(deltaTime, (Settings.Hand.Value == PenHand.Left) ? InputHand.Left : InputHand.Right);
			}
		}

		private void PositionPen()
		{
			_tip = null;
			if (_penRequested && (_rig.IsAlive || TryBuildRig()))
			{
				_rig.ApplyFit(PenFit.FromSettings());
				_tip = _rig.TipPosition;
			}
		}

		private void HandleDrawing(InputAction action)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!PenReady)
			{
				return;
			}
			Vector3 value = _tip.Value;
			if (action.IsHeld)
			{
				if (!_store.IsDrawing)
				{
					Color value2 = Settings.InkColor.Value;
					_store.BeginStroke(value2.r, value2.g, value2.b);
				}
				float width = WidthFor(action.Analog);
				PenPoint point = new PenPoint(value.x, value.y, value.z, width);
				if (!_store.TryAddPoint(in point, Settings.PointSpacing.Value))
				{
					return;
				}
				StrokeData activeStroke = _store.ActiveStroke;
				if (activeStroke != null)
				{
					_renderer.Sync(activeStroke);
					if (activeStroke != _sharedStroke)
					{
						BeginSharedStroke(activeStroke);
					}
					_outgoing.Add(in point);
				}
			}
			else if (_store.IsDrawing)
			{
				StrokeData activeStroke2 = _store.ActiveStroke;
				StrokeData strokeData = _store.EndStroke();
				if (strokeData != null)
				{
					_renderer.Sync(strokeData);
				}
				else if (activeStroke2 != null)
				{
					_renderer.Remove(activeStroke2);
				}
				EndSharedStroke();
			}
		}

		private void PumpNetwork(float deltaTime)
		{
			_network.EnsureRegistered();
			_network.PumpReplayQueue();
			RefreshPresence(deltaTime);
			_outgoing.Advance(deltaTime);
			if (_sharedStroke != null && _outgoing.TryFlush(out List<PenPoint> batch))
			{
				_network.SendStrokePoints(_sharedStroke.Id, batch);
			}
		}

		private void RefreshPresence(float deltaTime)
		{
			if (!PenNetwork.CanTransmit || !PenNetwork.NetworkingEnabled)
			{
				return;
			}
			_presenceTimer -= deltaTime;
			if (_presenceTimer > 0f)
			{
				return;
			}
			_presenceTimer = 2f;
			if (!_presencePublished)
			{
				PenNetwork.PublishPresence();
				_presencePublished = true;
			}
			foreach (int item in _network.RefreshPeers())
			{
				_network.SendSyncRequest(item, Settings.SyncOnJoin.Value);
			}
			DropDepartedPlayersStrokes();
		}

		private void DropDepartedPlayersStrokes()
		{
			IReadOnlyCollection<int> presentActors = _network.PresentActors;
			if (presentActors.Count == 0)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (StrokeData item in _remote.All)
			{
				if (StrokeId.TryGetOwner(item.Id, out var ownerActorNumber) && !presentActors.Contains(ownerActorNumber))
				{
					list.Add(item.Id);
				}
			}
			if (list.Count > 0)
			{
				_renderer.RemoveAll(_remote.Erase(list));
			}
		}

		private void BeginSharedStroke(StrokeData stroke)
		{
			EndSharedStroke();
			_sharedStroke = stroke;
			_network.SendStrokeStart(stroke.Id, stroke.ColorR, stroke.ColorG, stroke.ColorB);
		}

		private void EndSharedStroke()
		{
			if (_sharedStroke != null)
			{
				List<PenPoint> list = _outgoing.FlushRemaining();
				if (list.Count > 0)
				{
					_network.SendStrokePoints(_sharedStroke.Id, list);
				}
				_network.SendStrokeEnd(_sharedStroke.Id);
				_sharedStroke = null;
			}
		}

		public void OnNetworkMessage(PenMessage message)
		{
			if (!IsDrawableScene(CurrentScene))
			{
				return;
			}
			switch (message.Kind)
			{
			case PenMessageKind.StrokeStart:
				if (Settings.ShowOthersDrawings.Value)
				{
					_remote.Start(message.StrokeId, message.ColorR, message.ColorG, message.ColorB);
				}
				break;
			case PenMessageKind.StrokePoints:
				if (Settings.ShowOthersDrawings.Value)
				{
					StrokeData strokeData = _remote.Append(message.StrokeId, message.Points);
					if (strokeData != null)
					{
						_renderer.Sync(strokeData);
					}
				}
				break;
			case PenMessageKind.Unknown:
			case PenMessageKind.StrokeEnd:
				break;
			case PenMessageKind.Erase:
				ApplyRemoteErase(message.StrokeIds);
				break;
			case PenMessageKind.Clear:
				_renderer.RemoveAll(_remote.ClearOwner(message.Actor));
				break;
			case PenMessageKind.SyncRequest:
				_network.AddPeer(message.Actor);
				if (message.WantsReplay && Settings.SyncOnJoin.Value && Settings.ShareDrawings.Value)
				{
					_network.QueueReplay(_store.Strokes, message.Actor);
				}
				break;
			case PenMessageKind.Hello:
				_network.AddPeer(message.Actor);
				break;
			}
		}

		private void ApplyRemoteErase(IReadOnlyList<string> strokeIds)
		{
			_renderer.RemoveAll(_remote.Erase(strokeIds));
			foreach (string strokeId in strokeIds)
			{
				StrokeData strokeData = _store.RemoveById(strokeId);
				if (strokeData != null)
				{
					_renderer.Remove(strokeData);
				}
			}
		}

		private static float WidthFor(float trigger)
		{
			float value = Settings.LineWidth.Value;
			if (!Settings.PressureSensitivity.Value)
			{
				return value;
			}
			float num = Mathf.InverseLerp(0.5f, 1f, trigger);
			return Mathf.Lerp(value * 0.3f, value, num);
		}

		private void HandleErasing(InputAction action)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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)
			if (PenReady && action.IsHeld)
			{
				Vector3 value = _tip.Value;
				PenPoint probe = new PenPoint(value.x, value.y, value.z, 0f);
				float value2 = Settings.EraserRadius.Value;
				_erased.Clear();
				_store.EraseNear(in probe, value2, _erased);
				if (Settings.ShowOthersDrawings.Value)
				{
					_remote.EraseNear(in probe, value2, _erased);
				}
				if (_erased.Count != 0)
				{
					_renderer.RemoveAll(_erased);
					BroadcastErased(_erased);
				}
			}
		}

		private void BroadcastErased(IEnumerable<StrokeData> strokes)
		{
			List<string> list = new List<string>();
			foreach (StrokeData stroke in strokes)
			{
				if (!string.IsNullOrEmpty(stroke.Id))
				{
					list.Add(stroke.Id);
				}
			}
			_network.SendErase(list);
		}

		private void CycleColour()
		{
			//IL_003b: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			if (PenReady)
			{
				PaletteColor paletteColor = _palette.Advance();
				Color val = default(Color);
				((Color)(ref val))..ctor(paletteColor.R, paletteColor.G, paletteColor.B, 1f);
				Settings.InkColor.Value = val;
				_lastInkColor = val;
				_rig.SetInkColor(paletteColor.R, paletteColor.G, paletteColor.B);
			}
		}

		private void Undo()
		{
			if (PenReady)
			{
				StrokeData strokeData = _store.Undo();
				if (strokeData != null)
				{
					_renderer.Remove(strokeData);
					BroadcastErased(new <>z__ReadOnlySingleElementList<StrokeData>(strokeData));
				}
			}
		}

		private void SyncInkColorFromSettings()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			Color value = Settings.InkColor.Value;
			if (!Mathf.Approximately(value.r, _lastInkColor.r) || !Mathf.Approximately(value.g, _lastInkColor.g) || !Mathf.Approximately(value.b, _lastInkColor.b))
			{
				_lastInkColor = value;
				_palette.SelectNearest(value.r, value.g, value.b);
				_rig.SetInkColor(value.r, value.g, value.b);
			}
		}

		private bool TryBuildRig()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ResolveControllerTransform();
			if ((Object)(object)val == (Object)null)
			{
				if (!_warnedNoAnchor)
				{
					_warnedNoAnchor = true;
					PenMod.Logger.Warning("Waiting for the VR rig before the pen can be spawned.");
				}
				return false;
			}
			_warnedNoAnchor = false;
			if (!_rig.Build(val, PenFit.FromSettings()))
			{
				return false;
			}
			Color value = Settings.InkColor.Value;
			_rig.SetInkColor(value.r, value.g, value.b);
			return true;
		}

		private static Transform? ResolveControllerTransform()
		{
			try
			{
				PlayerController localPlayerController = Players.GetLocalPlayerController();
				if ((Object)(object)localPlayerController == (Object)null)
				{
					return null;
				}
				PlayerVR playerVR = localPlayerController.PlayerVR;
				if ((Object)(object)playerVR == (Object)null)
				{
					return null;
				}
				VRTransform obj = ((Settings.Hand.Value == PenHand.Left) ? playerVR.LeftController : playerVR.RightController);
				return (obj != null) ? obj.Transform : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		public void TrySave()
		{
			if (!IsDrawableScene(CurrentScene))
			{
				PenMod.Logger.Msg("Nothing to save here - the pen only works in the Park and the Gym.");
				return;
			}
			try
			{
				_repository.SaveScene(_store.Strokes, CurrentScene);
			}
			catch (Exception ex)
			{
				PenMod.Logger.Error("Could not save the drawing: " + ex.Message);
			}
		}

		public void TryLoad()
		{
			if (!IsDrawableScene(CurrentScene))
			{
				PenMod.Logger.Msg("Nothing to load here - the pen only works in the Park and the Gym.");
				return;
			}
			try
			{
				List<StrokeData> strokes = _repository.LoadScene(CurrentScene);
				_store.Replace(strokes);
				_renderer.RebuildAll(_store.Strokes);
			}
			catch (Exception ex)
			{
				PenMod.Logger.Error("Could not load the drawing: " + ex.Message);
			}
		}

		public void ClearDrawing()
		{
			_renderer.RemoveAll(_store.Clear());
			_outgoing.Discard();
			_sharedStroke = null;
			_network.SendClear();
		}

		public void DeleteSave()
		{
			try
			{
				if (!_repository.DeleteScene(CurrentScene))
				{
					PenMod.Logger.Msg("There is no saved drawing for " + CurrentScene + ".");
				}
			}
			catch (Exception ex)
			{
				PenMod.Logger.Error("Could not delete the save file: " + ex.Message);
			}
		}
	}
	internal static class RmapiRegistration
	{
		private static FieldInfo? _instanceField;

		private static FieldInfo? _registeredModsField;

		private static bool _resolved;

		private static bool _warned;

		public static bool Ensure(PenMod mod)
		{
			try
			{
				if (!Resolve())
				{
					return false;
				}
				object value = _instanceField.GetValue(null);
				if (value == null)
				{
					return false;
				}
				if (!(_registeredModsField.GetValue(value) is Dictionary<string, RumbleMod> dictionary))
				{
					return false;
				}
				string key = ((MelonBase)mod).Info.Name + "|" + ((MelonBase)mod).Info.Author;
				if (!dictionary.ContainsKey(key))
				{
					dictionary[key] = (RumbleMod)(object)mod;
				}
				((RumbleMod)mod).EventsRegisted = true;
				return true;
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					PenMod.Logger.Warning("Could not verify RMAPI event registration: " + ex.Message);
				}
				return false;
			}
		}

		private static bool Resolve()
		{
			if (_resolved)
			{
				if ((object)_instanceField != null)
				{
					return (object)_registeredModsField != null;
				}
				return false;
			}
			_resolved = true;
			Type nestedType = typeof(Utilities).GetNestedType("RaiseEventManager", BindingFlags.NonPublic);
			if ((object)nestedType == null)
			{
				return false;
			}
			_instanceField = nestedType.GetField("instance", BindingFlags.Static | BindingFlags.NonPublic);
			_registeredModsField = nestedType.GetField("RegisteredMods", BindingFlags.Instance | BindingFlags.NonPublic);
			if ((object)_instanceField != null)
			{
				return (object)_registeredModsField != null;
			}
			return false;
		}
	}
}
namespace RumbleParkPen.Unity.Rendering
{
	public static class PenMaterials
	{
		private static readonly string[] ShaderCandidates = new string[4] { "Universal Render Pipeline/Particles/Unlit", "Universal Render Pipeline/Unlit", "Sprites/Default", "Unlit/Color" };

		private static readonly Dictionary<int, Material> MaterialCache = new Dictionary<int, Material>();

		private static bool _resolved;

		[CompilerGenerated]
		private static Shader? <LineShader>k__BackingField;

		public static Shader? LineShader
		{
			get
			{
				if (_resolved)
				{
					return <LineShader>k__BackingField;
				}
				_resolved = true;
				string[] shaderCandidates = ShaderCandidates;
				for (int i = 0; i < shaderCandidates.Length; i++)
				{
					Shader val = Shader.Find(shaderCandidates[i]);
					if (!((Object)(object)val == (Object)null))
					{
						<LineShader>k__BackingField = val;
						return <LineShader>k__BackingField;
					}
				}
				PenMod.Logger.Error("No usable ink shader found. Strokes will render with Unity's default material. Tried: " + string.Join(", ", ShaderCandidates));
				return null;
			}
		}

		public static Material GetLineMaterial(float r, float g, float b)
		{
			//IL_0044: 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_004a: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			int key = QuantiseKey(r, g, b);
			if (MaterialCache.TryGetValue(key, out Material value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			Shader lineShader = LineShader;
			Material val = (((Object)(object)lineShader != (Object)null) ? new Material(lineShader) : new Material(Shader.Find("Diffuse")));
			Color val2 = default(Color);
			((Color)(ref val2))..ctor(r, g, b, 1f);
			if (val.HasProperty("_BaseColor"))
			{
				val.SetColor("_BaseColor", val2);
			}
			if (val.HasProperty("_Color"))
			{
				val.SetColor("_Color", val2);
			}
			if (val.HasProperty("_EmissionColor"))
			{
				val.SetColor("_EmissionColor", val2);
			}
			MaterialCache[key] = val;
			return val;
		}

		private static int QuantiseKey(float r, float g, float b)
		{
			int num = Mathf.Clamp(Mathf.RoundToInt(r * 31f), 0, 31);
			int num2 = Mathf.Clamp(Mathf.RoundToInt(g * 31f), 0, 31);
			int num3 = Mathf.Clamp(Mathf.RoundToInt(b * 31f), 0, 31);
			return (num << 10) | (num2 << 5) | num3;
		}

		public static void ResetCache()
		{
			MaterialCache.Clear();
		}
	}
	public sealed class StrokeRenderer
	{
		private sealed class StrokeView
		{
			public GameObject Object;

			public LineRenderer Line;

			public int RenderedPoints;

			public bool VariableWidth;

			public int WidthCurvePoints;

			public float WidthMultiplier = -1f;
		}

		private const string RootName = "RumbleParkPen.Strokes";

		private const int RoundingVertices = 4;

		private const int MaxWidthCurveKeys = 12;

		private const int WidthCurveRefreshPoints = 10;

		private readonly Dictionary<StrokeData, StrokeView> _views = new Dictionary<StrokeData, StrokeView>();

		private GameObject? _root;

		public int ViewCount => _views.Count;

		private GameObject Root
		{
			get
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Expected O, but got Unknown
				if ((Object)(object)_root != (Object)null)
				{
					return _root;
				}
				_root = new GameObject("RumbleParkPen.Strokes");
				return _root;
			}
		}

		public void Sync(StrokeData stroke)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			if (!stroke.IsRenderable)
			{
				Remove(stroke);
				return;
			}
			if (!_views.TryGetValue(stroke, out StrokeView value) || (Object)(object)value.Object == (Object)null)
			{
				value = CreateView(stroke);
				_views[stroke] = value;
			}
			int count = stroke.Points.Count;
			if (value.RenderedPoints != count)
			{
				value.Line.positionCount = count;
				for (int i = ((count > value.RenderedPoints) ? value.RenderedPoints : 0); i < count; i++)
				{
					PenPoint penPoint = stroke.Points[i];
					value.Line.SetPosition(i, new Vector3(penPoint.X, penPoint.Y, penPoint.Z));
				}
				value.RenderedPoints = count;
				ApplyWidth(stroke, value);
			}
		}

		private StrokeView CreateView(StrokeData stroke)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0098: 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)
			GameObject val = new GameObject("Stroke");
			val.transform.SetParent(Root.transform, true);
			LineRenderer val2 = val.AddComponent<LineRenderer>();
			val2.useWorldSpace = true;
			val2.numCapVertices = 4;
			val2.numCornerVertices = 4;
			val2.alignment = (LineAlignment)0;
			val2.textureMode = (LineTextureMode)0;
			((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val2).receiveShadows = false;
			Material lineMaterial = PenMaterials.GetLineMaterial(stroke.ColorR, stroke.ColorG, stroke.ColorB);
			((Renderer)val2).sharedMaterial = lineMaterial;
			Color val3 = default(Color);
			((Color)(ref val3))..ctor(stroke.ColorR, stroke.ColorG, stroke.ColorB, 1f);
			val2.startColor = val3;
			val2.endColor = val3;
			return new StrokeView
			{
				Object = val,
				Line = val2
			};
		}

		private static void ApplyWidth(StrokeData stroke, StrokeView view)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			float num = stroke.MaxWidth;
			if (num <= 0f)
			{
				num = 0.01f;
			}
			if (view.WidthMultiplier != num)
			{
				view.Line.widthMultiplier = num;
				view.WidthMultiplier = num;
			}
			if (!stroke.HasVariableWidth)
			{
				if (view.VariableWidth)
				{
					view.Line.widthCurve = AnimationCurve.Constant(0f, 1f, 1f);
					view.VariableWidth = false;
				}
				return;
			}
			int count = stroke.Points.Count;
			if (!view.VariableWidth || count <= 12 || count - view.WidthCurvePoints >= 10)
			{
				int num2 = Mathf.Min(count, 12);
				AnimationCurve val = new AnimationCurve();
				for (int i = 0; i < num2; i++)
				{
					float num3 = ((num2 == 1) ? 0f : ((float)i / (float)(num2 - 1)));
					int index = Mathf.RoundToInt(num3 * (float)(count - 1));
					val.AddKey(num3, stroke.Points[index].Width / num);
				}
				view.Line.widthCurve = val;
				view.VariableWidth = true;
				view.WidthCurvePoints = count;
			}
		}

		public void Remove(StrokeData stroke)
		{
			if (_views.TryGetValue(stroke, out StrokeView value))
			{
				_views.Remove(stroke);
				if ((Object)(object)value.Object != (Object)null)
				{
					Object.Destroy((Object)(object)value.Object);
				}
			}
		}

		public void RemoveAll(IEnumerable<StrokeData> strokes)
		{
			foreach (StrokeData stroke in strokes)
			{
				Remove(stroke);
			}
		}

		public void Clear()
		{
			foreach (StrokeView value in _views.Values)
			{
				if ((Object)(object)value.Object != (Object)null)
				{
					Object.Destroy((Object)(object)value.Object);
				}
			}
			_views.Clear();
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
				_root = null;
			}
		}

		public void ForgetSceneObjects()
		{
			_views.Clear();
			_root = null;
		}

		public void RebuildAll(IEnumerable<StrokeData> strokes)
		{
			Clear();
			foreach (StrokeData stroke in strokes)
			{
				Sync(stroke);
			}
		}
	}
}
namespace RumbleParkPen.Unity.Input
{
	public sealed class BindingSettingsRow
	{
		private readonly bool _modeIsFixed;

		public InputAction Action { get; }

		public ActivationMode FixedMode { get; }

		public MelonPreferences_Entry<BindingPreset> Preset { get; private set; }

		public MelonPreferences_Entry<string> Custom { get; private set; }

		public MelonPreferences_Entry<ActivationMode>? Mode { get; private set; }

		public MelonPreferences_Entry<float>? HoldSeconds { get; private set; }

		public ActivationMode CurrentMode
		{
			get
			{
				if (!_modeIsFixed)
				{
					return Mode.Value;
				}
				return FixedMode;
			}
		}

		public float CurrentHoldSeconds => HoldSeconds?.Value ?? 1f;

		public InputBinding CurrentBinding => Preset.Value.Resolve(Custom.Value);

		private ActivationMode EditedMode
		{
			get
			{
				if (!_modeIsFixed)
				{
					return Mode.EditedValue;
				}
				return FixedMode;
			}
		}

		private BindingSettingsRow(InputAction action, bool modeIsFixed, ActivationMode fixedMode)
		{
			Action = action;
			_modeIsFixed = modeIsFixed;
			FixedMode = fixedMode;
		}

		internal static BindingSettingsRow Create(InputService owner, MelonPreferences_Category category, InputAction action, string description, BindingPreset defaultPreset, ActivationMode defaultMode, bool modeIsFixed, float defaultHoldSeconds)
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			BindingSettingsRow bindingSettingsRow = new BindingSettingsRow(action, modeIsFixed, defaultMode);
			string id = action.Id;
			string label = action.Label;
			bindingSettingsRow.Preset = category.CreateEntry<BindingPreset>(id + "Binding", defaultPreset, label, description, false, false, (ValueValidator)null, (string)null);
			bindingSettingsRow.Custom = category.CreateEntry<string>(id + "Custom", defaultPreset.Resolve(null).ToString(), label + " (Custom)", "Buttons joined with +, all held together. For example NonDominantPrimary+NonDominantSecondary, DominantTrigger, LeftStickClick, Key:F7, or None.\n", false, false, (ValueValidator)null, (string)null);
			if (!modeIsFixed)
			{
				bindingSettingsRow.Mode = category.CreateEntry<ActivationMode>(id + "Mode", defaultMode, label + " Activation", "Button activiation mode.", false, false, (ValueValidator)null, (string)null);
				bindingSettingsRow.HoldSeconds = category.CreateEntry<float>(id + "HoldSeconds", defaultHoldSeconds, label + " Hold Time", "How long the buttons must be held.", false, false, (ValueValidator)new SliderDescriptor
				{
					Min = 0.1f,
					Max = 3f,
					DecimalPlaces = 2
				}, (string)null);
			}
			((MelonEventBase<LemonAction<BindingPreset, BindingPreset>>)(object)bindingSettingsRow.Preset.OnEntryValueChanged).Subscribe((LemonAction<BindingPreset, BindingPreset>)delegate
			{
				owner.HasChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)bindingSettingsRow.Custom.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				owner.HasChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<ActivationMode, ActivationMode>>)(object)bindingSettingsRow.Mode?.OnEntryValueChanged).Subscribe((LemonAction<ActivationMode, ActivationMode>)delegate
			{
				owner.HasChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)bindingSettingsRow.HoldSeconds?.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate
			{
				owner.HasChanged();
			}, 0, false);
			return bindingSettingsRow;
		}

		public bool ApplyVisibility()
		{
			bool flag = Preset.EditedValue != BindingPreset.Custom;
			bool flag2 = EditedMode != ActivationMode.Hold;
			bool result = ((MelonPreferences_Entry)Custom).IsHidden != flag || (HoldSeconds != null && ((MelonPreferences_Entry)HoldSeconds).IsHidden != flag2);
			((MelonPreferences_Entry)Custom).IsHidden = flag;
			MelonPreferences_Entry<float>? holdSeconds = HoldSeconds;
			if (holdSeconds != null)
			{
				((MelonPreferences_Entry)holdSeconds).IsHidden = flag2;
				return result;
			}
			return result;
		}

		public string? BindingError()
		{
			if (Preset.Value != BindingPreset.Custom)
			{
				return null;
			}
			if (!InputBinding.TryParse(Custom.Value, out InputBinding _, out string error))
			{
				return error;
			}
			return null;
		}
	}
	public sealed class ControllerReader(Instance logger)
	{
		private readonly InputsSnapshot _snapshot = new InputsSnapshot();

		private readonly Dictionary<string, KeyCode> _keyCodes = new Dictionary<string, KeyCode>(StringComparer.OrdinalIgnoreCase);

		private List<string> _boundKeys = new List<string>();

		private bool _warnedUnknownKey;

		public void SetBoundKeys(IEnumerable<string> keys)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			_boundKeys = keys.ToList();
			foreach (string boundKey in _boundKeys)
			{
				if (!_keyCodes.ContainsKey(boundKey))
				{
					if (Enum.TryParse<KeyCode>(boundKey, ignoreCase: true, out KeyCode result))
					{
						_keyCodes[boundKey] = result;
					}
					else if (!_warnedUnknownKey)
					{
						_warnedUnknownKey = true;
						logger.Warning("'" + boundKey + "' is not a key name, so that binding will never fire. Use a Unity KeyCode such as F7, LeftShift or Space.");
					}
				}
			}
		}

		public InputsSnapshot? TryRead(InputHand penHand)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				_snapshot.BeginFrame(penHand);
				Fill(InputHand.Left);
				Fill(InputHand.Right);
				foreach (string boundKey in _boundKeys)
				{
					if (_keyCodes.TryGetValue(boundKey, out var value) && Input.GetKey(value))
					{
						_snapshot.SetKeyHeld(boundKey);
					}
				}
				return _snapshot;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private void Fill(InputHand hand)
		{
			if (hand == InputHand.Left)
			{
				_snapshot.Set(hand, InputButton.Trigger, LeftController.GetTrigger());
				_snapshot.Set(hand, InputButton.Grip, LeftController.GetGrip());
				_snapshot.Set(hand, InputButton.Primary, LeftController.GetPrimary());
				_snapshot.Set(hand, InputButton.Secondary, LeftController.GetSecondary());
				_snapshot.Set(hand, InputButton.StickClick, LeftController.GetJoystickClick());
			}
			else
			{
				_snapshot.Set(hand, InputButton.Trigger, RightController.GetTrigger());
				_snapshot.Set(hand, InputButton.Grip, RightController.GetGrip());
				_snapshot.Set(hand, InputButton.Primary, RightController.GetPrimary());
				_snapshot.Set(hand, InputButton.Secondary, RightController.GetSecondary());
				_snapshot.Set(hand, InputButton.StickClick, RightController.GetJoystickClick());
			}
		}
	}
	public sealed class InputService
	{
		private readonly MelonBase _melon;

		private readonly ControllerReader _reader;

		private readonly InputMap _map = new InputMap();

		private readonly List<BindingSettingsRow> _settings = new List<BindingSettingsRow>();

		private int _revision = 1;

		private int _applied;

		public MelonPreferences_Category Category { get; }

		public IReadOnlyList<InputAction> Actions => _map.Actions;

		public InputService(MelonBase melon, string categoryId, string categoryLabel, string configPath)
		{
			_melon = melon;
			_reader = new ControllerReader(melon.LoggerInstance);
			Category = MelonPreferences.CreateCategory(categoryId, categoryLabel);
			Category.SetFilePath(configPath);
		}

		public InputAction Register(string id, string label, string description, BindingPreset defaultBinding, ActivationMode defaultActivation, bool activationIsFixed = false, float defaultHoldSeconds = 1f)
		{
			InputAction inputAction = _map.Register(id, label);
			_settings.Add(BindingSettingsRow.Create(this, Category, inputAction, description, defaultBinding, defaultActivation, activationIsFixed, defaultHoldSeconds));
			return inputAction;
		}

		public void DeleteSettings(params string[] retiredEntries)
		{
			foreach (BindingSettingsRow setting in _settings)
			{
				setting.ApplyVisibility();
			}
			foreach (string text in retiredEntries)
			{
				try
				{
					Category.DeleteEntry(text);
				}
				catch (Exception)
				{
				}
			}
		}

		public bool Update(float deltaTime, InputHand primaryHand)
		{
			ApplySettingsIfChanged();
			InputsSnapshot inputsSnapshot = _reader.TryRead(primaryHand);
			if (inputsSnapshot == null)
			{
				return false;
			}
			_map.Update(inputsSnapshot, deltaTime);
			return true;
		}

		public void Reset()
		{
			_map.Reset();
		}

		internal void HasChanged()
		{
			_revision++;
		}

		public void RefreshVisibility()
		{
			bool flag = false;
			foreach (BindingSettingsRow setting in _settings)
			{
				flag |= setting.ApplyVisibility();
			}
			if (flag)
			{
				UI.RequestRefresh(_melon);
			}
		}

		private void ApplySettingsIfChanged()
		{
			if (_applied == _revision)
			{
				return;
			}
			_applied = _revision;
			foreach (BindingSettingsRow setting in _settings)
			{
				_map.Configure(setting.Action, setting.CurrentBinding, setting.CurrentMode, setting.CurrentHoldSeconds);
				string text = setting.BindingError();
				if (text != null)
				{
					_melon.LoggerInstance.Warning(setting.Action.Label + " binding: " + text);
				}
			}
			_reader.SetBoundKeys(_map.BoundKeys());
		}
	}
}
namespace RumbleParkPen.Services.Sync
{
	public enum PenMessageKind
	{
		Unknown,
		StrokeStart,
		StrokePoints,
		StrokeEnd,
		Erase,
		Clear,
		SyncRequest,
		Hello
	}
	public sealed class PenMessage
	{
		public PenMessageKind Kind { get; init; }

		public string StrokeId { get; init; } = string.Empty;

		public int Actor { get; init; }

		public float ColorR { get; init; }

		public float ColorG { get; init; }

		public float ColorB { get; init; }

		public IReadOnlyList<PenPoint> Points { get; init; } = Array.Empty<PenPoint>();

		public IReadOnlyList<string> StrokeIds { get; init; } = Array.Empty<string>();

		public bool WantsReplay { get; init; }
	}
	public static class PenSyncFormat
	{
		public const int ProtocolVersion = 1;

		private const char FieldSeparator = ';';

		private const char ValueSeparator = ',';

		private const char StartTag = 'S';

		private const char PointsTag = 'P';

		private const char EndTag = 'E';

		private const char EraseTag = 'X';

		private const char ClearTag = 'C';

		private const char SyncTag = 'Q';

		private const char HelloTag = 'H';

		private const string PositionFormat = "0.###";

		private const string WidthFormat = "0.####";

		private const string ColorFormat = "0.###";

		private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture;

		public static string StrokeStart(string strokeId, float r, float g, float b)
		{
			return string.Join(';', 1.ToString(Invariant), 'S', strokeId, r.ToString("0.###", Invariant), g.ToString("0.###", Invariant), b.ToString("0.###", Invariant));
		}

		public static string StrokePoints(string strokeId, IReadOnlyList<PenPoint> points)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(1.ToString(Invariant)).Append(';').Append('P')
				.Append(';')
				.Append(strokeId)
				.Append(';');
			for (int i = 0; i < points.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				PenPoint penPoint = points[i];
				stringBuilder.Append(penPoint.X.ToString("0.###", Invariant)).Append(',').Append(penPoint.Y.ToString("0.###", Invariant))
					.Append(',')
					.Append(penPoint.Z.ToString("0.###", Invariant))
					.Append(',')
					.Append(penPoint.Width.ToString("0.####", Invariant));
			}
			return stringBuilder.ToString();
		}

		public static string StrokeEnd(string strokeId)
		{
			return string.Join(';', 1.ToString(Invariant), 'E', strokeId);
		}

		public static string Erase(IReadOnlyList<string> strokeIds)
		{
			return string.Join(';', 1.ToString(Invariant), 'X', string.Join(',', strokeIds));
		}

		public static string Clear(int actor)
		{
			return string.Join(';', 1.ToString(Invariant), 'C', actor.ToString(Invariant));
		}

		public static string SyncRequest(int actor, bool wantsReplay)
		{
			return string.Join(';', 1.ToString(Invariant), 'Q', actor.ToString(Invariant), wantsReplay ? "1" : "0");
		}

		public static string Hello(int actor)
		{
			return string.Join(';', 1.ToString(Invariant), 'H', actor.ToString(Invariant));
		}

		public static PenMessage Decode(string? payload)
		{
			if (string.IsNullOrWhiteSpace(payload))
			{
				return Unknown();
			}
			string[] array = payload.Split(';');
			if (array.Length < 2)
			{
				return Unknown();
			}
			if (!int.TryParse(array[0], NumberStyles.Integer, Invariant, out var result) || result != 1)
			{
				return Unknown();
			}
			if (array[1].Length != 1)
			{
				return Unknown();
			}
			return array[1][0] switch
			{
				'S' => DecodeStart(array), 
				'P' => DecodePoints(array), 
				'E' => (array.Length >= 3) ? new PenMessage
				{
					Kind = PenMessageKind.StrokeEnd,
					StrokeId = array[2]
				} : Unknown(), 
				'X' => DecodeErase(array), 
				'C' => DecodeActorOnly(array, PenMessageKind.Clear), 
				'Q' => DecodeSyncRequest(array), 
				'H' => DecodeActorOnly(array, PenMessageKind.Hello), 
				_ => Unknown(), 
			};
		}

		private static PenMessage DecodeStart(string[] fields)
		{
			if (fields.Length < 6 || string.IsNullOrEmpty(fields[2]))
			{
				return Unknown();
			}
			return new PenMessage
			{
				Kind = PenMessageKind.StrokeStart,
				StrokeId = fields[2],
				ColorR = ParseFloat(fields[3]),
				ColorG = ParseFloat(fields[4]),
				ColorB = ParseFloat(fields[5])
			};
		}

		private static PenMessage DecodePoints(string[] fields)
		{
			if (fields.Length < 4 || string.IsNullOrEmpty(fields[2]))
			{
				return Unknown();
			}
			string[] array = fields[3].Split(',');
			List<PenPoint> list = new List<PenPoint>(array.Length / 4);
			for (int i = 0; i + 3 < array.Length; i += 4)
			{
				list.Add(new PenPoint(ParseFloat(array[i]), ParseFloat(array[i + 1]), ParseFloat(array[i + 2]), ParseFloat(array[i + 3])));
			}
			if (list.Count == 0)
			{
				return Unknown();
			}
			return new PenMessage
			{
				Kind = PenMessageKind.StrokePoints,
				StrokeId = fields[2],
				Points = list
			};
		}

		private static PenMessage DecodeErase(string[] fields)
		{
			if (fields.Length < 3)
			{
				return Unknown();
			}
			string[] array = fields[2].Split(',');
			List<string> list = new List<string>(array.Length);
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (!string.IsNullOrWhiteSpace(text))
				{
					list.Add(text);
				}
			}
			if (list.Count == 0)
			{
				return Unknown();
			}
			return new PenMessage
			{
				Kind = PenMessageKind.Erase,
				StrokeIds = list
			};
		}

		private static PenMessage DecodeSyncRequest(string[] fields)
		{
			PenMessage penMessage = DecodeActorOnly(fields, PenMessageKind.SyncRequest);
			if (penMessage.Kind == PenMessageKind.Unknown)
			{
				return penMessage;
			}
			bool wantsReplay = fields.Length < 4 || fields[3] != "0";
			return new PenMessage
			{
				Kind = PenMessageKind.SyncRequest,
				Actor = penMessage.Actor,
				WantsReplay = wantsReplay
			};
		}

		private static PenMessage DecodeActorOnly(string[] fields, PenMessageKind kind)
		{
			if (fields.Length < 3 || !int.TryParse(fields[2], NumberStyles.Integer, Invariant, out var result))
			{
				return Unknown();
			}
			return new PenMessage
			{
				Kind = kind,
				Actor = result
			};
		}

		private static float ParseFloat(string value)
		{
			if (!float.TryParse(value, NumberStyles.Float, Invariant, out var result))
			{
				return 0f;
			}
			return result;
		}

		private static PenMessage Unknown()
		{
			return new PenMessage
			{
				Kind = PenMessageKind.Unknown
			};
		}
	}
	public sealed class PointBatcher
	{
		private readonly List<PenPoint> _pending = new List<PenPoint>();

		private float _waited;

		public int MaxBatchSize { get; } = Math.Max(1, maxBatchSize);

		public float MaxDelaySeconds { get; } = Math.Max(0f, maxDelaySeconds);

		public int PendingCount => _pending.Count;

		public bool HasPending => _pending.Count > 0;

		public PointBatcher(int maxBatchSize = 12, float maxDelaySeconds = 0.1f)
		{
		}

		public void Add(in PenPoint point)
		{
			_pending.Add(point);
		}

		public void Advance(float deltaTime)
		{
			if (_pending.Count > 0)
			{
				_waited += deltaTime;
			}
		}

		public bool TryFlush([NotNullWhen(true)] out List<PenPoint>? batch)
		{
			if (_pending.Count == 0 || (_pending.Count < MaxBatchSize && _waited < MaxDelaySeconds))
			{
				batch = null;
				return false;
			}
			batch = TakeAll();
			return true;
		}

		public List<PenPoint> FlushRemaining()
		{
			return TakeAll();
		}

		public void Discard()
		{
			_pending.Clear();
			_waited = 0f;
		}

		private List<PenPoint> TakeAll()
		{
			List<PenPoint> result = new List<PenPoint>(_pending);
			_pending.Clear();
			_waited = 0f;
			return result;
		}
	}
	public sealed class RemoteStrokes
	{
		private readonly Dictionary<string, StrokeData> _byId = new Dictionary<string, StrokeData>();

		public int Count => _byId.Count;

		public IEnumerable<StrokeData> All => _byId.Values;

		public StrokeData? Find(string strokeId)
		{
			if (!_byId.TryGetValue(strokeId, out StrokeData value))
			{
				return null;
			}
			return value;
		}

		public StrokeData Start(string strokeId, float r, float g, float b)
		{
			StrokeData strokeData = new StrokeData(r, g, b)
			{
				Id = strokeId
			};
			_byId[strokeId] = strokeData;
			return strokeData;
		}

		public StrokeData? Append(string strokeId, IReadOnlyList<PenPoint> points)
		{
			if (!_byId.TryGetValue(strokeId, out StrokeData value))
			{
				return null;
			}
			foreach (PenPoint point2 in points)
			{
				PenPoint point = point2;
				value.Add(in point);
			}
			return value;
		}

		public void EraseNear(in PenPoint probe, float radius, List<StrokeData> removed)
		{
			List<string> list = null;
			foreach (StrokeData value in _byId.Values)
			{
				if (value.IsWithin(in probe, radius))
				{
					(list ?? (list = new List<string>())).Add(value.Id);
					removed.Add(value);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item in list)
			{
				_byId.Remove(item);
			}
		}

		public List<StrokeData> Erase(IReadOnlyList<string> strokeIds)
		{
			List<StrokeData> list = new List<StrokeData>();
			foreach (string strokeId in strokeIds)
			{
				if (_byId.TryGetValue(strokeId, out StrokeData value))
				{
					_byId.Remove(strokeId);
					list.Add(value);
				}
			}
			return list;
		}

		public List<StrokeData> ClearOwner(int actorNumber)
		{
			List<StrokeData> list = new List<StrokeData>();
			foreach (StrokeData value in _byId.Values)
			{
				if (value.IsOwnedBy(actorNumber))
				{
					list.Add(value);
				}
			}
			foreach (StrokeData item in list)
			{
				_byId.Remove(item.Id);
			}
			return list;
		}

		public List<StrokeData> ClearAll()
		{
			List<StrokeData> result = new List<StrokeData>(_byId.Values);
			_byId.Clear();
			return result;
		}
	}
}
namespace RumbleParkPen.Services.Persistence
{
	public sealed class DrawingRepository
	{
		public const string DefaultRootFolder = "UserData/RumbleParkPen";

		public string RootFolder { get; } = rootFolder ?? "UserData/RumbleParkPen";

		public DrawingRepository(string? rootFolder = null)
		{
		}

		public string PathForScene(string scene)
		{
			return Path.Combine(RootFolder, SanitiseSceneFile(scene) + ".json");
		}

		public bool HasSave(string scene)
		{
			return File.Exists(PathForScene(scene));
		}

		public string SaveScene(IEnumerable<StrokeData> strokes, string scene)
		{
			Directory.CreateDirectory(RootFolder);
			string text = PathForScene(scene);
			string contents = DrawingSerializer.Serialize(strokes, scene);
			string text2 = text + ".tmp";
			File.WriteAllText(text2, contents);
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			File.Move(text2, text);
			return text;
		}

		public List<StrokeData> LoadScene(string scene)
		{
			string path = PathForScene(scene);
			if (!File.Exists(path))
			{
				return new List<StrokeData>();
			}
			return DrawingSerializer.Deserialize(File.ReadAllText(path));
		}

		public bool DeleteScene(string scene)
		{
			string path = PathForScene(scene);
			if (!File.Exists(path))
			{
				return false;
			}
			File.Delete(path);
			return true;
		}

		private static string SanitiseSceneFile(string scene)
		{
			if (string.IsNullOrWhiteSpace(scene))
			{
				return "Unknown";
			}
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			StringBuilder stringBuilder = new StringBuilder(scene.Length);
			foreach (char c in scene)
			{
				stringBuilder.Append((Array.IndexOf(invalidFileNameChars, c) >= 0) ? '_' : c);
			}
			return stringBuilder.ToString();
		}
	}
	public static class DrawingSerializer
	{
		private sealed class DrawingDto
		{
			[JsonProperty("v")]
			public int Version { get; set; } = 1;

			[JsonProperty("scene")]
			public string Scene { get; set; } = string.Empty;

			[JsonProperty("strokes")]
			public List<StrokeDto> Strokes { get; set; } = new List<StrokeDto>();
		}

		private sealed class StrokeDto
		{
			[JsonProperty("c")]
			public float[]? Color { get; set; }

			[JsonProperty("p")]
			public float[] Points { get; set; } = Array.Empty<float>();
		}

		public const int CurrentVersion = 1;

		private const int FloatsPerPoint = 4;

		public static string Serialize(IEnumerable<StrokeData> strokes, string scene)
		{
			DrawingDto drawingDto = new DrawingDto
			{
				Version = 1,
				Scene = scene
			};
			foreach (StrokeData stroke in strokes)
			{
				if (stroke.IsRenderable)
				{
					float[] array = new float[stroke.Points.Count * 4];
					for (int i = 0; i < stroke.Points.Count; i++)
					{
						PenPoint penPoint = stroke.Points[i];
						int num = i * 4;
						array[num] = penPoint.X;
						array[num + 1] = penPoint.Y;
						array[num + 2] = penPoint.Z;
						array[num + 3] = penPoint.Width;
					}
					drawingDto.Strokes.Add(new StrokeDto
					{
						Color = new float[3] { stroke.ColorR, stroke.ColorG, stroke.ColorB },
						Points = array
					});
				}
			}
			return JsonConvert.SerializeObject((object)drawingDto, (Formatting)0);
		}

		public static List<StrokeData> Deserialize(string json)
		{
			List<StrokeData> list = new List<StrokeData>();
			if (string.IsNullOrWhiteSpace(json))
			{
				return list;
			}
			DrawingDto drawingDto = JsonConvert.DeserializeObject<DrawingDto>(json);
			if (drawingDto?.Strokes == null)
			{
				return list;
			}
			foreach (StrokeDto stroke in drawingDto.Strokes)
			{
				if (stroke?.Points != null)
				{
					float[] array = stroke.Color ?? Array.Empty<float>();
					StrokeData strokeData = new StrokeData((array.Length != 0) ? array[0] : 1f, (array.Length > 1) ? array[1] : 1f, (array.Length > 2) ? array[2] : 1f);
					int num = stroke.Points.Length / 4;
					for (int i = 0; i < num; i++)
					{
						int num2 = i * 4;
						strokeData.Add(new PenPoint(stroke.Points[num2], stroke.Points[num2 + 1], stroke.Points[num2 + 2], stroke.Points[num2 + 3]));
					}
					if (strokeData.IsRenderable)
					{
						list.Add(strokeData);
					}
				}
			}
			return list;
		}
	}
}
namespace RumbleParkPen.Services.Input
{
	public enum BindingPreset
	{
		None,
		DominantTrigger,
		DominantGrip,
		DominantPrimary,
		DominantSecondary,
		DominantStickClick,
		NonDominantTrigger,
		NonDominantGrip,
		NonDominantPrimary,
		NonDominantSecondary,
		NonDominantStickClick,
		DominantPrimaryAndSecondary,
		NonDominantPrimaryAndSecondary,
		NonDominantGripAndPrimary,
		BothTriggers,
		BothGrips,
		BothPrimaries,
		BothSecondaries,
		Custom
	}
	public static class BindingPresetExtensions
	{
		private static readonly Dictionary<BindingPreset, string> Combos = new Dictionary<BindingPreset, string>
		{
			[BindingPreset.DominantPrimaryAndSecondary] = "DominantPrimary+DominantSecondary",
			[BindingPreset.NonDominantPrimaryAndSecondary] = "NonDominantPrimary+NonDominantSecondary",
			[BindingPreset.NonDominantGripAndPrimary] = "NonDominantGrip+NonDominantPrimary",
			[BindingPreset.BothTriggers] = "LeftTrigger+RightTrigger",
			[BindingPreset.BothGrips] = "LeftGrip+RightGrip",
			[BindingPreset.BothPrimaries] = "LeftPrimary+RightPrimary",
			[BindingPreset.BothSecondaries] = "LeftSecondary+RightSecondary"
		};

		public static string? BindingText(this BindingPreset preset)
		{
			switch (preset)
			{
			case BindingPreset.Custom:
				return null;
			case BindingPreset.None:
				return "None";
			default:
			{
				if (!Combos.TryGetValue(preset, out string value))
				{
					return preset.ToString();
				}
				return value;
			}
			}
		}

		public static InputBinding Resolve(this BindingPreset preset, string? customText)
		{
			return InputBinding.Parse(preset.BindingText() ?? customText);
		}
	}
	public sealed class ButtonLatch
	{
		private bool _holdFired;

		public bool Tapped { get; private set; }

		public bool HoldTriggered { get; private set; }

		public bool WasPressed { get; private set; }

		public float HeldTime { get; private set; }

		public void Update(bool pressed, float deltaTime, float holdSeconds)
		{
			Tapped = false;
			HoldTriggered = false;
			if (pressed)
			{
				HeldTime += deltaTime;
				if (!_holdFired && HeldTime >= holdSeconds)
				{
					HoldTriggered = true;
					_holdFired = true;
				}
			}
			else if (WasPressed)
			{
				if (!_holdFired)
				{
					Tapped = true;
				}
				HeldTime = 0f;
				_holdFired = false;
			}
			WasPressed = pressed;
		}

		public void Reset()
		{
			_holdFired = false;
			WasPressed = false;
			HeldTime = 0f;
			Tapped = false;
			HoldTriggered = false;
		}
	}
	public sealed class InputAction(string id, string label)
	{
		public string Id { get; } = id;

		public string Label { get; } = label;

		public InputBinding Binding { get; private set; } = InputBinding.None;

		public ActivationMode Mode { get; private set; } = ActivationMode.Tap;

		public float HoldSeconds { get; private set; } = 1f;

		public bool IsHeld { get; internal set; }

		public bool Fired { get; internal set; }

		public float Analog { get; internal set; }

		public Action<InputAction>? OnState { get; set; }

		public Action? OnFired { get; set; }

		internal ButtonLatch Latch { get; } = new ButtonLatch();

		internal float LatchHoldSeconds { get; set; } = float.MaxValue;

		internal bool ShadowedThisPress { get; set; }

		internal InputAction[] ShadowedBy { get; set; } = Array.Empty<InputAction>();

		public void Configure(InputBinding binding, ActivationMode mode, float holdSeconds)
		{
			Binding = binding;
			Mode = mode;
			HoldSeconds = holdSeconds;
		}

		internal void Evaluate(InputsSnapshot snapshot, float deltaTime)
		{
			bool flag = Binding.IsSatisfiedBy(snapshot);
			if (!flag)
			{
				ShadowedThisPress = false;
			}
			else if (IsShadowed(snapshot))
			{
				ShadowedThisPress = true;
			}
			if (flag && ShadowedThisPress)
			{
				Latch.Reset();
				ClearState();
				return;
			}
			IsHeld = flag && Mode == ActivationMode.WhileHeld;
			Analog = (flag ? Binding.AnalogIn(snapshot) : 0f);
			Latch.Update(flag, deltaTime, LatchHoldSeconds);
			Fired = Mode switch
			{
				ActivationMode.Tap => Latch.Tapped, 
				ActivationMode.Hold => Latch.HoldTriggered, 
				ActivationMode.WhileHeld => false, 
				_ => false, 
			};
		}

		private bool IsShadowed(InputsSnapshot snapshot)
		{
			InputAction[] shadowedBy = ShadowedBy;
			for (int i = 0; i < shadowedBy.Length; i++)
			{
				if (shadowedBy[i].Binding.IsSatisfiedBy(snapshot))
				{
					return true;
				}
			}
			return false;
		}

		internal void Dispatch()
		{
			OnState?.Invoke(this);
			if (Fired)
			{
				OnFired?.Invoke();
			}
		}

		internal void ClearState()
		{
			IsHeld = false;
			Fired = false;
			Analog = 0f;
		}

		public override string ToString()
		{
			return $"{Id} ({Binding}, {Mode})";
		}
	}
	public sealed class InputMap
	{
		private readonly List<InputAction> _actions = new List<InputAction>();

		public IReadOnlyList<InputAction> Actions => _actions;

		public InputAction Register(string id, string label)
		{
			InputAction inputAction = new InputAction(id, label);
			_actions.Add(inputAction);
			return inputAction;
		}

		public InputAction? Find(string id)
		{
			return _actions.Find((InputAction a) => string.Equals(a.Id, id, StringComparison.Ordinal));
		}

		public void Configure(InputAction action, InputBinding binding, ActivationMode mode, float holdSeconds)
		{
			action.Configure(binding, mode, holdSeconds);
			RecomputeRelationships();
		}

		private void RecomputeRelationships()
		{
			foreach (InputAction action in _actions)
			{
				action.ShadowedBy = _actions.Where((InputAction other) => other != action && action.Binding.IsSubsetOf(other.Binding)).ToArray();
			}
			foreach (InputAction action2 in _actions)
			{
				if (action2.Mode == ActivationMode.Hold)
				{
					action2.LatchHoldSeconds = action2.HoldSeconds;
					continue;
				}
				if (action2.Binding.IsUnbound)
				{
					action2.LatchHoldSeconds = float.MaxValue;
					continue;
				}
				float num = float.MaxValue;
				foreach (InputAction action3 in _actions)
				{
					if (action3 != action2 && action3.Mode == ActivationMode.Hold && action2.Binding.HasSameInputsAs(action3.Binding))
					{
						num = Math.Min(num, action3.HoldSeconds);
					}
				}
				action2.LatchHoldSeconds = num;
			}
		}

		public void Update(InputsSnapshot snapshot, float deltaTime)
		{
			foreach (InputAction action in _actions)
			{
				action.Evaluate(snapshot, deltaTime);
			}
			foreach (InputAction action2 in _actions)
			{
				action2.Dispatch();
			}
		}

		public void Reset()
		{
			foreach (InputAction action in _actions)
			{
				action.Latch.Reset();
				action.ClearState();
				action.ShadowedThisPress = false;
			}
		}

		public List<string> BoundKeys()
		{
			List<string> list = new List<string>();
			foreach (InputAction action in _actions)
			{
				foreach (string key in action.Binding.Keys)
				{
					if (!list.Contains<string>(key, StringComparer.OrdinalIgnoreCase))
					{
						list.Add(key);
					}
				}
			}
			return list;
		}
	}
	public enum InputHand
	{
		Dominant,
		NonDominant,
		Left,
		Right
	}
	public enum InputButton
	{
		Trigger,
		Grip,
		Primary,
		Secondary,
		StickClick
	}
	public enum ActivationMode
	{
		WhileHeld,
		Tap,
		Hold
	}
	public readonly record struct ButtonLocation(InputHand Hand, InputButton Button);
	public sealed class InputBinding(IReadOnlyList<ButtonLocation> controller, IReadOnlyList<string> keys)
	{
		public static readonly InputBinding None = new InputBinding(Array.Empty<ButtonLocation>(), Array.Empty<string>());

		public const string UnboundText = "None";

		private const char TermSeparator = '+';

		private const string KeyPrefix = "Key:";

		private static readonly InputButton[] ButtonsByNameLength = (from b in Enum.GetValues<InputButton>()
			orderby b.ToString().Length descending
			select b).ToArray();

		public IReadOnlyList<ButtonLocation> Controller { get; } = controller;

		public IReadOnlyList<string> Keys { get; } = keys;

		public bool IsUnbound
		{
			get
			{
				if (Controller.Count == 0)
				{
					return Keys.Count == 0;
				}
				return false;
			}
		}

		public int TotalInputs => Controller.Count + Keys.Count;

		public bool IsSatisfiedBy(InputsSnapshot snapshot)
		{
			if (IsUnbound)
			{
				return false;
			}
			foreach (ButtonLocation item in Controller)
			{
				if (!snapshot.IsPressed(item))
				{
					return false;
				}
			}
			foreach (string key in Keys)
			{
				if (!snapshot.IsKeyHeld(key))
				{
					return false;
				}
			}
			return true;
		}

		public bool IsSubsetOf(InputBinding other)
		{
			if (IsUnbound || other.IsUnbound)
			{
				return false;
			}
			if (TotalInputs >= other.TotalInputs)
			{
				return false;
			}
			foreach (ButtonLocation item in Controller)
			{
				if (!other.Controller.Contains(item))
				{
					return false;
				}
			}
			foreach (string key in Keys)
			{
				if (!other.Keys.Contains<string>(key, StringComparer.OrdinalIgnoreCase))
				{
					return false;
				}
			}
			return true;
		}

		public bool HasSameInputsAs(InputBinding other)
		{
			if (TotalInputs != other.TotalInputs)
			{
				return false;
			}
			if (IsUnbound == other.IsUnbound && Controller.All(((IEnumerable<ButtonLocation>)other.Controller).Contains<ButtonLocation>))
			{
				return Keys.All((string k) => other.Keys.Contains<string>(k, StringComparer.OrdinalIgnoreCase));
			}
			return false;
		}

		public float AnalogIn(InputsSnapshot snapshot)
		{
			if (Controller.Count == 0)
			{
				return 1f;
			}
			float num = 1f;
			foreach (ButtonLocation item in Controller)
			{
				num = Math.Min(num, snapshot.Value(item.Hand, item.Button));
			}
			return num;
		}

		public static bool TryParse(string? text, out InputBinding binding, out string? error)
		{
			binding = None;
			error = null;
			if (string.IsNullOrWhiteSpace(text))
			{
				return true;
			}
			if (string.Equals(text.Trim(), "None", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			List<ButtonLocation> list = new List<ButtonLocation>();
			List<string> list2 = new List<string>();
			string[] array = text.Split('+');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				if (text2.StartsWith("Key:", StringComparison.OrdinalIgnoreCase))
				{
					string text3 = text2.Substring("Key:".Length).Trim();
					if (text3.Length == 0)
					{
						error = "'" + text2 + "' is missing a key name, e.g. Key:F7";
						return false;
					}
					if (!list2.Contains<string>(text3, StringComparer.OrdinalIgnoreCase))
					{
						list2.Add(text3);
					}
				}
				else
				{
					if (!TryParseButtonLocation(text2, out var parsed))
					{
						error = "'" + text2 + "' is not a button. Expected something like DominantTrigger, NonDominantPrimary, LeftGrip, RightStickClick or Key:F7";
						return false;
					}
					if (!list.Contains(parsed))
					{
						list.Add(parsed);
					}
				}
			}
			binding = ((list.Count == 0 && list2.Count == 0) ? None : new InputBinding(list, list2));
			return true;
		}

		public static InputBinding Parse(string? text)
		{
			if (!TryParse(text, out InputBinding binding, out string _))
			{
				return None;
			}
			return binding;
		}

		public override string ToString()
		{
			if (IsUnbound)
			{
				return "None";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (ButtonLocation item in Controller)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('+');
				}
				stringBuilder.Append(item.Hand).Append(item.Button);
			}
			foreach (string key in Keys)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('+');
				}
				stringBuilder.Append("Key:").Append(key);
			}
			return stringBuilder.ToString();
		}

		private static bool TryParseButtonLocation(string term, out ButtonLocation parsed)
		{
			parsed = default(ButtonLocation);
			InputButton[] buttonsByNameLength = ButtonsByNameLength;
			for (int i = 0; i < buttonsByNameLength.Length; i++)
			{
				InputButton button = buttonsByNameLength[i];
				string text = button.ToString();
				if (term.Length > text.Length && term.EndsWith(text, StringComparison.OrdinalIgnoreCase))
				{
					int length = text.Length;
					if (Enum.TryParse<InputHand>(term.Substring(0, term.Length - length), ignoreCase: true, out var result))
					{
						parsed = new ButtonLocation(result, button);
						return true;
					}
				}
			}
			return false;
		}
	}
	public sealed class InputsSnapshot
	{
		public const float PressThreshold = 0.5f;

		private const int Hands = 2;

		private const int Buttons = 5;

		private readonly float[,] _values = new float[2, 5];

		private readonly HashSet<string> _heldKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public InputHand DominantHand { get; private set; } = InputHand.Right;

		public void BeginFrame(InputHand dominantHand)
		{
			DominantHand = ((dominantHand == InputHand.Left) ? InputHand.Left : InputHand.Right);
			Array.Clear(_values, 0, _values.Length);
			_heldKeys.Clear();
		}

		public void Set(InputHand hand, InputButton button, float value)
		{
			_values[HandIndex(Resolve(hand)), (int)button] = value;
		}

		public void SetKeys(IEnumerable<string> heldKeys)
		{
			_heldKeys.Clear();
			foreach (string heldKey in heldKeys)
			{
				_heldKeys.Add(heldKey);
			}
		}

		public void SetKeyHeld(string key)
		{
			_heldKeys.Add(key);
		}

		public float Value(InputHand hand, InputButton button)
		{
			return _values[HandIndex(Resolve(hand)), (int)button];
		}

		public bool IsPressed(ButtonLocation term)
		{
			return Value(term.Hand, term.Button) > 0.5f;
		}

		public bool IsKeyHeld(string key)
		{
			return _heldKeys.Contains(key);
		}

		public InputHand Resolve(InputHand hand)
		{
			return hand switch
			{
				InputHand.Dominant => DominantHand, 
				InputHand.NonDominant => (DominantHand == InputHand.Left) ? InputHand.Right : InputHand.Left, 
				InputHand.Left => InputHand.Left, 
				InputHand.Right => InputHand.Right, 
				_ => hand, 
			};
		}

		private static int HandIndex(InputHand physical)
		{
			return (physical != InputHand.Left) ? 1 : 0;
		}
	}
}
namespace RumbleParkPen.Services.Drawing
{
	public readonly struct PaletteColor
	{
		public string Name { get; }

		public float R { get; }

		public float G { get; }

		public float B { get; }

		public PaletteColor(string name, float r, float g, float b)
		{
			Name = name;
			R = r;
			G = g;
			B = b;
		}

		public float SquaredDistanceTo(float r, float g, float b)
		{
			float num = R - r;
			float num2 = G - g;
			float num3 = B - b;
			return num * num + num2 * num2 + num3 * num3;
		}
	}
	public sealed class PenPalette
	{
		public static readonly IReadOnlyList<PaletteColor> Default = new <>z__ReadOnlyArray<PaletteColor>(new PaletteColor[10]
		{
			new PaletteColor("White", 1f, 1f, 1f),
			new PaletteColor("Red", 0.95f, 0.15f, 0.15f),
			new PaletteColor("Orange", 1f, 0.5f, 0.05f),
			new PaletteColor("Yellow", 1f, 0.92f, 0.1f),
			new PaletteColor("Green", 0.2f, 0.85f, 0.25f),
			new PaletteColor("Cyan