Decompiled source of RumbleParkPen v1.0.1

Mods\RumbleParkPen.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.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.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.0.1", "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.0.1")]
[assembly: AssemblyInformationalVersion("1.0.1+7a652fed283fd598ed88dcc750cb9dfbea675f89")]
[assembly: AssemblyProduct("RumbleParkPen")]
[assembly: AssemblyTitle("RumbleParkPen")]
[assembly: AssemblyVersion("1.0.1.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;

		public static Instance Logger { get; private set; }

		public override void OnInitializeMelon()
		{
			Logger = ((MelonBase)this).LoggerInstance;
			_session = new PenSession(this);
			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();
				}
			});
			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
			{
				if (Settings.DesktopHotkey.Value && Input.GetKeyDown((KeyCode)288))
				{
					_session.TogglePen();
				}
				_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 enum PenToggleCombo
	{
		Off,
		FreeHandPrimaryAndSecondary,
		FreeHandGripAndPrimary,
		BothPrimaries,
		BothSecondaries
	}
	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 MelonPreferences_Entry<PenHand> Hand { get; private set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		public static MelonPreferences_Entry<float> HoldToClearSeconds { get; private set; }

		public static MelonPreferences_Entry<PenToggleCombo> ToggleCombo { get; private set; }

		public static MelonPreferences_Entry<float> ToggleHoldSeconds { get; private set; }

		public static MelonPreferences_Entry<bool> DesktopHotkey { get; private set; }

		public static void Initialize(MelonBase melon, Actions actions)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Expected O, but got Unknown
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Expected O, but got Unknown
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Expected O, but got Unknown
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Expected O, but got Unknown
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Expected O, but got Unknown
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0301: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Expected O, but got Unknown
			//IL_04b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_04cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dc: Expected O, but got Unknown
			//IL_0519: Unknown result type (might be due to invalid IL or missing references)
			//IL_051e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0529: Unknown result type (might be due to invalid IL or missing references)
			//IL_0534: Unknown result type (might be due to invalid IL or missing references)
			//IL_0541: Expected O, but got Unknown
			Directory.CreateDirectory("UserData/RumbleParkPen");
			string filePath = Path.Combine("UserData/RumbleParkPen", "config.cfg");
			MelonPreferences_Category val = MelonPreferences.CreateCategory("RumbleParkPen_Pen", "Pen");
			val.SetFilePath(filePath);
			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(filePath);
			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(filePath);
			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(filePath);
			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);
			MelonPreferences_Category val5 = MelonPreferences.CreateCategory("RumbleParkPen_Controls", "Controls");
			val5.SetFilePath(filePath);
			HoldToClearSeconds = val5.CreateEntry<float>("HoldToClearSeconds", 1f, "Hold To Clear", "How long to hold the secondary button (B / Y) to clear your drawings", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.4f,
				Max = 3f,
				DecimalPlaces = 2
			}, (string)null);
			ToggleCombo = val5.CreateEntry<PenToggleCombo>("ToggleCombo", PenToggleCombo.FreeHandPrimaryAndSecondary, "Quick Toggle", "Controller combo that spawns or puts away the pen, without opening this menu", false, false, (ValueValidator)null, (string)null);
			ToggleHoldSeconds = val5.CreateEntry<float>("ToggleHoldSeconds", 0.35f, "Quick Toggle Hold", "How long the quick-toggle combo must be held before it fires", false, false, (ValueValidator)new SliderDescriptor
			{
				Min = 0.1f,
				Max = 2f,
				DecimalPlaces = 2
			}, (string)null);
			DesktopHotkey = val5.CreateEntry<bool>("DesktopHotkey", true, "F7 Hotkey", "Lets F7 on the keyboard spawn or put away the pen", false, false, (ValueValidator)null, (string)null);
			UI.RegisterMelon(melon, (MelonPreferences_Category[])(object)new MelonPreferences_Category[5] { val, val2, val3, val4, val5 });
			val.SaveToFile(false);
			val2.SaveToFile(false);
			val3.SaveToFile(false);
			val4.SaveToFile(false);
			val5.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 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
			{
				HashSet<int> hashSet = new HashSet<int>();
				foreach (Player item in (Il2CppArrayBase<Player>)(object)PhotonNetwork.PlayerList)
				{
					hashSet.Add(item.ActorNumber);
				}
				_peers.IntersectWith(hashSet);
			}
			catch (Exception)
			{
				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 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;

		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, Vector3 localPosition, Vector3 localEuler, float scale)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: 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_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: 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_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)anchor == (Object)null)
			{
				return false;
			}
			Destroy();
			_root = new GameObject("RumbleParkPen.Pen");
			_root.transform.SetParent(anchor, false);
			_root.transform.localPosition = localPosition;
			_root.transform.localRotation = Quaternion.Euler(localEuler);
			_root.transform.localScale = Vector3.one * scale;
			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 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;
		}

		public void ForgetSceneObjects()
		{
			_root = null;
			Tip = null;
			_taperRenderer = null;
			_nibRenderer = null;
		}
	}
	public sealed class PenSession
	{
		private readonly struct HandInput
		{
			public float Trigger { get; init; }

			public float Grip { get; init; }

			public float Primary { get; init; }

			public float Secondary { get; init; }
		}

		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 ButtonLatch _secondaryLatch = new ButtonLatch();

		private readonly ButtonLatch _primaryLatch = new ButtonLatch();

		private readonly ButtonLatch _toggleLatch = new ButtonLatch();

		private bool _penRequested;

		private Color _lastInkColor = Color.white;

		private bool _warnedNoAnchor;

		public bool IsPenOut => _rig.IsAlive;

		public int StrokeCount => _store.Count;

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

		private static Vector3 FitOffset => new Vector3(Settings.PenOffsetSide.Value, Settings.PenOffsetUp.Value, Settings.PenOffsetForward.Value);

		private static Vector3 FitRotation => new Vector3(Settings.PenPitch.Value, Settings.PenYaw.Value, 0f);

		public PenSession(PenMod mod, DrawingRepository? repository = null)
		{
			//IL_0061: 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)
			_repository = repository ?? new DrawingRepository();
			_network = new PenNetwork(mod);
			_store = new StrokeStore(_network.NextStrokeId);
		}

		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;
			_secondaryLatch.Reset();
			_primaryLatch.Reset();
			_toggleLatch.Reset();
			_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();
			_secondaryLatch.Reset();
			_primaryLatch.Reset();
		}

		public void Tick(float deltaTime)
		{
			//IL_00c0: 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)
			if (!IsDrawableScene(CurrentScene))
			{
				return;
			}
			PumpNetwork(deltaTime);
			SyncInkColorFromSettings();
			if (!TryReadControllers(out var left, out var right))
			{
				return;
			}
			bool num = Settings.Hand.Value == PenHand.Left;
			HandInput penHand = (num ? left : right);
			bool flag = IsToggleComboHeld(in penHand, num ? right : left);
			_toggleLatch.Update(flag, deltaTime, Settings.ToggleHoldSeconds.Value);
			if (_toggleLatch.HoldTriggered)
			{
				TogglePen();
			}
			else
			{
				if (!_penRequested || (!_rig.IsAlive && !TryBuildRig()))
				{
					return;
				}
				ApplyFit();
				Vector3? tipPosition = _rig.TipPosition;
				if (tipPosition.HasValue)
				{
					HandleDrawing(penHand.Trigger, tipPosition.Value);
					HandleErasing(penHand.Grip, tipPosition.Value);
					if (flag)
					{
						_primaryLatch.Reset();
						_secondaryLatch.Reset();
					}
					else
					{
						HandleColorCycle(penHand.Primary, deltaTime);
						HandleUndoAndClear(penHand.Secondary, deltaTime);
					}
				}
			}
		}

		private static bool IsToggleComboHeld(in HandInput penHand, in HandInput freeHand)
		{
			return Settings.ToggleCombo.Value switch
			{
				PenToggleCombo.FreeHandPrimaryAndSecondary => freeHand.Primary > 0.5f && freeHand.Secondary > 0.5f, 
				PenToggleCombo.FreeHandGripAndPrimary => freeHand.Grip > 0.5f && freeHand.Primary > 0.5f, 
				PenToggleCombo.BothPrimaries => penHand.Primary > 0.5f && freeHand.Primary > 0.5f, 
				PenToggleCombo.BothSecondaries => penHand.Secondary > 0.5f && freeHand.Secondary > 0.5f, 
				PenToggleCombo.Off => false, 
				_ => false, 
			};
		}

		private void HandleDrawing(float trigger, Vector3 tip)
		{
			//IL_0050: 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_005c: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (trigger > 0.5f)
			{
				if (!_store.IsDrawing)
				{
					Color value = Settings.InkColor.Value;
					_store.BeginStroke(value.r, value.g, value.b);
				}
				float width = WidthFor(trigger);
				PenPoint point = new PenPoint(tip.x, tip.y, tip.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(float grip, Vector3 tip)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (grip <= 0.5f)
			{
				return;
			}
			PenPoint probe = new PenPoint(tip.x, tip.y, tip.z, 0f);
			float value = Settings.EraserRadius.Value;
			List<StrokeData> list = _store.EraseNear(in probe, value);
			if (Settings.ShowOthersDrawings.Value)
			{
				foreach (StrokeData item in _remote.All.ToList())
				{
					if (item.IsWithin(in probe, value))
					{
						list.AddRange(_remote.Erase(new <>z__ReadOnlySingleElementList<string>(item.Id)));
					}
				}
			}
			if (list.Count != 0)
			{
				_renderer.RemoveAll(list);
				BroadcastErased(list);
			}
		}

		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 HandleColorCycle(float primary, float deltaTime)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			_primaryLatch.Update(primary > 0.5f, deltaTime, float.MaxValue);
			if (_primaryLatch.Tapped)
			{
				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 HandleUndoAndClear(float secondary, float deltaTime)
		{
			_secondaryLatch.Update(secondary > 0.5f, deltaTime, Settings.HoldToClearSeconds.Value);
			if (_secondaryLatch.HoldTriggered)
			{
				ClearDrawing();
			}
			else if (_secondaryLatch.Tapped)
			{
				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 void ApplyFit()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			Transform tip = _rig.Tip;
			if (!((Object)(object)tip == (Object)null))
			{
				Transform parent = tip.parent;
				if (!((Object)(object)parent == (Object)null))
				{
					parent.localPosition = FitOffset;
					parent.localRotation = Quaternion.Euler(FitRotation);
					parent.localScale = Vector3.one * Settings.PenScale.Value;
				}
			}
		}

		private bool TryBuildRig()
		{
			//IL_003d: 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_005f: 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_006b: 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)
			//IL_0077: 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, FitOffset, FitRotation, Settings.PenScale.Value))
			{
				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;
			}
		}

		private static bool TryReadControllers(out HandInput left, out HandInput right)
		{
			left = default(HandInput);
			right = default(HandInput);
			try
			{
				left = new HandInput
				{
					Trigger = LeftController.GetTrigger(),
					Grip = LeftController.GetGrip(),
					Primary = LeftController.GetPrimary(),
					Secondary = LeftController.GetSecondary()
				};
				right = new HandInput
				{
					Trigger = RightController.GetTrigger(),
					Grip = RightController.GetGrip(),
					Primary = RightController.GetPrimary(),
					Secondary = RightController.GetSecondary()
				};
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

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

		private const string RootName = "RumbleParkPen.Strokes";

		private const int RoundingVertices = 4;

		private const int MaxWidthCurveKeys = 12;

		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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			float num = stroke.MaxWidth;
			if (num <= 0f)
			{
				num = 0.01f;
			}
			view.Line.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;
			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;
		}

		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.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(out List<PenPoint> batch)
		{
			if (_pending.Count == 0 || (_pending.Count < MaxBatchSize && _waited < MaxDelaySeconds))
			{
				batch = new List<PenPoint>();
				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 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 (KeyValuePair<string, StrokeData> item in _byId)
			{
				if (StrokeId.IsOwnedBy(item.Key, actorNumber))
				{
					list.Add(item.Value);
				}
			}
			foreach (StrokeData item2 in list)
			{
				_byId.Remove(item2.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 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;
		}
	}
}
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", 0.15f, 0.9f, 0.95f),
			new PaletteColor("Blue", 0.2f, 0.4f, 1f),
			new PaletteColor("Purple", 0.6f, 0.25f, 0.95f),
			new PaletteColor("Magenta", 1f, 0.2f, 0.75f),
			new PaletteColor("Black", 0.02f, 0.02f, 0.02f)
		});

		private readonly IReadOnlyList<PaletteColor> _colors;

		public int Index { get; private set; }

		public PaletteColor Current => _colors[Index];

		public PenPalette(IReadOnlyList<PaletteColor>? colors = null)
		{
			_colors = ((colors != null && colors.Count > 0) ? colors : Default);
		}

		public PaletteColor Advance()
		{
			Index = (Index + 1) % _colors.Count;
			return Current;
		}

		public PaletteColor SelectNearest(float r, float g, float b)
		{
			int index = 0;
			float num = float.MaxValue;
			for (int i = 0; i < _colors.Count; i++)
			{
				float num2 = _colors[i].SquaredDistanceTo(r, g, b);
				if (!(num2 >= num))
				{
					num = num2;
					index = i;
				}
			}
			Index = index;
			return Current;
		}
	}
	public static class StrokeId
	{
		private const char OwnerSeparator = '.';

		public static string Create(int ownerActorNumber, int sequence)
		{
			return ownerActorNumber.ToString(CultureInfo.InvariantCulture) + "." + sequence.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryGetOwner(string? id, out int ownerActorNumber)
		{
			ownerActorNumber = 0;
			if (string.IsNullOrEmpty(id))
			{
				return false;
			}
			int num = id.IndexOf('.');
			if (num <= 0)
			{
				return false;
			}
			return int.TryParse(id.AsSpan(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out ownerActorNumber);
		}

		public static bool IsOwnedBy(string? id, int actorNumber)
		{
			if (TryGetOwner(id, out var ownerActorNumber))
			{
				return ownerActorNumber == actorNumber;
			}
			return false;
		}
	}
	public readonly struct PenPoint
	{
		public float X { get; }

		public float Y { get; }

		public float Z { get; }

		public float Width { get; }

		public PenPoint(float x, float y, float z, float width)
		{
			X = x;
			Y = y;
			Z = z;
			Width = width;
		}

		public float SquaredDistanceTo(in PenPoint other)
		{
			float num = X - other.X;
			float num2 = Y - other.Y;
			float num3 = Z - other.Z;
			return num * num + num2 * num2 + num3 * num3;
		}
	}
	public sealed class StrokeData
	{
		private const float WidthVariationEpsilon = 0.0005f;

		private float _minWidth = float.MaxValue;

		public List<PenPoint> Points { get; } = new List<PenPoint>();

		public float ColorR { get; set; }

		public float ColorG { get; set; }

		public float ColorB { get; set; }

		public string Id { get; set; } = string.Empty;

		public float MaxWidth { get; private set; }

		public float MinWidth
		{
			get
			{
				if (Points.Count != 0)
				{
					return _minWidth;
				}
				return 0f;
			}
		}

		public bool HasVariableWidth
		{
			get
			{
				if (Points.Count > 1)
				{
					return MaxWidth - _minWidth > 0.0005f;
				}
				return false;
			}
		}

		public bool IsRenderable => Points.Count >= 2;

		public StrokeData()
		{
		}

		public StrokeData(float r, float g, float b)
		{
			ColorR = r;
			ColorG = g;
			ColorB = b;
		}

		public void Add(in PenPoint point)
		{
			Points.Add(point);
			if (point.Width > MaxWidth)
			{
				MaxWidth = point.Width;
			}
			if (point.Width < _minWidth)
			{
				_minWidth = point.Width;
			}
		}

		public bool IsWithin(in PenPoint probe, float radius)
		{
			float num = radius * radius;
			foreach (PenPoint point in Points)
			{
				if (point.SquaredDistanceTo(in probe) <= num)
				{
					return true;
				}
			}
			return false;
		}
	}
	public sealed class StrokeStore
	{
		public const int MaxPointsPerStroke = 1500;

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

		private readonly Func<string>? _idFactory;

		public IReadOnlyList<StrokeData> Strokes => _strokes;

		public StrokeData? ActiveStroke { get; private set; }

		public bool IsDrawing => ActiveStroke != null;

		public int Count => _strokes.Count;

		public StrokeStore(Func<string>? idFactory = null)
		{
			_idFactory = idFactory;
			base..ctor();
		}

		public StrokeData BeginStroke(float r, float g, float b)
		{
			if (ActiveStroke != null)
			{
				EndStroke();
			}
			ActiveStroke = new StrokeData(r, g, b)
			{
				Id = (_idFactory?.Invoke() ?? string.Empty)
			};
			return ActiveStroke;
		}

		public StrokeData? RemoveById(string strokeId)
		{
			if (string.IsNullOrEmpty(strokeId))
			{
				return null;
			}
			for (int i = 0; i < _strokes.Count; i++)
			{
				if (string.Equals(_strokes[i].Id, strokeId, StringComparison.Ordinal))
				{
					StrokeData result = _strokes[i];
					_strokes.RemoveAt(i);
					return result;
				}
			}
			return null;
		}

		public bool TryAddPoint(in PenPoint point, float minSpacing)
		{
			if (ActiveStroke == null)
			{
				return false;
			}
			if (ActiveStroke.Points.Count > 0 && point.SquaredDistanceTo(ActiveStroke.Points[ActiveStroke.Points.Count - 1]) < minSpacing * minSpacing)
			{
				return false;
			}
			if (ActiveStroke.Points.Count >= 1500)
			{
				SplitActiveStroke();
			}
			ActiveStroke.Add(in point);
			return true;
		}

		private void SplitActiveStroke()
		{
			StrokeData activeStroke = ActiveStroke;
			PenPoint point = activeStroke.Points[activeStroke.Points.Count - 1];
			EndStroke();
			BeginStroke(activeStroke.ColorR, activeStroke.ColorG, activeStroke.ColorB);
			ActiveStroke.Add(in point);
		}

		public StrokeData? EndStroke()
		{
			StrokeData activeStroke = ActiveStroke;
			ActiveStroke = null;
			if (activeStroke == null || !activeStroke.IsRenderable)
			{
				return null;
			}
			_strokes.Add(activeStroke);
			return activeStroke;
		}

		public StrokeData? CancelStroke()
		{
			StrokeData? activeStroke = ActiveStroke;
			ActiveStroke = null;
			return activeStroke;
		}

		public StrokeData? Undo()
		{
			if (_strokes.Count == 0)
			{
				return null;
			}
			StrokeData result = _strokes[_strokes.Count - 1];
			_strokes.RemoveAt(_strokes.Count - 1);
			return result;
		}

		public List<StrokeData> EraseNear(in PenPoint probe, float radius)
		{
			List<StrokeData> list = new List<StrokeData>();
			for (int num = _strokes.Count - 1; num >= 0; num--)
			{
				if (_strokes[num].IsWithin(in probe, radius))
				{
					list.Add(_strokes[num]);
					_strokes.RemoveAt(num);
				}
			}
			return list;
		}

		public List<StrokeData> Clear()
		{
			List<StrokeData> list = new List<StrokeData>(_strokes);
			_strokes.Clear();
			if (ActiveStroke != null)
			{
				list.Add(ActiveStroke);
				ActiveStroke = null;
			}
			return list;
		}

		public List<StrokeData> Replace(IEnumerable<StrokeData> strokes)
		{
			List<StrokeData> result = Clear();
			_strokes.AddRange(strokes.Where((StrokeData s) => s.IsRenderable));
			return result;
		}
	}
}
namespace RumbleParkPen.Patches
{
	internal static class RmapiLogFilter
	{
		private static readonly string[] Muted = new string[2] { "unboxing data with length of", "is being sent to 'RumbleParkPen|" };

		public static bool Apply(Harmony harmony)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("RumbleModdingAPI.RumbleModdingAPI");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Log", new Type[2]
			{
				typeof(string),
				typeof(bool)
			}, (Type[])null));
			if (methodInfo == null)
			{
				return false;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(RmapiLogFilter), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			return true;
		}

		private static bool Prefix(string msg)
		{
			if (Settings.QuietNetworkLog == null || !Settings.QuietNetworkLog.Value || msg == null)
			{
				return true;
			}
			string[] muted = Muted;
			foreach (string value in muted)
			{
				if (msg.Contains(value, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}
	}
}