Decompiled source of MarkerMod v1.6.1

MarkerMod.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using Bifrost.Cooked;
using DooDesch;
using HarmonyLib;
using MarkerMod;
using MarkerMod.Config;
using MarkerMod.Managers;
using MarkerMod.Patches;
using MarkerMod.Services;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using Mimic;
using Mimic.Actors;
using MimicAPI.GameAPI;
using ReluProtocol;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "MarkerMod", "1.6.1", "DooDesch", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: MelonOptionalDependencies(new string[] { "MimicAPI" })]
[assembly: HarmonyDontPatchAll]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DooDesch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © DooDesch")]
[assembly: AssemblyFileVersion("1.6.1.0")]
[assembly: AssemblyInformationalVersion("1.6.1+a596ae365ed04fec43aaa56c0c4ff449470ddffc")]
[assembly: AssemblyProduct("MarkerMod")]
[assembly: AssemblyTitle("MarkerMod")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of Generative AI.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic/OpenAI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.6.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DooDesch
{
	internal sealed class HostSettings
	{
		[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyCreated")]
		private static class CreatedPatch
		{
			private static void Postfix(LobbyCreated_t callback)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Invalid comparison between Unknown and I4
				//IL_001e: 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)
				if ((int)callback.m_eResult != 1)
				{
					return;
				}
				foreach (HostSettings instance in Instances)
				{
					instance.Begin(new CSteamID(callback.m_ulSteamIDLobby), host: true);
				}
			}
		}

		[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyEntered")]
		private static class EnteredPatch
		{
			private static void Prefix(LobbyEnter_t callback)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: 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 (callback.m_EChatRoomEnterResponse != 1)
				{
					return;
				}
				CSteamID val = default(CSteamID);
				((CSteamID)(ref val))..ctor(callback.m_ulSteamIDLobby);
				foreach (HostSettings instance in Instances)
				{
					if (!instance.isHost || !(instance.lobby == val))
					{
						instance.Begin(val, host: false);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SteamMatchmaking), "LeaveLobby")]
		private static class LeavePatch
		{
			private static void Prefix(CSteamID steamIDLobby)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				foreach (HostSettings instance in Instances)
				{
					if (instance.lobby == steamIDLobby)
					{
						instance.Reset();
					}
				}
			}
		}

		[HarmonyPatch(typeof(MainMenu), "Start")]
		private static class MenuPatch
		{
			private static void Prefix()
			{
				foreach (HostSettings instance in Instances)
				{
					instance.Reset();
				}
			}
		}

		private static readonly List<HostSettings> Instances = new List<HostSettings>();

		private static Callback<LobbyDataUpdate_t> dataCallback;

		private static float nextPoll;

		private readonly string modId;

		private readonly string lobbyKey;

		private readonly int schemaVersion;

		private readonly List<ISetting> settings = new List<ISetting>();

		private readonly string modernKey;

		private string modernPayload;

		private string lastModernPayload;

		private long modernRevision;

		private bool legacyHost;

		private Dictionary<string, object> snapshot;

		private CSteamID lobby;

		private CSteamID owner;

		private bool isHost;

		private bool hostCreated;

		private bool authorityValid;

		private bool ownerChanged;

		private bool published;

		private string hostPayload;

		private string warningReason;

		private int warningRepeats;

		internal bool IsInSession => lobby != CSteamID.Nil;

		internal bool IsHost
		{
			get
			{
				if (isHost)
				{
					return authorityValid;
				}
				return false;
			}
		}

		internal bool UsingLegacyHost
		{
			get
			{
				if (!isHost && authorityValid && legacyHost)
				{
					return modernRevision == 0;
				}
				return false;
			}
		}

		internal event Action EffectiveValuesChanged;

		internal event Action LiveValuesChanged;

		internal HostSettings(string modId, int schemaVersion = 1)
		{
			if (string.IsNullOrWhiteSpace(modId))
			{
				throw new ArgumentException("A mod ID is required.", "modId");
			}
			if (schemaVersion < 1)
			{
				throw new ArgumentOutOfRangeException("schemaVersion");
			}
			this.modId = modId;
			this.schemaVersion = schemaVersion;
			lobbyKey = "doodesch." + modId.ToLowerInvariant() + ".settings.v" + schemaVersion;
			modernKey = lobbyKey + ".extended";
			if (Encoding.UTF8.GetByteCount(modernKey) >= 255)
			{
				throw new ArgumentException("The mod ID exceeds the Steam lobby key limit.", "modId");
			}
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobbyKey == lobbyKey)
				{
					throw new ArgumentException("The mod already has a settings snapshot.", "modId");
				}
			}
			Instances.Add(this);
		}

		internal HostSetting<T> Register<T>(MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate = null, bool live = false, bool legacy = true, Func<T> localValue = null)
		{
			//IL_005b: 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)
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			if (string.IsNullOrWhiteSpace(((MelonPreferences_Entry)entry).Identifier))
			{
				throw new ArgumentException("A setting identifier is required.", "entry");
			}
			if (((MelonPreferences_Entry)entry).Identifier.IndexOfAny(new char[3] { '\n', '\r', '\t' }) >= 0)
			{
				throw new ArgumentException("Setting identifiers cannot contain wire delimiters.", "entry");
			}
			if (lobby != CSteamID.Nil)
			{
				throw new InvalidOperationException("Register settings before entering a lobby.");
			}
			if (typeof(T) != typeof(bool) && typeof(T) != typeof(int) && typeof(T) != typeof(float) && typeof(T) != typeof(string))
			{
				throw new NotSupportedException("Host settings support bool, int, float and string values.");
			}
			if (legacy && typeof(T) == typeof(string))
			{
				throw new ArgumentException("String settings must use the extended snapshot.", "legacy");
			}
			foreach (ISetting setting in settings)
			{
				if (setting.Key == ((MelonPreferences_Entry)entry).Identifier)
				{
					throw new ArgumentException("Duplicate setting: " + ((MelonPreferences_Entry)entry).Identifier, "entry");
				}
			}
			HostSetting<T> hostSetting = new HostSetting<T>(this, entry, vanillaValue, validate, live, legacy, localValue);
			if (!hostSetting.IsValid(vanillaValue))
			{
				throw new ArgumentException("The vanilla value is invalid: " + ((MelonPreferences_Entry)entry).Identifier, "vanillaValue");
			}
			if (!hostSetting.IsValid(entry.DefaultValue))
			{
				throw new ArgumentException("The configured default is invalid: " + ((MelonPreferences_Entry)entry).Identifier, "entry");
			}
			settings.Add(hostSetting);
			return hostSetting;
		}

		internal T GetValue<T>(string key, T localValue, T vanillaValue, bool live)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (lobby == CSteamID.Nil)
			{
				return localValue;
			}
			if (live && !authorityValid)
			{
				return vanillaValue;
			}
			if (snapshot == null || !snapshot.TryGetValue(key, out var value))
			{
				return vanillaValue;
			}
			return (T)value;
		}

		internal static void Update()
		{
			//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)
			if (Time.realtimeSinceStartup < nextPoll)
			{
				return;
			}
			nextPoll = Time.realtimeSinceStartup + 1f;
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobby != CSteamID.Nil)
				{
					instance.Refresh();
				}
			}
		}

		private void Begin(CSteamID newLobby, bool host)
		{
			//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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: 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_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_0097: 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_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			if (settings.Count == 0 || (lobby == newLobby && (!host || hostCreated)))
			{
				return;
			}
			if (lobby != newLobby)
			{
				Reset();
			}
			lobby = newLobby;
			isHost = false;
			hostCreated = host;
			authorityValid = false;
			ownerChanged = false;
			snapshot = null;
			published = false;
			hostPayload = null;
			owner = CSteamID.Nil;
			try
			{
				owner = SteamMatchmaking.GetLobbyOwner(lobby);
				CSteamID steamID = SteamUser.GetSteamID();
				if (owner == CSteamID.Nil || (host ? (owner != steamID) : (owner == steamID)))
				{
					owner = CSteamID.Nil;
					Warn("Lobby owner does not identify the expected host; using vanilla settings.");
					Changed();
					return;
				}
				isHost = host;
				authorityValid = true;
				if (host)
				{
					CaptureHostSnapshot();
				}
				else
				{
					CaptureClientSnapshot();
				}
			}
			catch (Exception ex)
			{
				Warn("Initial lobby settings failed; vanilla settings are frozen for this session: " + ex.GetType().Name + ": " + ex.Message);
			}
			Refresh();
			Changed();
		}

		private static void EnsureCallback()
		{
			if (dataCallback == null)
			{
				dataCallback = Callback<LobbyDataUpdate_t>.Create((DispatchDelegate<LobbyDataUpdate_t>)OnLobbyDataUpdated);
			}
		}

		private static void OnLobbyDataUpdated(LobbyDataUpdate_t callback)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobby.m_SteamID == callback.m_ulSteamIDLobby && callback.m_ulSteamIDMember == callback.m_ulSteamIDLobby)
				{
					if (callback.m_bSuccess == 0)
					{
						instance.Warn("Steam lobby data update failed; frozen session settings are retained.");
					}
					else
					{
						instance.Refresh();
					}
				}
			}
		}

		private void CaptureHostSnapshot()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			StringBuilder stringBuilder = new StringBuilder(schemaVersion.ToString(CultureInfo.InvariantCulture));
			for (int i = 0; i < settings.Count; i++)
			{
				ISetting setting = settings[i];
				object localValue = setting.LocalValue;
				dictionary.Add(setting.Key, localValue);
				if (setting.Legacy)
				{
					stringBuilder.Append('\n').Append(setting.Key).Append('\t')
						.Append(Format(localValue));
				}
			}
			string s = stringBuilder.ToString();
			if (Encoding.UTF8.GetByteCount(s) + 1 > 8192)
			{
				throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
			}
			hostPayload = s;
			snapshot = dictionary;
			modernRevision = 1L;
			modernPayload = BuildModern(dictionary, modernRevision);
		}

		private unsafe void CaptureClientSnapshot()
		{
			//IL_0001: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			string lobbyData = SteamMatchmaking.GetLobbyData(lobby, modernKey);
			if (!string.IsNullOrEmpty(lobbyData))
			{
				snapshot = ParseModern(lobbyData, out modernRevision);
				lastModernPayload = lobbyData;
				MelonLogger.Msg("[" + modId + "] Host settings captured; live values follow the host, lobby values remain fixed.");
				return;
			}
			string lobbyData2 = SteamMatchmaking.GetLobbyData(lobby, lobbyKey);
			if (string.IsNullOrEmpty(lobbyData2))
			{
				Warn("Host snapshot is absent; vanilla settings are frozen for this session.");
				return;
			}
			snapshot = ParseSnapshot(lobbyData2);
			legacyHost = true;
			string[] obj = new string[5] { "[", modId, "] Host settings snapshot frozen for lobby ", null, null };
			CSteamID val = lobby;
			obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
			obj[4] = ".";
			MelonLogger.Msg(string.Concat(obj));
		}

		private void Refresh()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				EnsureCallback();
				CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(lobby);
				if (ownerChanged || owner == CSteamID.Nil || lobbyOwner != owner)
				{
					authorityValid = false;
					if (lobbyOwner != CSteamID.Nil && lobbyOwner != owner)
					{
						ownerChanged = true;
					}
					Warn("Lobby owner changed or is unavailable; authority revoked and frozen session settings retained.");
					return;
				}
				bool num = !authorityValid;
				authorityValid = true;
				flag = true;
				if (num)
				{
					MelonLogger.Msg("[" + modId + "] Pinned lobby authority recovered; frozen settings retained.");
				}
				if (isHost)
				{
					if (hostPayload != null)
					{
						if (published)
						{
							RefreshHostLive();
						}
						else
						{
							Publish();
						}
					}
				}
				else
				{
					RefreshClientLive();
				}
			}
			catch (Exception ex)
			{
				authorityValid = flag;
				Warn("Refresh lobby settings failed; frozen session settings retained: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private unsafe void Publish()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_0086: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload && !SteamMatchmaking.SetLobbyData(lobby, lobbyKey, hostPayload))
			{
				CSteamID val = lobby;
				Warn("SetLobbyData rejected the settings snapshot for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
			}
			else if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload)
			{
				CSteamID val = lobby;
				Warn("Settings snapshot readback differs for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
			}
			else if (PublishModern(modernPayload))
			{
				published = true;
				Recover();
				string[] obj = new string[5] { "[", modId, "] Settings snapshot confirmed by local lobby readback for ", null, null };
				CSteamID val = lobby;
				obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
				obj[4] = ".";
				MelonLogger.Msg(string.Concat(obj));
			}
		}

		private Dictionary<string, object> ParseSnapshot(string payload)
		{
			if (Encoding.UTF8.GetByteCount(payload) + 1 > 8192)
			{
				throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
			}
			string[] array = payload.Split('\n');
			if (array[0] != schemaVersion.ToString(CultureInfo.InvariantCulture))
			{
				throw new FormatException("Settings snapshot schema mismatch.");
			}
			if (array.Length != settings.FindAll((ISetting s) => s.Legacy).Count + 1)
			{
				throw new FormatException("Settings snapshot has missing or extra keys.");
			}
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			for (int num = 1; num < array.Length; num++)
			{
				string text = array[num];
				int num2 = text.IndexOf('\t');
				if (num2 <= 0 || num2 != text.LastIndexOf('\t') || text.IndexOf('\r') >= 0)
				{
					throw new FormatException("Settings snapshot has an invalid entry delimiter.");
				}
				string key = text.Substring(0, num2);
				if (dictionary.ContainsKey(key))
				{
					throw new FormatException("Settings snapshot has a duplicate or missing key.");
				}
				ISetting setting = settings.Find((ISetting candidate) => candidate.Key == key && candidate.Legacy);
				if (setting == null)
				{
					throw new FormatException("Settings snapshot has an unknown key: " + key);
				}
				dictionary.Add(key, setting.Parse(text.Substring(num2 + 1)));
			}
			return dictionary;
		}

		private string BuildModern(Dictionary<string, object> values, long revision)
		{
			StringBuilder stringBuilder = new StringBuilder("2\t" + revision.ToString(CultureInfo.InvariantCulture));
			foreach (ISetting setting in settings)
			{
				stringBuilder.Append('\n').Append(setting.Key).Append('\t')
					.Append(Format(values[setting.Key]));
			}
			string text = stringBuilder.ToString();
			if (Encoding.UTF8.GetByteCount(text) + 1 > 8192)
			{
				throw new FormatException("Extended settings exceed the Steam metadata limit.");
			}
			return text;
		}

		private Dictionary<string, object> ParseModern(string payload, out long revision)
		{
			if (Encoding.UTF8.GetByteCount(payload) + 1 > 8192)
			{
				throw new FormatException("Extended settings exceed the Steam metadata limit.");
			}
			string[] array = payload.Split('\n');
			string[] array2 = array[0].Split('\t');
			if (array2.Length != 2 || array2[0] != "2" || !long.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out revision) || revision < 1)
			{
				throw new FormatException("Invalid extended settings schema or revision.");
			}
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 1; i < array.Length; i++)
			{
				int num = array[i].IndexOf('\t');
				if (num <= 0 || num != array[i].LastIndexOf('\t') || array[i].IndexOf('\r') >= 0)
				{
					throw new FormatException("Invalid extended settings delimiter.");
				}
				string key = array[i].Substring(0, num);
				if (!hashSet.Add(key))
				{
					throw new FormatException("Duplicate extended setting: " + key);
				}
				ISetting setting = settings.Find((ISetting s) => s.Key == key);
				if (setting != null)
				{
					dictionary.Add(key, setting.Parse(array[i].Substring(num + 1)));
				}
			}
			return dictionary;
		}

		private bool PublishModern(string payload)
		{
			//IL_0001: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			if (SteamMatchmaking.GetLobbyData(lobby, modernKey) != payload && !SteamMatchmaking.SetLobbyData(lobby, modernKey, payload))
			{
				Warn("SetLobbyData rejected the extended settings; last confirmed values retained.");
				return false;
			}
			if (SteamMatchmaking.GetLobbyData(lobby, modernKey) != payload)
			{
				Warn("Extended settings readback differs; last confirmed values retained.");
				return false;
			}
			return true;
		}

		private void RefreshHostLive()
		{
			if (snapshot == null)
			{
				return;
			}
			Dictionary<string, object> dictionary = new Dictionary<string, object>(snapshot, StringComparer.Ordinal);
			bool flag = false;
			foreach (ISetting setting in settings)
			{
				if (setting.Live)
				{
					object localValue = setting.LocalValue;
					if (!dictionary.TryGetValue(setting.Key, out var value) || !object.Equals(value, localValue))
					{
						dictionary[setting.Key] = localValue;
						flag = true;
					}
				}
			}
			if (!flag)
			{
				if (PublishModern(modernPayload))
				{
					Recover();
				}
				return;
			}
			long revision = checked(modernRevision + 1);
			string payload = BuildModern(dictionary, revision);
			if (PublishModern(payload))
			{
				snapshot = dictionary;
				modernRevision = revision;
				modernPayload = payload;
				Recover();
				Changed(this.LiveValuesChanged);
			}
		}

		private void RefreshClientLive()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			string lobbyData = SteamMatchmaking.GetLobbyData(lobby, modernKey);
			if (string.IsNullOrEmpty(lobbyData) || lobbyData == lastModernPayload)
			{
				return;
			}
			long revision;
			Dictionary<string, object> dictionary = ParseModern(lobbyData, out revision);
			if (revision <= modernRevision)
			{
				Warn("Extended settings revision did not advance; last confirmed values retained.");
				return;
			}
			Dictionary<string, object> dictionary2 = ((snapshot == null) ? new Dictionary<string, object>(StringComparer.Ordinal) : new Dictionary<string, object>(snapshot, StringComparer.Ordinal));
			bool flag = false;
			foreach (ISetting setting in settings)
			{
				if (setting.Live && dictionary.TryGetValue(setting.Key, out var value) && (!dictionary2.TryGetValue(setting.Key, out var value2) || !object.Equals(value2, value)))
				{
					dictionary2[setting.Key] = value;
					flag = true;
				}
			}
			snapshot = dictionary2;
			modernRevision = revision;
			lastModernPayload = lobbyData;
			Recover();
			if (flag)
			{
				Changed(this.LiveValuesChanged);
			}
		}

		private static string Format(object value)
		{
			if (value is string s)
			{
				return "s:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s));
			}
			if (value is float num)
			{
				return num.ToString("R", CultureInfo.InvariantCulture);
			}
			if (value is bool)
			{
				if (!(bool)value)
				{
					return "false";
				}
				return "true";
			}
			return ((int)value).ToString(CultureInfo.InvariantCulture);
		}

		private void Warn(string reason)
		{
			if (warningReason == reason)
			{
				warningRepeats++;
				return;
			}
			if (warningRepeats > 0)
			{
				MelonLogger.Warning("[" + modId + "] Previous settings warning repeated " + warningRepeats + " times.");
			}
			warningReason = reason;
			warningRepeats = 0;
			MelonLogger.Warning("[" + modId + "] " + reason);
		}

		private void Recover()
		{
			if (warningReason != null)
			{
				MelonLogger.Msg("[" + modId + "] Lobby settings recovered after " + (warningRepeats + 1) + " warning occurrences.");
				warningReason = null;
				warningRepeats = 0;
			}
		}

		private void Changed()
		{
			Changed(this.EffectiveValuesChanged);
		}

		private void Changed(Action handlers)
		{
			if (handlers == null)
			{
				return;
			}
			Delegate[] invocationList = handlers.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action action = (Action)invocationList[i];
				try
				{
					action();
				}
				catch (Exception ex)
				{
					MelonLogger.Error("[" + modId + "] Apply effective settings failed.", ex);
				}
			}
		}

		private void Reset()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			bool num = lobby != CSteamID.Nil;
			lobby = CSteamID.Nil;
			owner = CSteamID.Nil;
			isHost = false;
			hostCreated = false;
			authorityValid = false;
			ownerChanged = false;
			published = false;
			snapshot = null;
			hostPayload = null;
			modernPayload = null;
			lastModernPayload = null;
			modernRevision = 0L;
			legacyHost = false;
			warningReason = null;
			warningRepeats = 0;
			if (num)
			{
				MelonLogger.Msg("[" + modId + "] Session settings cleared; local settings restored.");
				Changed();
			}
		}
	}
	internal interface ISetting
	{
		string Key { get; }

		object LocalValue { get; }

		bool Live { get; }

		bool Legacy { get; }

		bool IsValid(object value);

		object Parse(string value);
	}
	internal sealed class HostSetting<T> : ISetting
	{
		private readonly HostSettings owner;

		private readonly MelonPreferences_Entry<T> entry;

		private readonly T vanillaValue;

		private readonly Func<T, bool> validate;

		private readonly Func<T> localValue;

		private bool invalidLocal;

		public bool Live { get; }

		public bool Legacy { get; }

		internal T Value => owner.GetValue(Key, (T)LocalValue, vanillaValue, Live);

		public string Key => ((MelonPreferences_Entry)entry).Identifier;

		public object LocalValue
		{
			get
			{
				T val = ((localValue == null) ? entry.Value : localValue());
				if (IsValid(val))
				{
					if (invalidLocal)
					{
						MelonLogger.Msg("Local setting recovered: " + Key);
					}
					invalidLocal = false;
					return val;
				}
				if (!invalidLocal)
				{
					MelonLogger.Warning("Invalid local setting " + Key + "; using its configured default without overwriting the file.");
				}
				invalidLocal = true;
				return entry.DefaultValue;
			}
		}

		internal HostSetting(HostSettings owner, MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate, bool live, bool legacy, Func<T> localValue)
		{
			this.owner = owner;
			this.entry = entry;
			this.vanillaValue = vanillaValue;
			this.validate = validate;
			this.localValue = localValue;
			Live = live;
			Legacy = legacy;
		}

		public bool IsValid(object value)
		{
			if (!(value is T arg))
			{
				return false;
			}
			if (value is float f && (float.IsNaN(f) || float.IsInfinity(f)))
			{
				return false;
			}
			if (value is string text && (Encoding.UTF8.GetByteCount(text) > 1024 || text.IndexOf('\0') >= 0))
			{
				return false;
			}
			if (validate != null)
			{
				return validate(arg);
			}
			return true;
		}

		public object Parse(string value)
		{
			object obj;
			int result2;
			float result3;
			if (typeof(T) == typeof(bool) && bool.TryParse(value, out var result))
			{
				obj = result;
			}
			else if (typeof(T) == typeof(int) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
			{
				obj = result2;
			}
			else if (typeof(T) == typeof(float) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result3))
			{
				obj = result3;
			}
			else
			{
				if (!(typeof(T) == typeof(string)) || !value.StartsWith("s:", StringComparison.Ordinal))
				{
					throw new FormatException("Invalid settings value: " + Key);
				}
				obj = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(Convert.FromBase64String(value.Substring(2)));
			}
			if (!IsValid(obj))
			{
				throw new FormatException("Settings value is out of range: " + Key);
			}
			return obj;
		}
	}
	internal static class ModVersion
	{
		internal const string Current = "1.6.1";
	}
}
namespace MarkerMod
{
	public class Core : MelonMod
	{
		public override void OnInitializeMelon()
		{
			MarkerPreferences.Initialize();
			((MelonBase)this).HarmonyInstance.PatchAll();
			((MelonBase)this).LoggerInstance.Msg($"MarkerMod initialized. Lifespan={MarkerPreferences.PermanentLifetimeSeconds:F0}s, Footprints={MarkerPreferences.KeepFootprints}, Puddles={MarkerPreferences.KeepPuddles}, InfinitePaintballs={MarkerPreferences.InfinitePaintballs}");
		}

		public override void OnUpdate()
		{
			HostSettings.Update();
		}
	}
}
namespace MarkerMod.Services
{
	internal static class MaterialColorService
	{
		private sealed class RendererColors
		{
			internal readonly Dictionary<int, MaterialPropertyBlock> Original = new Dictionary<int, MaterialPropertyBlock>();

			internal readonly MaterialPropertyBlock Working = new MaterialPropertyBlock();
		}

		private static readonly ConditionalWeakTable<Renderer, RendererColors> Colors = new ConditionalWeakTable<Renderer, RendererColors>();

		internal static void ApplyColorToRenderer(Renderer renderer, Color color, bool useDefaultMaterial)
		{
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Expected O, but got Unknown
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)renderer == (Object)null)
			{
				return;
			}
			Material[] sharedMaterials = renderer.sharedMaterials;
			if (useDefaultMaterial)
			{
				if (!Colors.TryGetValue(renderer, out var value))
				{
					return;
				}
				foreach (KeyValuePair<int, MaterialPropertyBlock> item in value.Original)
				{
					if (item.Key < sharedMaterials.Length)
					{
						renderer.SetPropertyBlock(item.Value.isEmpty ? null : item.Value, item.Key);
					}
				}
				Colors.Remove(renderer);
				return;
			}
			RendererColors value2 = Colors.GetValue(renderer, (Renderer _) => new RendererColors());
			for (int num = 0; num < sharedMaterials.Length; num++)
			{
				Material val = sharedMaterials[num];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string text = (val.HasProperty("_Color") ? "_Color" : (val.HasProperty("_BaseColor") ? "_BaseColor" : (val.HasProperty("_TintColor") ? "_TintColor" : null)));
				if (text != null)
				{
					if (!value2.Original.ContainsKey(num))
					{
						MaterialPropertyBlock val2 = new MaterialPropertyBlock();
						renderer.GetPropertyBlock(val2, num);
						value2.Original.Add(num, val2);
					}
					renderer.GetPropertyBlock(value2.Working, num);
					if (value2.Working.isEmpty)
					{
						renderer.GetPropertyBlock(value2.Working);
					}
					value2.Working.SetColor(text, (Color)(IsBlackColor(color) ? new Color(0.15f, 0.15f, 0.15f, 1f) : color));
					if (val.HasProperty("_MainTex"))
					{
						value2.Working.SetTexture("_MainTex", (Texture)(object)Texture2D.whiteTexture);
					}
					if (val.HasProperty("_BaseMap"))
					{
						value2.Working.SetTexture("_BaseMap", (Texture)(object)Texture2D.whiteTexture);
					}
					renderer.SetPropertyBlock(value2.Working, num);
				}
			}
		}

		internal static void ApplyColorToTransform(Transform transform, Color color, bool useDefaultMaterial)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)transform == (Object)null))
			{
				Renderer[] componentsInChildren = ((Component)transform).GetComponentsInChildren<Renderer>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					ApplyColorToRenderer(componentsInChildren[i], color, useDefaultMaterial);
				}
			}
		}

		private static bool IsBlackColor(Color color)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (color.r < 0.1f && color.g < 0.1f)
			{
				return color.b < 0.1f;
			}
			return false;
		}
	}
}
namespace MarkerMod.Patches
{
	[HarmonyPatch(typeof(UIPrefab_Inventory), "UpdateSlot")]
	internal static class InfinitePaintballDisplayPatch
	{
		private static readonly FieldInfo InventoryUi = AccessTools.Field(typeof(GameMainBase), "inventoryui") ?? throw new MissingFieldException("GameMainBase", "inventoryui");

		internal static void Refresh()
		{
			GameMainBase main = Hub.Main;
			ProtoActor val = ((main != null) ? main.GetMyAvatar() : null);
			if ((Object)(object)val != (Object)null && InventoryUi.GetValue(main) != null)
			{
				main.UpdateInventoryUI(val);
			}
		}

		private static void Postfix(List<InventoryItem> inventoryItems, List<int> visibleSlotIndices, List<Slot> ___inventorySlots)
		{
			if (!MarkerPreferences.InfinitePaintballs || visibleSlotIndices == null)
			{
				return;
			}
			foreach (int visibleSlotIndex in visibleSlotIndices)
			{
				if (visibleSlotIndex >= 0 && visibleSlotIndex < inventoryItems.Count && visibleSlotIndex < ___inventorySlots.Count)
				{
					InventoryItem obj = inventoryItems[visibleSlotIndex];
					TMP_Text val = ___inventorySlots[visibleSlotIndex]?.stackCount;
					if (obj != null && obj.ItemMasterID == 2030 && (Object)(object)val != (Object)null)
					{
						val.text = "∞";
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(ProtoActor), "Update")]
	internal static class PaintBallColorUpdatePatch
	{
		private static long lastSelectedItemID = -1L;

		private static bool wasRightMouseButtonPressedLastFrame = false;

		[HarmonyPostfix]
		public static void Postfix(ProtoActor __instance)
		{
			try
			{
				if (!__instance.AmIAvatar())
				{
					return;
				}
				if (!MarkerPreferences.EnablePaintballColorChange)
				{
					if (lastSelectedItemID != -1)
					{
						PaintBallColorManager.ResetColorSelection();
						object localInventory = PlayerAPI.GetLocalInventory();
						if (localInventory != null)
						{
							PaintBallColorManager.ApplyColorToPaintball(ReflectionHelper.GetPropertyValue(localInventory, "SelectedItem"));
						}
					}
					lastSelectedItemID = -1L;
					wasRightMouseButtonPressedLastFrame = false;
					return;
				}
				if ((Object)(object)PlayerAPI.GetLocalPlayer() == (Object)null)
				{
					lastSelectedItemID = -1L;
					return;
				}
				object localInventory2 = PlayerAPI.GetLocalInventory();
				if (localInventory2 == null)
				{
					lastSelectedItemID = -1L;
					return;
				}
				object propertyValue = ReflectionHelper.GetPropertyValue(localInventory2, "SelectedItem");
				if (propertyValue == null)
				{
					if (lastSelectedItemID != -1)
					{
						MelonLogger.Msg("[PaintBallColor] No item selected");
						lastSelectedItemID = -1L;
					}
					return;
				}
				object propertyValue2 = ReflectionHelper.GetPropertyValue(propertyValue, "ItemID");
				object propertyValue3 = ReflectionHelper.GetPropertyValue(propertyValue, "ItemMasterID");
				if (propertyValue2 == null || propertyValue3 == null)
				{
					return;
				}
				long num = Convert.ToInt64(propertyValue2);
				bool flag = PaintBallColorManager.IsPaintball(Convert.ToInt32(propertyValue3));
				if (flag && num != lastSelectedItemID)
				{
					PaintBallColorManager.ResetColorSelection();
					PaintBallColorManager.ApplyColorToPaintball(propertyValue);
					lastSelectedItemID = num;
				}
				else if (!flag && lastSelectedItemID != -1)
				{
					lastSelectedItemID = -1L;
					PaintBallColorManager.ResetColorSelection();
				}
				if (flag)
				{
					Mouse current = Mouse.current;
					if (current != null)
					{
						bool flag2 = MarkerPreferences.ColorMouseButton switch
						{
							0 => current.leftButton.isPressed, 
							1 => current.rightButton.isPressed, 
							2 => current.middleButton.isPressed, 
							_ => false, 
						};
						bool num2 = flag2 && !wasRightMouseButtonPressedLastFrame;
						wasRightMouseButtonPressedLastFrame = flag2;
						if (num2)
						{
							PaintBallColorManager.CycleColor();
							PaintBallColorManager.ApplyColorToPaintball(propertyValue);
							string currentColorName = PaintBallColorManager.GetCurrentColorName();
							MelonLogger.Msg("[PaintBallColor] Farbe geändert zu: " + currentColorName);
						}
					}
				}
				else
				{
					wasRightMouseButtonPressedLastFrame = false;
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[PaintBallColor] Error: " + ex.Message + "\n" + ex.StackTrace);
			}
		}
	}
	[HarmonyPatch]
	internal static class PaintFieldLifetimePatch
	{
		private static readonly FieldInfo Duration = AccessTools.Field(typeof(FieldSkillMemberInfo), "Duration");

		private static readonly MethodInfo Resolve = AccessTools.Method(typeof(PaintFieldLifetimePatch), "ResolveDuration", (Type[])null, (Type[])null);

		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.DeclaredMethod(typeof(FieldSkillObject), "Update", (Type[])null, (Type[])null);
			yield return AccessTools.DeclaredMethod(typeof(FieldSkillObject), "GetInfo", (Type[])null, (Type[])null);
		}

		internal static long ResolveDuration(FieldSkillMemberInfo member, FieldSkillObject field)
		{
			if (!MarkerPreferences.KeepPuddles || member.Index != 1 || !PaintPersistenceManager.IsPaintPuddle(((VActor)field).MasterID) || member.Duration <= 0)
			{
				return member.Duration;
			}
			return Math.Max(member.Duration, PaintPersistenceManager.PermanentLifetimeMilliseconds);
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
		{
			int matches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, Duration))
				{
					yield return CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_0, (object)null), instruction), instruction);
					instruction.opcode = OpCodes.Call;
					instruction.operand = Resolve;
					matches++;
				}
				yield return instruction;
			}
			if (matches == 0)
			{
				throw new InvalidOperationException("Paint lifetime patch failed: " + __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name + " has no field-duration reads.");
			}
		}
	}
	[HarmonyPatch(typeof(FieldSkillActor), "Spawn")]
	internal static class FieldSkillActorSpawnPatch
	{
		private static readonly MethodInfo Destroy2Arg = AccessTools.Method(typeof(Object), "Destroy", new Type[2]
		{
			typeof(Object),
			typeof(float)
		}, (Type[])null);

		private static readonly MethodInfo DestroyOverride = AccessTools.Method(typeof(PaintPersistenceManager), "DestroyWithOverride", (Type[])null, (Type[])null);

		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, Destroy2Arg))
				{
					yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null);
					instruction.operand = DestroyOverride;
				}
				yield return instruction;
			}
		}

		[HarmonyPostfix]
		public static void Postfix(FieldSkillActor __instance, FieldSkillObjectInfo fieldSkillObjectInfo, ref float __result)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			PaintPersistenceManager.TrackDecal(fieldSkillObjectInfo);
			__result = PaintPersistenceManager.OverrideLifetime(fieldSkillObjectInfo, __result);
			if (!MarkerPreferences.EnablePaintballColorChange || fieldSkillObjectInfo == null)
			{
				return;
			}
			try
			{
				if (PaintBallColorManager.IsPaintball(fieldSkillObjectInfo.fieldSkillMasterID) && PaintBallColorManager.HasColorBeenSelected() && (Object)(object)__instance != (Object)null)
				{
					((MonoBehaviour)__instance).StartCoroutine(ApplyColorToProjectileCoroutine(__instance, PaintBallColorManager.GetCurrentColor()));
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[PaintballProjectileColor] Error: " + ex.Message + "\n" + ex.StackTrace);
			}
		}

		private static IEnumerator ApplyColorToProjectileCoroutine(FieldSkillActor actor, Color color)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			yield return null;
			yield return null;
			if ((Object)(object)actor != (Object)null && (Object)(object)((Component)actor).transform != (Object)null)
			{
				MaterialColorService.ApplyColorToTransform(((Component)actor).transform, color, useDefaultMaterial: false);
			}
		}
	}
	[HarmonyPatch(typeof(GameMainBase), "SpawnFieldSkill")]
	internal static class GameMainBaseSpawnFieldSkillPatch
	{
		private static readonly MethodInfo Destroy2Arg = AccessTools.Method(typeof(Object), "Destroy", new Type[2]
		{
			typeof(Object),
			typeof(float)
		}, (Type[])null);

		private static readonly MethodInfo DestroyOverride = AccessTools.Method(typeof(PaintPersistenceManager), "DestroyWithOverride", (Type[])null, (Type[])null);

		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, Destroy2Arg))
				{
					yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null);
					instruction.operand = DestroyOverride;
				}
				yield return instruction;
			}
		}
	}
	[HarmonyPatch(typeof(WorldDecal), "Activate")]
	internal static class WorldDecalActivatePatch
	{
		[HarmonyPrefix]
		public static void Prefix(WorldDecal __instance)
		{
			//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)
			if (MarkerPreferences.KeepFootprints && PaintPersistenceManager.IsPermanentDecal(__instance.decalId))
			{
				__instance.lifetimeMSec = PaintPersistenceManager.PermanentLifetimeMilliseconds;
				__instance.fadeoutMSec = 0L;
			}
			if (MarkerPreferences.EnablePaintballColorChange && PaintBallColorManager.HasColorBeenSelected() && PaintBallColorManager.GetCurrentColorIndex() != -1 && PaintPersistenceManager.IsPaintDecal(__instance.decalId))
			{
				__instance.decalColor = PaintBallColorManager.GetCurrentColor();
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class DecalDataCreatePatch
	{
		[HarmonyPrefix]
		public static void Prefix(string pathWithSocket, ref long lifetimeMSec, ref long fadeoutMSec, Transform spawnBase)
		{
			bool flag = !string.IsNullOrEmpty(pathWithSocket) && pathWithSocket.IndexOf("paint", StringComparison.OrdinalIgnoreCase) >= 0;
			bool flag2 = (Object)(object)spawnBase != (Object)null;
			bool flag3 = false;
			if (MarkerPreferences.KeepFootprints && flag2 && flag)
			{
				flag3 = true;
			}
			if (MarkerPreferences.KeepPuddles && !flag2 && flag)
			{
				flag3 = true;
			}
			if (flag3)
			{
				lifetimeMSec = PaintPersistenceManager.PermanentLifetimeMilliseconds;
				fadeoutMSec = 0L;
			}
		}

		[HarmonyPostfix]
		public static void Postfix(DecalData __instance, string pathWithSocket, Transform spawnBase)
		{
			bool flag = !string.IsNullOrEmpty(pathWithSocket) && pathWithSocket.IndexOf("paint", StringComparison.OrdinalIgnoreCase) >= 0;
			bool flag2 = (Object)(object)spawnBase != (Object)null;
			if (MarkerPreferences.KeepFootprints && flag2 && flag && __instance != null)
			{
				PaintPersistenceManager.TrackDecalIfNeeded(__instance);
			}
		}
	}
	[HarmonyPatch]
	internal static class PaintballDurabilityCallPatch
	{
		private static readonly MethodInfo Setter = AccessTools.Method(typeof(EquipmentItemElement), "SetDurability", (Type[])null, (Type[])null);

		private static readonly MethodInfo Apply = AccessTools.Method(typeof(PaintballDurabilityCallPatch), "ApplyDurability", (Type[])null, (Type[])null);

		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.DeclaredMethod(typeof(InventoryController), "OnUseSkill", (Type[])null, (Type[])null);
			yield return AccessTools.DeclaredMethod(typeof(InventoryController), "OnUseSkill_SpawnProjectile", (Type[])null, (Type[])null);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ApplyDurability(EquipmentItemElement item, int durability)
		{
			if (!MarkerPreferences.InfinitePaintballs || ((ItemElement)item).ItemMasterID != 2030 || durability >= item.RemainDurability)
			{
				item.SetDurability(durability);
			}
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
		{
			int matches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, Setter))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = Apply;
					matches++;
				}
				yield return instruction;
			}
			if (matches == 0)
			{
				throw new InvalidOperationException("Paintball durability patch failed: " + __originalMethod.Name + " has no durability writes.");
			}
		}
	}
	[HarmonyPatch(typeof(InventoryController), "OnUseSkill_SpawnProjectile", new Type[]
	{
		typeof(int),
		typeof(VProjectileObject)
	})]
	internal static class OnUseSkillSpawnProjectilePatch
	{
		[HarmonyPrefix]
		public static bool Prefix(VProjectileObject projectile)
		{
			if (!MarkerPreferences.InfinitePaintballs || projectile == null)
			{
				return true;
			}
			try
			{
				object fieldValue = ReflectionHelper.GetFieldValue((object)projectile, "_projectileInfo");
				if (fieldValue == null)
				{
					return true;
				}
				if (PaintBallColorManager.IsPaintball(ReflectionHelper.GetFieldValue<int>(fieldValue, "SpawnItemMasterIDonCollision")))
				{
					return false;
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[InfinitePaintballs] OnUseSkill_SpawnProjectile patch error: " + ex.Message);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(EquipmentItemElement), "SetDurability")]
	internal static class EquipmentItemElementSetDurabilityPatch
	{
		[HarmonyPrefix]
		public static bool Prefix(EquipmentItemElement __instance, int durability)
		{
			if (!MarkerPreferences.InfinitePaintballs)
			{
				return true;
			}
			try
			{
				if (PaintBallColorManager.IsPaintball(ReflectionHelper.GetFieldValue<int>((object)__instance, "ItemMasterID")))
				{
					int propertyValue = ReflectionHelper.GetPropertyValue<int>((object)__instance, "RemainDurability");
					if (durability < propertyValue)
					{
						return false;
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[InfinitePaintballs] Error in SetDurability patch: " + ex.Message);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(VProjectileObject), "HandlePhysicsProj")]
	internal static class VProjectileSelfHitGracePatch
	{
		private const float HugeGraceDistance = 1E+09f;

		public static float ResolveGraceDistance(float originalDistance)
		{
			if (!MarkerPreferences.PreventPaintballSelfDamage)
			{
				return originalDistance;
			}
			return 1E+09f;
		}

		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			foreach (CodeInstruction instruction in instructions)
			{
				yield return instruction;
				if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo { Name: "SelfHitGraceDistance" })
				{
					yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(VProjectileSelfHitGracePatch), "ResolveGraceDistance", (Type[])null, (Type[])null));
				}
			}
		}
	}
	[HarmonyPatch(typeof(VProjectileObject), "OnHit")]
	internal static class VProjectileSelfHitPatch
	{
		[HarmonyPrefix]
		public static void Prefix(VProjectileObject __instance, List<int> hitTargetActorIDs)
		{
			if (!MarkerPreferences.PreventPaintballSelfDamage || hitTargetActorIDs == null || hitTargetActorIDs.Count == 0)
			{
				return;
			}
			try
			{
				object fieldValue = ReflectionHelper.GetFieldValue((object)__instance, "_parentActor");
				if (fieldValue == null)
				{
					return;
				}
				int parentActorID = ReflectionHelper.GetPropertyValue<int>(fieldValue, "ObjectID");
				if (parentActorID != 0)
				{
					hitTargetActorIDs.RemoveAll((int id) => id == parentActorID);
					_ = 0;
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[PaintballSelfDamage] Projectile OnHit patch error: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(FieldSkillObject), "OnHit")]
	internal static class FieldSkillSelfHitPatch
	{
		[HarmonyPrefix]
		public static void Prefix(FieldSkillObject __instance, FieldSkillHitInputData hitInfo)
		{
			if (!MarkerPreferences.PreventPaintballSelfDamage || !PaintPersistenceManager.IsPaintFieldSkill(((VActor)__instance).MasterID) || ((hitInfo != null) ? hitInfo.SkillSequenceInfo : null) == null || hitInfo.SkillSequenceInfo.MutableValue == 0L || ((hitInfo != null) ? hitInfo.Targets : null) == null || hitInfo.Targets.Count == 0)
			{
				return;
			}
			try
			{
				object fieldValue = ReflectionHelper.GetFieldValue((object)__instance, "_parentSkillContext");
				object obj = ((fieldValue != null) ? ReflectionHelper.GetPropertyValue(fieldValue, "ContextInfo") : null);
				object obj2 = ((obj != null) ? ReflectionHelper.GetFieldValue(obj, "Creature") : null);
				int casterID = ((obj2 != null) ? ReflectionHelper.GetPropertyValue<int>(obj2, "ObjectID") : 0);
				if (casterID != 0)
				{
					hitInfo.Targets.RemoveAll((TargetInfo t) => t.targetObjectID == casterID);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[PaintballSelfDamage] FieldSkill OnHit patch error: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(VProjectileObject), "HandlePhysicsProj")]
	internal static class VProjectileNavmeshPatch
	{
		public static float ResolveSnapRadius(float originalRadius)
		{
			if (!MarkerPreferences.AllowLongPaintballThrows)
			{
				return originalRadius;
			}
			return MarkerPreferences.LongThrowSnapRadius;
		}

		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> codes = new List<CodeInstruction>(instructions);
			for (int i = 0; i < codes.Count; i++)
			{
				CodeInstruction val = codes[i];
				if (val.opcode != OpCodes.Ldc_R4 || i + 1 >= codes.Count)
				{
					yield return val;
					continue;
				}
				CodeInstruction next = codes[i + 1];
				bool isCall = next.opcode == OpCodes.Call || next.opcode == OpCodes.Callvirt;
				yield return val;
				if (isCall && next.operand is MethodInfo { Name: "GetNearestPointOnNavMesh" })
				{
					yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(VProjectileNavmeshPatch), "ResolveSnapRadius", (Type[])null, (Type[])null));
				}
			}
		}
	}
}
namespace MarkerMod.Managers
{
	internal static class PaintBallColorManager
	{
		private static readonly List<Color> ColorPalette = new List<Color>
		{
			new Color(1f, 0f, 0f, 1f),
			new Color(1f, 1f, 0f, 1f),
			new Color(0f, 1f, 0f, 1f),
			new Color(0f, 0f, 1f, 1f),
			new Color(1f, 0.5f, 0f, 1f),
			new Color(0.5f, 0f, 1f, 1f),
			new Color(1f, 1f, 1f, 1f),
			new Color(0f, 0f, 0f, 1f)
		};

		private static int currentColorIndex = -1;

		private static string cachedPaletteText;

		private static List<Color> cachedPalette;

		private static bool hasColorBeenSelected = false;

		private static readonly HashSet<int> PaintballMasterIDs = new HashSet<int> { 2030, 70010, 70011, 70012, 70013, 70014, 70015, 70016, 70017, 70018 };

		internal static void CycleColor()
		{
			List<Color> palette = GetPalette();
			hasColorBeenSelected = true;
			if (currentColorIndex == -1)
			{
				currentColorIndex = 0;
			}
			else if (currentColorIndex == palette.Count - 1)
			{
				currentColorIndex = -1;
			}
			else
			{
				currentColorIndex++;
			}
		}

		internal static void ResetColorSelection()
		{
			hasColorBeenSelected = false;
			currentColorIndex = -1;
		}

		internal static Color GetCurrentColor()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			List<Color> palette = GetPalette();
			if (currentColorIndex == -1)
			{
				return Color.white;
			}
			return palette[currentColorIndex];
		}

		internal static string GetCurrentColorName()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			List<Color> palette = GetPalette();
			if (currentColorIndex >= 0 && MarkerPreferences.PaintballColors != "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000")
			{
				return "#" + ColorUtility.ToHtmlStringRGBA(palette[currentColorIndex]);
			}
			return currentColorIndex switch
			{
				-1 => "Default", 
				0 => "Rot", 
				1 => "Gelb", 
				2 => "Grün", 
				3 => "Blau", 
				4 => "Orange", 
				5 => "Lila", 
				6 => "Weiß", 
				7 => "Schwarz", 
				_ => "Unbekannt", 
			};
		}

		private static List<Color> GetPalette()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			string paintballColors = MarkerPreferences.PaintballColors;
			if (paintballColors == "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000")
			{
				if (currentColorIndex >= ColorPalette.Count)
				{
					currentColorIndex = -1;
				}
				return ColorPalette;
			}
			if (paintballColors != cachedPaletteText)
			{
				List<Color> list = new List<Color>();
				string[] array = paintballColors.Split(',');
				Color item = default(Color);
				for (int i = 0; i < array.Length; i++)
				{
					if (!ColorUtility.TryParseHtmlString(array[i].Trim(), ref item))
					{
						throw new InvalidOperationException("Effective paint palette contains an invalid HTML color.");
					}
					list.Add(item);
				}
				cachedPalette = list;
				cachedPaletteText = paintballColors;
			}
			if (currentColorIndex >= cachedPalette.Count)
			{
				currentColorIndex = -1;
			}
			return cachedPalette;
		}

		internal static bool IsPaintball(int itemMasterID)
		{
			return PaintballMasterIDs.Contains(itemMasterID);
		}

		internal static void ApplyColorToPaintball(object inventoryItem)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (inventoryItem == null)
			{
				return;
			}
			try
			{
				Transform propertyValue = ReflectionHelper.GetPropertyValue<Transform>(inventoryItem, "Transform");
				if ((Object)(object)propertyValue == (Object)null)
				{
					return;
				}
				bool useDefaultMaterial = !hasColorBeenSelected || currentColorIndex == -1;
				Color currentColor = GetCurrentColor();
				Renderer[] componentsInChildren = ((Component)propertyValue).GetComponentsInChildren<Renderer>(true);
				if (componentsInChildren != null && componentsInChildren.Length != 0)
				{
					Renderer[] array = componentsInChildren;
					for (int i = 0; i < array.Length; i++)
					{
						MaterialColorService.ApplyColorToRenderer(array[i], currentColor, useDefaultMaterial);
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[PaintBallColorManager] Error: " + ex.Message + "\n" + ex.StackTrace);
			}
		}

		internal static bool HasColorBeenSelected()
		{
			return hasColorBeenSelected;
		}

		internal static int GetCurrentColorIndex()
		{
			return currentColorIndex;
		}
	}
	internal static class PaintPersistenceManager
	{
		private const int PaintspotMasterId = 71001;

		private static readonly HashSet<int> PaintballMasterIds = new HashSet<int> { 70010, 70011, 70012, 70013, 70014, 70015, 70016, 70017, 70018 };

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

		private static readonly object DecalLock = new object();

		private static readonly Func<Hub, DataManager> DataManagerGetter = CreateDataManagerGetter();

		internal static long PermanentLifetimeMilliseconds
		{
			get
			{
				double num = (double)MarkerPreferences.PermanentLifetimeSeconds * 1000.0;
				if (!(num >= 9.223372036854776E+18))
				{
					return (long)Math.Max(1.0, num);
				}
				return long.MaxValue;
			}
		}

		internal static void ResetSession()
		{
			lock (DecalLock)
			{
				PermanentDecalIds.Clear();
			}
		}

		internal static bool ShouldKeep(FieldSkillObjectInfo info)
		{
			if (info == null)
			{
				return false;
			}
			if (MarkerPreferences.KeepFootprints && info.fieldSkillMasterID == 71001)
			{
				return true;
			}
			if (MarkerPreferences.KeepPuddles && PaintballMasterIds.Contains(info.fieldSkillMasterID))
			{
				return true;
			}
			return false;
		}

		internal static float OverrideLifetime(FieldSkillObjectInfo info, float originalSeconds)
		{
			if (!ShouldKeep(info))
			{
				return originalSeconds;
			}
			return MathF.Max(originalSeconds, MarkerPreferences.PermanentLifetimeSeconds);
		}

		internal static void DestroyWithOverride(Object target, float originalSeconds, FieldSkillObjectInfo info)
		{
			Object.Destroy(target, OverrideLifetime(info, originalSeconds));
		}

		internal static void TrackDecal(FieldSkillObjectInfo info)
		{
			if (!MarkerPreferences.KeepFootprints || info == null || info.fieldSkillMasterID != 71001)
			{
				return;
			}
			FieldSkillMemberInfo val = ResolveMemberInfo(info);
			if (val == null || string.IsNullOrEmpty(val.DecalId))
			{
				return;
			}
			lock (DecalLock)
			{
				PermanentDecalIds.Add(val.DecalId);
			}
		}

		internal static bool IsPermanentDecal(string decalId)
		{
			if (string.IsNullOrWhiteSpace(decalId))
			{
				return false;
			}
			lock (DecalLock)
			{
				return PermanentDecalIds.Contains(decalId);
			}
		}

		internal static void EnsureDecalLifetime(DecalData decalData)
		{
		}

		internal static void TrackDecalIfNeeded(DecalData decalData)
		{
			if (decalData == null || !MarkerPreferences.KeepFootprints || string.IsNullOrEmpty(decalData.DecalId) || !((Object)(object)decalData.SpawnBase != (Object)null) || (!ContainsPaintKeyword(decalData.DecalId) && !ContainsPaintKeyword(decalData.Socket)))
			{
				return;
			}
			lock (DecalLock)
			{
				PermanentDecalIds.Add(decalData.DecalId);
			}
		}

		private static bool ContainsPaintKeyword(string value)
		{
			if (!string.IsNullOrEmpty(value))
			{
				return value.IndexOf("paint", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		internal static bool IsPaintDecal(string decalId)
		{
			if (!ContainsPaintKeyword(decalId))
			{
				return IsPermanentDecal(decalId);
			}
			return true;
		}

		internal static bool IsPaintFieldSkill(int fieldSkillMasterID)
		{
			if (fieldSkillMasterID != 71001)
			{
				return PaintballMasterIds.Contains(fieldSkillMasterID);
			}
			return true;
		}

		internal static bool IsPaintPuddle(int fieldSkillMasterID)
		{
			return PaintballMasterIds.Contains(fieldSkillMasterID);
		}

		private static FieldSkillMemberInfo ResolveMemberInfo(FieldSkillObjectInfo info)
		{
			Hub s = Hub.s;
			if ((Object)(object)s == (Object)null)
			{
				return null;
			}
			DataManager val = DataManagerGetter(s);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			FieldSkillInfo fieldSkillData = val.ExcelDataManager.GetFieldSkillData(info.fieldSkillMasterID);
			if (fieldSkillData == null)
			{
				return null;
			}
			if (!fieldSkillData.FieldSkillMemberInfos.TryGetValue(info.fieldSkillIndex, out var value))
			{
				return null;
			}
			return value;
		}

		private static Func<Hub, DataManager> CreateDataManagerGetter()
		{
			return (Hub hub) => (!((Object)(object)hub != (Object)null)) ? null : ReflectionHelper.GetPropertyValue<DataManager>((object)hub, "dataman");
		}
	}
}
namespace MarkerMod.Config
{
	internal static class MarkerPreferences
	{
		private const string CategoryId = "MarkerMod";

		private static MelonPreferences_Category _category;

		private static MelonPreferences_Entry<bool> _keepFootprints;

		private static MelonPreferences_Entry<bool> _keepPuddles;

		private static MelonPreferences_Entry<float> _lifetimeSeconds;

		private static MelonPreferences_Entry<bool> _infinitePaintballs;

		private static MelonPreferences_Entry<bool> _enablePaintballColorChange;

		private static MelonPreferences_Entry<bool> _preventPaintballSelfDamage;

		private static MelonPreferences_Entry<bool> _allowLongPaintballThrows;

		private static HostSettings _hostSettings;

		private static HostSetting<bool> _hostInfinitePaintballs;

		private static HostSetting<bool> _hostPreventPaintballSelfDamage;

		private static HostSetting<bool> _hostAllowLongPaintballThrows;

		private static HostSetting<bool> _hostEnabled;

		private static HostSetting<bool> _hostKeepFootprints;

		private static HostSetting<bool> _hostKeepPuddles;

		private static HostSetting<float> _hostLifetimeSeconds;

		private static HostSetting<bool> _hostColorChange;

		private static HostSetting<float> _longThrowSnapRadius;

		private static HostSetting<string> _paintballColors;

		private static MelonPreferences_Entry<int> _colorMouseButton;

		internal const string DefaultPalette = "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000";

		internal static bool Enabled
		{
			get
			{
				HostSettings hostSettings = _hostSettings;
				if (hostSettings == null || !hostSettings.UsingLegacyHost)
				{
					return _hostEnabled?.Value ?? false;
				}
				return true;
			}
		}

		internal static bool KeepFootprints
		{
			get
			{
				if (Enabled)
				{
					return _hostKeepFootprints?.Value ?? false;
				}
				return false;
			}
		}

		internal static bool KeepPuddles
		{
			get
			{
				if (Enabled)
				{
					return _hostKeepPuddles?.Value ?? false;
				}
				return false;
			}
		}

		internal static float PermanentLifetimeSeconds => _hostLifetimeSeconds?.Value ?? 1f;

		internal static bool InfinitePaintballs
		{
			get
			{
				if (Enabled)
				{
					return _hostInfinitePaintballs?.Value ?? false;
				}
				return false;
			}
		}

		internal static bool EnablePaintballColorChange
		{
			get
			{
				if (Enabled)
				{
					return _hostColorChange?.Value ?? false;
				}
				return false;
			}
		}

		internal static bool PreventPaintballSelfDamage
		{
			get
			{
				if (Enabled)
				{
					return _hostPreventPaintballSelfDamage?.Value ?? false;
				}
				return false;
			}
		}

		internal static bool AllowLongPaintballThrows
		{
			get
			{
				if (Enabled)
				{
					return _hostAllowLongPaintballThrows?.Value ?? false;
				}
				return false;
			}
		}

		internal static float LongThrowSnapRadius => _longThrowSnapRadius?.Value ?? 25f;

		internal static string PaintballColors => _paintballColors?.Value ?? "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000";

		internal static int ColorMouseButton => Math.Clamp(_colorMouseButton?.Value ?? 1, -1, 2);

		internal static void Initialize()
		{
			if (_category == null)
			{
				_category = MelonPreferences.CreateCategory("MarkerMod", "Marker Mod");
				_keepFootprints = CreateEntry("keepFootprints", defaultValue: true, "Keep Footprints", "Keep paint footprints after puddles dry. When enabled, paint footprints will persist even after the paint puddles have dried up. Default: true");
				_keepPuddles = CreateEntry("keepPuddles", defaultValue: false, "Keep Puddles", "Keep paint puddles for the configured lifetime. On the host, their contact effect also stays active so they can keep producing footprints. Footprints are controlled separately by keepFootprints. Default: false");
				_lifetimeSeconds = CreateEntry("permanentLifetimeSeconds", 86400f, "Lifetime", "Lifetime in seconds for persistent paint. This determines how long paint marks will remain visible. Default: 86400 (24 hours)");
				_infinitePaintballs = CreateEntry("infinitePaintballs", defaultValue: false, "Infinite Paintballs", "Host setting, live. Paintballs are not consumed when thrown. Clients use the host value; a host without MarkerMod uses normal consumption. No new save is needed. Default: false");
				_enablePaintballColorChange = CreateEntry("enablePaintballColorChange", defaultValue: true, "Enable Paintball Color Change", "Host setting, live. Permit cycling through PaintballColors while holding a paintball. The local ColorMouseButton selects the input. Default: true");
				_preventPaintballSelfDamage = CreateEntry("preventPaintballSelfDamage", defaultValue: true, "Prevent Paintball Self Damage", "Stop your own thrown paintball from hitting and damaging you. In game 0.3.0 projectiles can hit their thrower after a short distance; this excludes you from your own paintball hits. Default: true");
				_allowLongPaintballThrows = CreateEntry("allowLongPaintballThrows", defaultValue: false, "Allow Long Paintball Throws (experimental)", "Reduce the navmesh clamp that makes thrown paintballs stop early at an 'invisible wall'. Experimental and server-authoritative (host only) - verify in-game before relying on it. Default: false");
				_hostSettings = new HostSettings("MarkerMod");
				_hostEnabled = _hostSettings.Register(CreateEntry("Enabled", defaultValue: true, "Enabled", "Host setting, next lobby. Master switch for MarkerMod. Existing timed paint keeps the lifetime assigned when it spawned."), vanillaValue: false, null, live: false, legacy: false);
				_hostKeepFootprints = _hostSettings.Register(_keepFootprints, vanillaValue: false, null, live: false, legacy: false);
				_hostKeepPuddles = _hostSettings.Register(_keepPuddles, vanillaValue: false, null, live: false, legacy: false);
				_hostLifetimeSeconds = _hostSettings.Register(_lifetimeSeconds, 1f, (float value) => value >= 1f, live: false, legacy: false, () => Mathf.Max(1f, _lifetimeSeconds.Value));
				_hostInfinitePaintballs = _hostSettings.Register(_infinitePaintballs, vanillaValue: false, null, live: true);
				_hostPreventPaintballSelfDamage = _hostSettings.Register(_preventPaintballSelfDamage, vanillaValue: false, null, live: true);
				_hostAllowLongPaintballThrows = _hostSettings.Register(_allowLongPaintballThrows, vanillaValue: false, null, live: true);
				_hostColorChange = _hostSettings.Register(_enablePaintballColorChange, vanillaValue: false, null, live: true, legacy: false);
				_longThrowSnapRadius = _hostSettings.Register(CreateEntry("LongThrowSnapRadius", 25f, "Long Throw Snap Radius", "Host setting, live. Navmesh search radius used when long throws are enabled. Positive world units. Default: 25"), 25f, (float value) => value > 0f, live: true, legacy: false);
				_paintballColors = _hostSettings.Register(CreateEntry("PaintballColors", "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000", "Paintball Color Palette", "Host setting, next lobby. Comma-separated HTML colors, 1-32 entries. The native eight colors are the default."), "#FF0000,#FFFF00,#00FF00,#0000FF,#FF8000,#8000FF,#FFFFFF,#000000", IsValidPalette, live: false, legacy: false);
				_colorMouseButton = CreateEntry("ColorMouseButton", 1, "Color Cycle Mouse Button", "Local input setting. Mouse button for changing paintball color: 0 left, 1 right, 2 middle, -1 disables cycling. Default: 1");
				_hostSettings.EffectiveValuesChanged += LogSessionSettings;
				_hostSettings.EffectiveValuesChanged += PaintBallColorManager.ResetColorSelection;
				_hostSettings.EffectiveValuesChanged += PaintPersistenceManager.ResetSession;
				_hostSettings.LiveValuesChanged += LogSessionSettings;
				_hostSettings.LiveValuesChanged += InfinitePaintballDisplayPatch.Refresh;
			}
		}

		private static void LogSessionSettings()
		{
			MelonLogger.Msg($"[MarkerMod] Session settings: inLobby={_hostSettings.IsInSession}, host={_hostSettings.IsHost}, infinitePaintballs={InfinitePaintballs} (local={_infinitePaintballs.Value}), keepFootprints={KeepFootprints}, keepPuddles={KeepPuddles}, lifetimeSeconds={PermanentLifetimeSeconds:F0}.");
		}

		private static MelonPreferences_Entry<T> CreateEntry<T>(string identifier, T defaultValue, string displayName, string description = null)
		{
			if (_category == null)
			{
				throw new InvalidOperationException("Preference category not initialized.");
			}
			return _category.CreateEntry<T>(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null);
		}

		private static bool IsValidPalette(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			string[] array = value.Split(',');
			if (array.Length > 32)
			{
				return false;
			}
			string[] array2 = array;
			Color val = default(Color);
			for (int i = 0; i < array2.Length; i++)
			{
				if (!ColorUtility.TryParseHtmlString(array2[i].Trim(), ref val))
				{
					return false;
				}
			}
			return true;
		}
	}
}