Decompiled source of RunningMan v1.5.10

plugins\RunningMan\RunningMan.dll

Decompiled 2 days 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.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunningMan.Net;
using RunningMan.Patches;
using RunningMan.Storage;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("RunningMan")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Marathon race tracker for Valheim dedicated servers")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0a508e319f966e579403952eda19df5d5ddd287a")]
[assembly: AssemblyProduct("RunningMan")]
[assembly: AssemblyTitle("RunningMan")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunningMan
{
	[Serializable]
	public sealed class RaceGate
	{
		public Vector3Data PointA = new Vector3Data();

		public Vector3Data PointB = new Vector3Data();

		public Vector3Data Forward = new Vector3Data();

		public RaceGate()
		{
		}

		public RaceGate(Vector3 pointA, Vector3 pointB, Vector3 forward)
		{
			//IL_0028: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			PointA = ValheimUtil.ToData(pointA);
			PointB = ValheimUtil.ToData(pointB);
			Forward = ValheimUtil.ToData(forward);
		}

		public Vector3 GetPointA()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return ValheimUtil.FromData(PointA);
		}

		public Vector3 GetPointB()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return ValheimUtil.FromData(PointB);
		}

		public Vector3 GetForward()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return ValheimUtil.FromData(Forward);
		}

		public Vector3 GetMidpoint()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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)
			return (GetPointA() + GetPointB()) * 0.5f;
		}

		public bool IsConfigured()
		{
			//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)
			//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)
			if (PointA != null && PointB != null)
			{
				Vector3 val = GetPointA() - GetPointB();
				return ((Vector3)(ref val)).sqrMagnitude > 0.01f;
			}
			return false;
		}

		public void SetEndpoint(bool pointA, Vector3 worldPosition, Vector3? preferredForward = null)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			if (pointA)
			{
				PointA = ValheimUtil.ToData(worldPosition);
			}
			else
			{
				PointB = ValheimUtil.ToData(worldPosition);
			}
			RecomputeForward(preferredForward);
		}

		public void RecomputeForward(Vector3? preferredForward = null)
		{
			//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_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_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			Vector3 pointA = GetPointA();
			Vector3 val = GetPointB() - pointA;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude < 0.0001f)
			{
				Vector3 val2 = (Vector3)(((??)preferredForward) ?? GetForward());
				val2.y = 0f;
				if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f)
				{
					val2 = Vector3.forward;
				}
				Forward = ValheimUtil.ToData(((Vector3)(ref val2)).normalized);
				return;
			}
			Vector3 val3 = Vector3.Cross(Vector3.up, ((Vector3)(ref val)).normalized);
			if (preferredForward.HasValue)
			{
				Vector3 value = preferredForward.Value;
				value.y = 0f;
				if (((Vector3)(ref value)).sqrMagnitude > 0.0001f && Vector3.Dot(val3, value) < 0f)
				{
					val3 = -val3;
				}
			}
			else
			{
				Vector3 forward = GetForward();
				forward.y = 0f;
				if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f && Vector3.Dot(val3, forward) < 0f)
				{
					val3 = -val3;
				}
			}
			Forward = ValheimUtil.ToData(((Vector3)(ref val3)).normalized);
		}

		public static RaceGate FromPositionAndForward(Vector3 position, Vector3 forward, float width)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_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)
			forward.y = 0f;
			if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
			{
				forward = Vector3.forward;
			}
			else
			{
				((Vector3)(ref forward)).Normalize();
			}
			Vector3 val = Vector3.Cross(Vector3.up, forward);
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			float num = width * 0.5f;
			return new RaceGate(position - normalized * num, position + normalized * num, forward);
		}

		public static RaceGate FromPlayerPosition(Player player, float width)
		{
			//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_0017: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			Vector3 forward = ((Component)player).transform.forward;
			return FromPositionAndForward(position, forward, width);
		}

		public static RaceGate FromPeerPosition(ZNetPeer peer, float width)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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)
			if (peer != null && ValheimUtil.TryGetPeerWorldPosition(peer, out var position))
			{
				return FromPositionAndForward(position, Vector3.forward, width);
			}
			return FromPositionAndForward(Vector3.zero, Vector3.forward, width);
		}
	}
	[Serializable]
	public sealed class Checkpoint
	{
		public int Index;

		public RaceGate Gate = new RaceGate();

		public Checkpoint()
		{
		}

		public Checkpoint(int index, RaceGate gate)
		{
			Index = index;
			Gate = gate;
		}
	}
	[Serializable]
	public sealed class TrackConfig
	{
		public string Name = string.Empty;

		public RaceGate StartGate = new RaceGate();

		public RaceGate FinishGate = new RaceGate();

		public List<Checkpoint> Checkpoints = new List<Checkpoint>();

		public bool HasStart
		{
			get
			{
				if (StartGate != null)
				{
					return StartGate.IsConfigured();
				}
				return false;
			}
		}

		public bool HasFinish
		{
			get
			{
				if (FinishGate != null)
				{
					return FinishGate.IsConfigured();
				}
				return false;
			}
		}

		public int CheckpointCount => Checkpoints?.Count ?? 0;
	}
	public static class CommandContext
	{
		[ThreadStatic]
		private static ZRpc _currentRpc;

		private static readonly List<string> PendingReplies = new List<string>();

		public static long SenderPeerId { get; set; }

		public static bool ExecutingFromRemote { get; set; }

		public static bool HasOrigin { get; private set; }

		public static Vector3 OriginPosition { get; private set; }

		public static Vector3 OriginForward { get; private set; }

		public static ZRpc CurrentRpc
		{
			get
			{
				return _currentRpc;
			}
			set
			{
				_currentRpc = value;
			}
		}

		public static void SetOrigin(Vector3 position, Vector3 forward)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			HasOrigin = true;
			OriginPosition = position;
			OriginForward = forward;
		}

		public static bool TryGetOrigin(out Vector3 position, out Vector3 forward)
		{
			//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_000c: 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)
			position = OriginPosition;
			forward = OriginForward;
			return HasOrigin;
		}

		public static void ClearOrigin()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			HasOrigin = false;
			OriginPosition = Vector3.zero;
			OriginForward = Vector3.forward;
		}

		public static void Clear()
		{
			_currentRpc = null;
			SenderPeerId = 0L;
			ClearOrigin();
			PendingReplies.Clear();
		}

		public static void QueueReply(string message)
		{
			if (!string.IsNullOrEmpty(message))
			{
				PendingReplies.Add(message);
			}
		}

		public static IReadOnlyList<string> TakePendingReplies()
		{
			string[] result = PendingReplies.ToArray();
			PendingReplies.Clear();
			return result;
		}
	}
	public static class Commands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__HandleRunCommand;

			public static Action<ConsoleEventArgs> <1>__HandleStart;

			public static Action<ConsoleEventArgs> <2>__HandleDebug;

			public static Action<ConsoleEventArgs> <3>__HandleReset;

			public static Action<ConsoleEventArgs> <4>__HandleExport;

			public static Action<ConsoleEventArgs> <5>__HandleReload;

			public static Action<ConsoleEventArgs> <6>__HandleRegister;

			public static Action<ConsoleEventArgs> <7>__HandleRemove;

			public static Action<ConsoleEventArgs> <8>__HandleClear;

			public static Action<ConsoleEventArgs> <9>__HandleAutoDetect;

			public static Action<ConsoleEventArgs> <10>__HandleTrackName;

			public static Action<ConsoleEventArgs> <11>__HandleGearSet;

			public static Action<ConsoleEventArgs> <12>__HandleEdit;

			public static Action<ConsoleEventArgs> <13>__HandleSetPoint;
		}

		private static bool _registered;

		public static void Register()
		{
			//IL_003c: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			if (!_registered)
			{
				_registered = true;
				object obj = <>O.<0>__HandleRunCommand;
				if (obj == null)
				{
					ConsoleEvent val = HandleRunCommand;
					<>O.<0>__HandleRunCommand = val;
					obj = (object)val;
				}
				new ConsoleCommand("run", "RunningMan marathon commands", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				RunningManPlugin.Log.LogInfo((object)"Registered /run chat command.");
			}
		}

		public static void ExecuteFromRemote(long senderPeerId, string argLine)
		{
			ZRpc rpc = null;
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				rpc = ZNet.instance.GetPeer(senderPeerId)?.m_rpc;
			}
			CommandContext.ExecutingFromRemote = true;
			CommandContext.SenderPeerId = senderPeerId;
			try
			{
				Execute(argLine, rpc);
			}
			finally
			{
				CommandContext.ExecutingFromRemote = false;
			}
		}

		public static void Execute(string argLine, ZRpc rpc)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			CommandContext.CurrentRpc = rpc;
			if (rpc != null && (Object)(object)ZNet.instance != (Object)null)
			{
				ZNetPeer peer = ZNet.instance.GetPeer(rpc);
				if (peer != null)
				{
					CommandContext.SenderPeerId = peer.m_uid;
				}
			}
			HandleRunCommand(new ConsoleEventArgs("run" + (string.IsNullOrWhiteSpace(argLine) ? string.Empty : (" " + argLine.Trim())), (Terminal)(object)Console.instance));
		}

		private static void HandleRunCommand(ConsoleEventArgs args)
		{
			try
			{
				string[] array = NormalizeArgs(args.Args);
				if (TryHandleClientLocalCommand(array, args))
				{
					return;
				}
				if (!ValheimUtil.IsServerAuthority() && !CommandContext.ExecutingFromRemote && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
				{
					ValheimUtil.RunCommand(string.Join(" ", array));
					return;
				}
				if (array.Length == 0)
				{
					PrintHelp(args.Context);
					return;
				}
				string text = array[0].ToLowerInvariant();
				switch (text)
				{
				case "status":
					HandleStatus(args);
					break;
				case "join":
					HandleJoin(args);
					break;
				case "leave":
					HandleLeave(args);
					break;
				case "gear":
				case "gearcheck":
					HandleGearCheck(args);
					break;
				case "loadout":
					ValheimUtil.Reply(args.Context, GearValidator.FormatRequiredLoadout());
					break;
				case "admincheck":
					HandleAdminCheck(args);
					break;
				case "last":
					HandleLastRun(args);
					break;
				case "pb":
					HandlePersonalBest(args);
					break;
				case "open":
					RequireAdmin(args, delegate(ConsoleEventArgs a)
					{
						RaceManager.Instance.OpenRegistration();
						ValheimUtil.Reply(a.Context, "Registration opened.");
					});
					break;
				case "close":
					RequireAdmin(args, delegate(ConsoleEventArgs a)
					{
						RaceManager.Instance.CloseRegistration();
						ValheimUtil.Reply(a.Context, "Registration closed.");
					});
					break;
				case "start":
					RequireAdmin(args, HandleStart);
					break;
				case "cancel":
					RequireAdmin(args, delegate(ConsoleEventArgs a)
					{
						RaceManager.Instance.CancelEvent();
						ValheimUtil.Reply(a.Context, "Event cancelled.");
					});
					break;
				case "debug":
					RequireAdmin(args, HandleDebug);
					break;
				case "records":
				case "leaderboard":
				case "worldrecords":
					HandleWorldRecords(args);
					break;
				case "reset":
					RequireAdmin(args, HandleReset);
					break;
				case "export":
					RequireAdmin(args, HandleExport);
					break;
				case "reload":
					RequireAdmin(args, HandleReload);
					break;
				case "sync":
					HandleSync(args);
					break;
				case "register":
					RequireAdmin(args, HandleRegister);
					break;
				case "remove":
					RequireAdmin(args, HandleRemove);
					break;
				case "clear":
					RequireAdmin(args, HandleClear);
					break;
				case "autodetect":
					RequireAdmin(args, HandleAutoDetect);
					break;
				case "trackname":
					RequireAdmin(args, HandleTrackName);
					break;
				case "gearset":
					RequireAdmin(args, HandleGearSet);
					break;
				case "edit":
					RequireAdmin(args, HandleEdit);
					break;
				case "setpoint":
					RequireAdmin(args, HandleSetPoint);
					break;
				case "wrboard":
				case "bulletin":
					RequireAdmin(args, delegate(ConsoleEventArgs a)
					{
						HandleBulletinBoard(a, RaceBulletinKind.Records);
					});
					break;
				case "rulesboard":
				case "rulesign":
					RequireAdmin(args, delegate(ConsoleEventArgs a)
					{
						HandleBulletinBoard(a, RaceBulletinKind.Rules);
					});
					break;
				default:
					ValheimUtil.Reply(args.Context, "Unknown /run subcommand: " + text);
					PrintHelp(args.Context);
					break;
				}
			}
			catch (Exception ex)
			{
				ValheimUtil.Reply(args.Context, "RunningMan error: " + ex.Message);
				RunningManPlugin.Log.LogError((object)ex);
			}
		}

		private static string[] NormalizeArgs(string[] args)
		{
			if (args == null || args.Length == 0)
			{
				return Array.Empty<string>();
			}
			if (args[0].Equals("run", StringComparison.OrdinalIgnoreCase))
			{
				return args.Skip(1).ToArray();
			}
			return args;
		}

		private static void HandleStatus(ConsoleEventArgs args)
		{
			if (!ValheimUtil.TryResolveCommandParticipant(out var participant))
			{
				ValheimUtil.ReplyHud("Could not resolve player for status.");
			}
			else
			{
				ValheimUtil.ReplyHud(RaceManager.Instance.FormatStatus(participant.PlayerId, DateTime.UtcNow));
			}
		}

		private static void HandleJoin(ConsoleEventArgs args)
		{
			if (!ValheimUtil.TryResolveCommandParticipant(out var participant))
			{
				ValheimUtil.ReplyHud("Could not resolve player.");
			}
			else if (RaceManager.Instance.JoinPlayer(participant.PlayerId, participant.PlayerName))
			{
				ValheimUtil.ReplyHud(participant.PlayerName + " joined the race.");
			}
			else
			{
				ValheimUtil.ReplyHud("Could not join — registration may be closed or you are already registered.");
			}
		}

		private static void HandleLeave(ConsoleEventArgs args)
		{
			if (!ValheimUtil.TryResolveCommandParticipant(out var participant))
			{
				ValheimUtil.ReplyHud("Could not resolve player.");
			}
			else if (RaceManager.Instance.LeavePlayer(participant.PlayerId))
			{
				ValheimUtil.ReplyHud(participant.PlayerName + " left the race.");
			}
			else
			{
				ValheimUtil.ReplyHud("You are not registered.");
			}
		}

		private static void HandleStart(ConsoleEventArgs args)
		{
			if (RaceManager.Instance.StartCountdown(out var failureReason))
			{
				ValheimUtil.Reply(args.Context, $"Countdown started ({ModConfig.CountdownSeconds.Value}s).");
			}
			else
			{
				ValheimUtil.Reply(args.Context, string.IsNullOrEmpty(failureReason) ? "Could not start countdown. Use /run open, have runners /run join, then /run start." : failureReason);
			}
		}

		private static void HandleDebug(ConsoleEventArgs args)
		{
			bool? flag = ResolveDebugToggle(NormalizeArgs(args.Args));
			if (!flag.HasValue)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run debug [on|off]");
				return;
			}
			RaceManager.Instance.SetDebugMode(flag.Value);
			ValheimUtil.Reply(args.Context, "Debug markers: " + (flag.Value ? "ON" : "OFF"));
		}

		private static bool? ResolveDebugToggle(string[] subArgs)
		{
			if (subArgs.Length >= 2)
			{
				switch (subArgs[1].ToLowerInvariant())
				{
				case "on":
				case "true":
				case "1":
					return true;
				case "off":
				case "false":
				case "0":
					return false;
				}
			}
			return !RaceNetSync.IsDebugModeActive();
		}

		private static bool TryHandleClientLocalCommand(string[] subArgs, ConsoleEventArgs args)
		{
			if (subArgs.Length == 0 || (Object)(object)Player.m_localPlayer == (Object)null || CommandContext.ExecutingFromRemote)
			{
				return false;
			}
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return false;
			}
			switch (subArgs[0].ToLowerInvariant())
			{
			case "gearcheck":
			case "gear":
				HandleGearCheck(args, Player.m_localPlayer);
				return true;
			case "edit":
				HandleEdit(args);
				return true;
			case "loadout":
				ValheimUtil.Reply(args.Context, GearValidator.FormatRequiredLoadout());
				return true;
			default:
				return false;
			}
		}

		private static void HandleGearCheck(ConsoleEventArgs args, Player player = null)
		{
			if (player == null)
			{
				player = ValheimUtil.ResolvePlayerForGearCheck();
			}
			if ((Object)(object)player == (Object)null)
			{
				ValheimUtil.Reply(args.Context, "Could not resolve player.");
				return;
			}
			GearValidator.GearCheckResult gearCheckResult = RaceManager.Instance.CheckPlayerGear(player);
			string title = (gearCheckResult.IsValid ? "Gear check passed" : "Gear check failed");
			string body = (gearCheckResult.IsValid ? "Gear check passed." : ("- " + string.Join("\n- ", gearCheckResult.Issues)));
			RaceGui.ShowInfoPanel(title, body, gearCheckResult.IsValid ? 5f : 14f);
			ValheimUtil.Reply(args.Context, gearCheckResult.Format(multiline: false));
		}

		private static void HandleAdminCheck(ConsoleEventArgs args)
		{
			if (ValheimUtil.IsServerAuthority())
			{
				ZRpc currentRpc = CommandContext.CurrentRpc;
				ZNetPeer val = ((currentRpc != null) ? ZNet.instance.GetPeer(currentRpc) : null);
				string message = ValheimUtil.FormatAdminIdentity(val, currentRpc);
				string text = ((val != null) ? ValheimUtil.GetPlatformUserIdString(val) : null);
				bool flag = ValheimUtil.IsAdmin(currentRpc);
				ValheimUtil.Reply(args.Context, $"Server admin check: admin={flag}");
				ValheimUtil.Reply(args.Context, message);
				if (!string.IsNullOrEmpty(text))
				{
					ValheimUtil.Reply(args.Context, "Add this exact line to adminlist.txt on the server if crossplay is enabled: " + text);
				}
				ValheimUtil.ReplyHud(flag ? "Admin: true" : "Admin: false");
				if (CommandContext.SenderPeerId != 0L)
				{
					RaceNetSync.SendAdminStatusToPeer(CommandContext.SenderPeerId, flag);
				}
			}
			else
			{
				bool num = ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost()) || RaceNetSync.ClientIsAdmin;
				ValheimUtil.Reply(args.Context, $"Client: Valheim admin={(Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost()}, synced admin={RaceNetSync.ClientIsAdmin}");
				ValheimUtil.ReplyHud(num ? "Admin: true" : "Admin: false");
			}
		}

		private static void HandlePersonalBest(ConsoleEventArgs args)
		{
			if (!ValheimUtil.TryResolveCommandParticipant(out var participant))
			{
				ValheimUtil.Reply(args.Context, "Could not resolve player.");
			}
			else
			{
				ValheimUtil.Reply(args.Context, Leaderboard.FormatPersonalBest(Leaderboard.GetPersonalBest(participant.PlayerId)));
			}
		}

		private static void HandleLastRun(ConsoleEventArgs args)
		{
			if (!ValheimUtil.TryResolveCommandParticipant(out var participant))
			{
				ValheimUtil.Reply(args.Context, "Could not resolve player.");
			}
			else
			{
				ValheimUtil.Reply(args.Context, Leaderboard.FormatLastRun(Leaderboard.GetLastRun(participant.PlayerId)));
			}
		}

		private static void HandleWorldRecords(ConsoleEventArgs args)
		{
			string message = Leaderboard.FormatWorldRecords(JsonStorage.Track, ModConfig.LeaderboardLimit.Value);
			ValheimUtil.Reply(args.Context, message);
			ValheimUtil.ReplyHud(message);
		}

		private static void HandleTrackName(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Current track: " + TrackIdentity.GetDisplayName(JsonStorage.Track) + ". Usage: /run trackname <name>");
				return;
			}
			string text = string.Join(" ", array.Skip(1));
			RaceManager.Instance.SetTrackName(text);
			ValheimUtil.Reply(args.Context, "Track name set to: " + text);
		}

		private static void HandleGearSet(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run gearset reset | /run gearset save <payload>  (use F6 Allowed gear)");
				ValheimUtil.Reply(args.Context, GearValidator.FormatRequiredLoadout());
				return;
			}
			switch (array[1].ToLowerInvariant())
			{
			case "reset":
			case "defaults":
				JsonStorage.ResetAllowedGearToDefaults();
				RaceManager.Instance.BroadcastState();
				ValheimUtil.Reply(args.Context, "Allowed gear reset to marathon defaults.");
				ValheimUtil.Reply(args.Context, GearValidator.FormatRequiredLoadout());
				break;
			case "save":
			case "set":
			{
				if (!TryParseGearSetPayload(string.Join(" ", array.Skip(2)), out var rules))
				{
					ValheimUtil.Reply(args.Context, "Invalid gearset payload. Use F6 Allowed gear → Save.");
					break;
				}
				JsonStorage.SetAllowedGear(rules);
				RaceManager.Instance.BroadcastState();
				ValheimUtil.Reply(args.Context, "Allowed gear saved.");
				ValheimUtil.Reply(args.Context, GearValidator.FormatRequiredLoadout());
				break;
			}
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run gearset reset | /run gearset save <payload>");
				break;
			}
		}

		private static bool TryParseGearSetPayload(string payload, out AllowedGearRules rules)
		{
			rules = null;
			if (string.IsNullOrWhiteSpace(payload))
			{
				return false;
			}
			string[] array = payload.Split(new char[1] { '§' }, StringSplitOptions.None);
			if (array.Length < 11)
			{
				return false;
			}
			if (!int.TryParse(array[8], out var result) || !int.TryParse(array[9], out var result2) || !int.TryParse(array[10], out var _))
			{
				return false;
			}
			AllowedGearRules allowedGearRules = JsonStorage.AllowedGear ?? AllowedGearRules.CreateDefaults();
			rules = allowedGearRules.Clone();
			rules.Helmet = array[0] ?? string.Empty;
			rules.Chest = array[1] ?? string.Empty;
			rules.Legs = array[2] ?? string.Empty;
			rules.Cape = array[3] ?? string.Empty;
			rules.AllowedHandItems = array[4] ?? string.Empty;
			if (!string.IsNullOrWhiteSpace(array[5]))
			{
				rules.AntiStingPrefab = array[5].Trim();
			}
			if (!string.IsNullOrWhiteSpace(array[6]))
			{
				rules.RatatoskPrefab = array[6].Trim();
			}
			rules.RequiredAntiSting = Math.Max(0, result);
			rules.RequiredRatatosk = Math.Max(0, result2);
			if (array.Length >= 17 && int.TryParse(array[14], out var result4) && int.TryParse(array[15], out var result5) && int.TryParse(array[16], out var result6))
			{
				if (!string.IsNullOrWhiteSpace(array[11]))
				{
					rules.SaladPrefab = array[11].Trim();
				}
				if (!string.IsNullOrWhiteSpace(array[12]))
				{
					rules.BloodPuddingPrefab = array[12].Trim();
				}
				if (!string.IsNullOrWhiteSpace(array[13]))
				{
					rules.MushroomOmelettePrefab = array[13].Trim();
				}
				rules.RequiredSalad = Math.Max(0, result4);
				rules.RequiredBloodPudding = Math.Max(0, result5);
				rules.RequiredMushroomOmelette = Math.Max(0, result6);
			}
			return true;
		}

		private static void HandleEdit(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			bool flag;
			if (array.Length >= 2)
			{
				switch (array[1].ToLowerInvariant())
				{
				case "on":
				case "true":
				case "1":
					flag = true;
					break;
				case "off":
				case "false":
				case "0":
					flag = false;
					break;
				default:
					ValheimUtil.Reply(args.Context, "Usage: /run edit [on|off]");
					return;
				}
			}
			else
			{
				flag = !RaceGateEditor.EditMode;
			}
			RaceGateEditor.SetEditMode(flag);
			ValheimUtil.Reply(args.Context, flag ? "Gate edit ON. Equip Hammer, look at endpoint dots, Use (E) to pick up / place." : "Gate edit OFF.");
		}

		private static void HandleSetPoint(ConsoleEventArgs args)
		{
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 3)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run setpoint start|finish a|b  OR  /run setpoint checkpoint <index> a|b");
				return;
			}
			if (!TryResolveSearchOrigin(ResolvePlayer(args), out var origin))
			{
				ValheimUtil.Reply(args.Context, "Could not resolve position for setpoint.");
				return;
			}
			string text = array[1].ToLowerInvariant();
			int result = 0;
			RaceManager.GateKind gateKind;
			string text2;
			switch (text)
			{
			case "start":
				gateKind = RaceManager.GateKind.Start;
				text2 = array[2];
				break;
			case "finish":
				gateKind = RaceManager.GateKind.Finish;
				text2 = array[2];
				break;
			case "checkpoint":
			case "cp":
				if (array.Length < 4 || !int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
				{
					ValheimUtil.Reply(args.Context, "Usage: /run setpoint checkpoint <index> a|b");
					return;
				}
				gateKind = RaceManager.GateKind.Checkpoint;
				text2 = array[3];
				break;
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run setpoint start|finish a|b  OR  /run setpoint checkpoint <index> a|b");
				return;
			}
			bool flag;
			switch (text2.ToLowerInvariant())
			{
			case "a":
			case "1":
			case "left":
				flag = true;
				break;
			case "b":
			case "2":
			case "right":
				flag = false;
				break;
			default:
				ValheimUtil.Reply(args.Context, "Endpoint must be a or b.");
				return;
			}
			Vector3? preferredForward = null;
			if (CommandContext.TryGetOrigin(out var _, out var forward))
			{
				preferredForward = forward;
			}
			if (RaceManager.Instance.SetGateEndpoint(gateKind, result, flag, origin, preferredForward))
			{
				string text3 = ((gateKind == RaceManager.GateKind.Checkpoint) ? $"CP{result}" : gateKind.ToString().ToUpperInvariant());
				ValheimUtil.Reply(args.Context, "Moved " + text3 + " point " + (flag ? "A" : "B") + ".");
			}
			else
			{
				ValheimUtil.Reply(args.Context, "Could not move that endpoint (gate missing?).");
			}
		}

		private static void HandleReset(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run reset <player>");
				return;
			}
			Player val = ValheimUtil.FindPlayerByName(array[1]);
			if ((Object)(object)val == (Object)null)
			{
				ValheimUtil.Reply(args.Context, "Player not found: " + array[1]);
			}
			else if (RaceManager.Instance.ResetActiveRun(val.GetPlayerID()))
			{
				ValheimUtil.Reply(args.Context, "Reset active run for " + val.GetPlayerName() + ".");
			}
			else
			{
				ValheimUtil.Reply(args.Context, val.GetPlayerName() + " has no active run.");
			}
		}

		private static void HandleExport(ConsoleEventArgs args)
		{
			JsonStorage.ExportAll();
			ValheimUtil.Reply(args.Context, "Exported race data to " + JsonStorage.ExportFilePath);
		}

		private static void HandleSync(ConsoleEventArgs args)
		{
			if (!ValheimUtil.IsServerAuthority())
			{
				ValheimUtil.Reply(args.Context, "State sync must run on the server.");
				return;
			}
			RaceStateSnapshot snapshot = RaceManager.Instance.BuildClientSnapshot(DateTime.UtcNow);
			long senderPeerId = CommandContext.SenderPeerId;
			if (senderPeerId != 0L)
			{
				RaceNetSync.SendStateToPeer(senderPeerId, snapshot);
				RaceNetSync.SendTrackToPeer(senderPeerId, JsonStorage.Track);
			}
			else
			{
				RaceManager.Instance.BroadcastState();
				ValheimUtil.Reply(args.Context, "RunningMan state refreshed.");
			}
		}

		private static void HandleReload(ConsoleEventArgs args)
		{
			ModConfig.ConfigFile.Reload();
			ModConfig.Initialize(ModConfig.ConfigFile);
			JsonStorage.Initialize(Paths.ConfigPath);
			RaceManager.Instance.SyncTrack();
			RaceNetSync.SendBulletins();
			ValheimUtil.Reply(args.Context, "RunningMan configuration and data reloaded.");
		}

		private static void HandleBulletinBoard(ConsoleEventArgs args, RaceBulletinKind kind)
		{
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			string text = ((kind == RaceBulletinKind.Rules) ? "RULES" : "WR");
			List<Vector3Data> list = ((kind == RaceBulletinKind.Rules) ? JsonStorage.RulesBoards : JsonStorage.WrBoards);
			string[] array = NormalizeArgs(args.Args);
			string text2 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "add");
			string message = ((kind == RaceBulletinKind.Rules) ? "Usage: /run rulesboard [add|remove|list|clear]" : "Usage: /run wrboard [add|remove|list|clear]");
			switch (text2)
			{
			case "list":
				ValheimUtil.Reply(args.Context, (list.Count == 0) ? ("No " + text + " bulletin boards marked.") : $"{list.Count} {text} bulletin board(s) marked.");
				break;
			case "clear":
				if (kind == RaceBulletinKind.Rules)
				{
					JsonStorage.SetRulesBoards(new List<Vector3Data>());
				}
				else
				{
					JsonStorage.SetWrBoards(new List<Vector3Data>());
				}
				RaceNetSync.SendBulletins();
				ValheimUtil.Reply(args.Context, "All " + text + " bulletin boards cleared.");
				break;
			case "remove":
			{
				if (!TryResolveBoardPosition(args, out var position2))
				{
					ValheimUtil.Reply(args.Context, "Look at a marked " + text + " Sign (or stand near one) and run the remove command.");
					break;
				}
				int num = -1;
				float num2 = float.MaxValue;
				for (int i = 0; i < list.Count; i++)
				{
					float num3 = Vector3.Distance(position2, ValheimUtil.FromData(list[i]));
					if (num3 < num2)
					{
						num2 = num3;
						num = i;
					}
				}
				if (num < 0 || num2 > 8f)
				{
					ValheimUtil.Reply(args.Context, "No " + text + " board within 8m to remove.");
					break;
				}
				list.RemoveAt(num);
				if (kind == RaceBulletinKind.Rules)
				{
					JsonStorage.SaveRulesBoards();
				}
				else
				{
					JsonStorage.SaveWrBoards();
				}
				RaceNetSync.SendBulletins();
				ValheimUtil.Reply(args.Context, $"Removed {text} board ({list.Count} remaining).");
				break;
			}
			default:
			{
				if (text2 != "add" && array.Length >= 2)
				{
					ValheimUtil.Reply(args.Context, message);
					break;
				}
				if (!TryResolveBoardPosition(args, out var position))
				{
					ValheimUtil.Reply(args.Context, "Look at a Hammer Sign and run /run " + ((kind == RaceBulletinKind.Rules) ? "rulesboard" : "wrboard") + ".");
					break;
				}
				if (RaceWrBoards.TryFindBoardIndex(RaceBulletinKind.Records, position, 1.5f, out var index) || RaceWrBoards.TryFindBoardIndex(RaceBulletinKind.Rules, position, 1.5f, out index))
				{
					ValheimUtil.Reply(args.Context, "That Sign is already marked as a bulletin.");
					break;
				}
				list.Add(ValheimUtil.ToData(position));
				if (kind == RaceBulletinKind.Rules)
				{
					JsonStorage.SaveRulesBoards();
				}
				else
				{
					JsonStorage.SaveWrBoards();
				}
				RaceNetSync.SendBulletins();
				ValheimUtil.Reply(args.Context, $"{text} Sign marked ({list.Count} total). Walk up to it to read.");
				break;
			}
			}
		}

		private static bool TryResolveBoardPosition(ConsoleEventArgs args, out Vector3 position)
		{
			//IL_0030: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (CommandContext.TryGetOrigin(out position, out var _))
			{
				return true;
			}
			Player val = ResolvePlayer(args);
			if ((Object)(object)val != (Object)null)
			{
				position = ((Component)val).transform.position;
				return true;
			}
			position = Vector3.zero;
			return false;
		}

		private static void HandleRegister(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run register start|finish|checkpoint [width]");
				return;
			}
			Player player = ResolvePlayer(args);
			float num = ParseRegistrationWidth(array, 2);
			RaceGate raceGate = ValheimUtil.CreateRegistrationGate(player, ValheimUtil.ResolveCommandPeer(), num);
			if (raceGate == null)
			{
				ValheimUtil.Reply(args.Context, "Could not resolve player position for registration.");
				return;
			}
			switch (array[1].ToLowerInvariant())
			{
			case "start":
				RaceManager.Instance.RegisterStartGate(raceGate);
				ValheimUtil.Reply(args.Context, $"Start gate registered (width {num:0.#}m). Enable debug to preview markers.");
				break;
			case "finish":
				RaceManager.Instance.RegisterFinishGate(raceGate);
				ValheimUtil.Reply(args.Context, $"Finish gate registered (width {num:0.#}m). Enable debug to preview markers.");
				break;
			case "checkpoint":
			{
				int num2 = RaceManager.Instance.RegisterCheckpoint(raceGate);
				ValheimUtil.Reply(args.Context, $"Checkpoint {num2} registered (width {num:0.#}m). Later checkpoints were renumbered. Enable debug to preview markers.");
				break;
			}
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run register start|finish|checkpoint [width]");
				break;
			}
		}

		private static void HandleRemove(ConsoleEventArgs args)
		{
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run remove start|finish|checkpoint <index|last>");
				return;
			}
			switch (array[1].ToLowerInvariant())
			{
			case "start":
				ValheimUtil.Reply(args.Context, RaceManager.Instance.ClearStartGate() ? "Start gate removed." : "No start gate is registered.");
				break;
			case "finish":
				ValheimUtil.Reply(args.Context, RaceManager.Instance.ClearFinishGate() ? "Finish gate removed." : "No finish gate is registered.");
				break;
			case "checkpoint":
			{
				int result;
				if (array.Length < 3)
				{
					ValheimUtil.Reply(args.Context, "Usage: /run remove checkpoint <index|last|nearest>");
				}
				else if (array[2].Equals("last", StringComparison.OrdinalIgnoreCase))
				{
					ValheimUtil.Reply(args.Context, RaceManager.Instance.RemoveLastCheckpoint() ? "Last checkpoint removed." : "No checkpoints are registered.");
				}
				else if (array[2].Equals("nearest", StringComparison.OrdinalIgnoreCase))
				{
					if (!TryResolveSearchOrigin(ResolvePlayer(args), out var origin))
					{
						ValheimUtil.Reply(args.Context, "Could not resolve position for nearest checkpoint removal.");
					}
					else
					{
						ValheimUtil.Reply(args.Context, RaceManager.Instance.RemoveNearestCheckpoint(origin) ? "Nearest checkpoint removed." : "No checkpoints are registered.");
					}
				}
				else if (!int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
				{
					ValheimUtil.Reply(args.Context, "Usage: /run remove checkpoint <index|last|nearest>");
				}
				else
				{
					ValheimUtil.Reply(args.Context, RaceManager.Instance.RemoveCheckpoint(result) ? $"Checkpoint {result} removed." : $"Checkpoint {result} was not found.");
				}
				break;
			}
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run remove start|finish|checkpoint <index|last|nearest>");
				break;
			}
		}

		private static void HandleClear(ConsoleEventArgs args)
		{
			string[] array = NormalizeArgs(args.Args);
			if (array.Length < 2)
			{
				ValheimUtil.Reply(args.Context, "Usage: /run clear checkpoints|start|finish|track|records [all]");
				return;
			}
			switch (array[1].ToLowerInvariant())
			{
			case "records":
			case "worldrecords":
				if (array.Length >= 3 && array[2].Equals("all", StringComparison.OrdinalIgnoreCase))
				{
					int num3 = RaceManager.Instance.ClearAllWorldRecords();
					ValheimUtil.Reply(args.Context, $"Cleared {num3} world record(s) for all tracks.");
				}
				else
				{
					int num4 = RaceManager.Instance.ClearTrackWorldRecords();
					ValheimUtil.Reply(args.Context, (num4 > 0) ? $"Cleared {num4} world record(s) for {TrackIdentity.GetDisplayName(JsonStorage.Track)}." : "No world records for this track.");
				}
				break;
			case "checkpoints":
			case "checkpoint":
			{
				int num2 = RaceManager.Instance.ClearCheckpoints();
				ValheimUtil.Reply(args.Context, (num2 > 0) ? $"Removed {num2} checkpoint(s)." : "No checkpoints are registered.");
				break;
			}
			case "start":
				ValheimUtil.Reply(args.Context, RaceManager.Instance.ClearStartGate() ? "Start gate removed." : "No start gate is registered.");
				break;
			case "finish":
				ValheimUtil.Reply(args.Context, RaceManager.Instance.ClearFinishGate() ? "Finish gate removed." : "No finish gate is registered.");
				break;
			case "track":
			{
				RaceManager.Instance.ClearStartGate();
				RaceManager.Instance.ClearFinishGate();
				int num = RaceManager.Instance.ClearCheckpoints();
				ValheimUtil.Reply(args.Context, $"Track cleared ({num} checkpoint(s) removed).");
				break;
			}
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run clear checkpoints|start|finish|track|records [all]");
				break;
			}
		}

		private static float ParseRegistrationWidth(string[] subArgs, int widthArgIndex)
		{
			float value = ModConfig.GateRegistrationWidth.Value;
			if (subArgs.Length <= widthArgIndex)
			{
				return value;
			}
			if (float.TryParse(subArgs[widthArgIndex], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return Mathf.Clamp(result, 1f, 50f);
			}
			return value;
		}

		private static void HandleAutoDetect(ConsoleEventArgs args)
		{
			//IL_020b: 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_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			string[] array = NormalizeArgs(args.Args);
			if (!TryResolveSearchOrigin(ResolvePlayer(args), out var origin))
			{
				ValheimUtil.Reply(args.Context, "Could not resolve player for auto-detect.");
				return;
			}
			float value = ModConfig.AutoDetectSearchRadius.Value;
			switch ((array.Length > 1) ? array[1].ToLowerInvariant() : "checkpoints")
			{
			case "start":
			{
				RaceGate raceGate3 = TriggerDetector.DetectGraustenGate(origin, value);
				if (raceGate3 == null)
				{
					ValheimUtil.Reply(args.Context, "No Grausten gate found nearby.");
					break;
				}
				RaceManager.Instance.RegisterStartGate(raceGate3);
				ValheimUtil.Reply(args.Context, "Start gate auto-detected.");
				break;
			}
			case "finish":
			{
				RaceGate raceGate2 = TriggerDetector.DetectGraustenGate(origin, value);
				if (raceGate2 == null)
				{
					ValheimUtil.Reply(args.Context, "No Grausten gate found nearby.");
					break;
				}
				RaceManager.Instance.RegisterFinishGate(raceGate2);
				ValheimUtil.Reply(args.Context, "Finish gate auto-detected.");
				break;
			}
			case "checkpoints":
			{
				List<Checkpoint> list = TriggerDetector.DetectTorchCheckpoints(origin, value, ModConfig.AutoDetectTorchPairMinDistance.Value, ModConfig.AutoDetectTorchPairMaxDistance.Value);
				if (list.Count == 0)
				{
					ValheimUtil.Reply(args.Context, "No torch checkpoint pairs found nearby.");
					break;
				}
				RaceManager.Instance.ReplaceCheckpoints(list);
				ValheimUtil.Reply(args.Context, $"Auto-detected {list.Count} checkpoint pair(s).");
				break;
			}
			case "checkpoint":
			case "cp":
			case "nearest":
			case "here":
			{
				float value2 = ModConfig.AutoDetectNearestSearchRadius.Value;
				RaceGate raceGate = TriggerDetector.DetectNearestTorchPair(origin, value2, ModConfig.AutoDetectTorchPairMinDistance.Value, ModConfig.AutoDetectTorchPairMaxDistance.Value);
				if (raceGate == null)
				{
					ValheimUtil.Reply(args.Context, $"No valid Standing Iron Torch pair within {value2:0.#}m. Need two torches {ModConfig.AutoDetectTorchPairMinDistance.Value:0.#}-{ModConfig.AutoDetectTorchPairMaxDistance.Value:0.#}m apart.");
					break;
				}
				int num = RaceManager.Instance.RegisterCheckpoint(raceGate);
				ValheimUtil.Reply(args.Context, $"Added checkpoint {num} from the two nearest Standing Iron Torches (later CPs renumbered).");
				break;
			}
			default:
				ValheimUtil.Reply(args.Context, "Usage: /run autodetect start|finish|checkpoints|checkpoint");
				break;
			}
		}

		private static void RequireAdmin(ConsoleEventArgs args, Action<ConsoleEventArgs> action)
		{
			if (!ValheimUtil.IsServerAuthority())
			{
				ValheimUtil.Reply(args.Context, "RunningMan admin commands must run on the server.");
			}
			else if (!ValheimUtil.IsAdmin(CommandContext.CurrentRpc))
			{
				string text = ValheimUtil.FormatAdminIdentity(ValheimUtil.ResolveCommandPeer(), CommandContext.CurrentRpc);
				RunningManPlugin.Log.LogWarning((object)("RunningMan admin denied for " + text + "."));
				ValheimUtil.Reply(args.Context, "Admin privileges required. If this server uses crossplay, adminlist.txt needs the Platform ID from F2, e.g. Steam_7656119...");
				ValheimUtil.Reply(args.Context, "Server sees: " + text);
			}
			else
			{
				action(args);
			}
		}

		private static bool TryResolveSearchOrigin(Player player, out Vector3 origin)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			if ((Object)(object)player != (Object)null)
			{
				origin = ((Component)player).transform.position;
				return true;
			}
			if (CommandContext.TryGetOrigin(out origin, out var _))
			{
				return true;
			}
			ZNetPeer val = ValheimUtil.ResolveCommandPeer();
			if (val != null && ValheimUtil.TryGetPeerWorldPosition(val, out origin))
			{
				return true;
			}
			origin = Vector3.zero;
			return false;
		}

		private static Player ResolvePlayer(ConsoleEventArgs args)
		{
			return ValheimUtil.ResolvePlayerForGearCheck();
		}

		private static void PrintHelp(Terminal context)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("RunningMan — press F6 for GUI");
			stringBuilder.AppendLine("Player:");
			stringBuilder.AppendLine("  /run join | leave | status | gearcheck | loadout | admincheck | pb | last");
			stringBuilder.AppendLine("Admin:");
			stringBuilder.AppendLine("  /run open | close | start | cancel | debug");
			stringBuilder.AppendLine("  /run register start|finish|checkpoint [width]");
			stringBuilder.AppendLine("  /run remove start|finish|checkpoint <index|last>");
			stringBuilder.AppendLine("  /run clear checkpoints|start|finish|track");
			stringBuilder.AppendLine("  /run autodetect start|finish|checkpoints|checkpoint");
			stringBuilder.AppendLine("  /run edit [on|off]  (hammer + Use to move gate endpoints)");
			stringBuilder.AppendLine("  /run setpoint start|finish a|b | checkpoint <n> a|b");
			stringBuilder.AppendLine("  /run worldrecords | trackname <name> | gearset reset|save");
			stringBuilder.AppendLine("  /run wrboard [add|remove|list|clear]  (look at a Hammer Sign)");
			stringBuilder.AppendLine("  /run rulesboard [add|remove|list|clear]  (RULES Sign)");
			stringBuilder.AppendLine("  /run clear records | clear records all");
			stringBuilder.AppendLine("  /run leaderboard | reset <player> | export | reload");
			ValheimUtil.Reply(context, stringBuilder.ToString());
		}
	}
	public static class ModConfig
	{
		public static ConfigEntry<bool> EnableBroadcasts { get; private set; }

		public static ConfigEntry<bool> EnableHud { get; private set; }

		public static ConfigEntry<bool> DebugMode { get; private set; }

		public static ConfigEntry<KeyCode> GuiHotkey { get; private set; }

		public static ConfigEntry<int> CountdownSeconds { get; private set; }

		public static ConfigEntry<bool> RequireRegistration { get; private set; }

		public static ConfigEntry<float> StartTriggerDistance { get; private set; }

		public static ConfigEntry<float> CheckpointDistance { get; private set; }

		public static ConfigEntry<float> GateEndPadding { get; private set; }

		public static ConfigEntry<float> CheckpointVerticalDistance { get; private set; }

		public static ConfigEntry<float> GateVerticalDistance { get; private set; }

		public static ConfigEntry<float> FinishTriggerDistance { get; private set; }

		public static ConfigEntry<bool> EnableLiveStandings { get; private set; }

		public static ConfigEntry<int> LiveStandingsLimit { get; private set; }

		public static ConfigEntry<float> UpdateInterval { get; private set; }

		public static ConfigEntry<float> GateRegistrationWidth { get; private set; }

		public static ConfigEntry<int> CountdownFontSize { get; private set; }

		public static ConfigEntry<int> CheckpointHudFontSize { get; private set; }

		public static ConfigEntry<string> SavePath { get; private set; }

		public static ConfigEntry<string> ExportFormat { get; private set; }

		public static ConfigEntry<int> LeaderboardLimit { get; private set; }

		public static ConfigEntry<int> WorldRecordsLimit { get; private set; }

		public static ConfigEntry<bool> EnableRaceSounds { get; private set; }

		public static ConfigEntry<float> RaceSoundVolume { get; private set; }

		public static ConfigEntry<string> RaceStartSounds { get; private set; }

		public static ConfigEntry<string> CheckpointSound { get; private set; }

		public static ConfigEntry<string> FirstPlaceFinishSounds { get; private set; }

		public static ConfigEntry<bool> UseCustomRaceSounds { get; private set; }

		public static ConfigEntry<string> CustomSoundsFolder { get; private set; }

		public static ConfigEntry<string> CustomStartSoundFile { get; private set; }

		public static ConfigEntry<string> CustomCheckpointSoundFile { get; private set; }

		public static ConfigEntry<string> CustomFinishSoundFile { get; private set; }

		public static ConfigEntry<string> CustomCountdownSoundFile { get; private set; }

		public static ConfigEntry<string> CustomFalseStartSoundFile { get; private set; }

		public static ConfigEntry<float> StartingAreaOffset { get; private set; }

		public static ConfigEntry<float> StartingAreaDepth { get; private set; }

		public static ConfigEntry<float> StartingAreaSidePadding { get; private set; }

		public static ConfigEntry<bool> RequireStartingArea { get; private set; }

		public static ConfigEntry<bool> DisqualifyOnFalseStart { get; private set; }

		public static ConfigEntry<float> AutoDetectTorchPairMinDistance { get; private set; }

		public static ConfigEntry<float> AutoDetectTorchPairMaxDistance { get; private set; }

		public static ConfigEntry<float> AutoDetectSearchRadius { get; private set; }

		public static ConfigEntry<float> AutoDetectNearestSearchRadius { get; private set; }

		public static ConfigEntry<float> EndpointSnapRadius { get; private set; }

		public static ConfigEntry<float> EndpointSnapAngle { get; private set; }

		public static ConfigEntry<KeyCode> RegisterCheckpointHotkey { get; private set; }

		public static ConfigEntry<KeyCode> AutoDetectNearestCheckpointHotkey { get; private set; }

		public static ConfigEntry<bool> NormalizeRunSkill { get; private set; }

		public static ConfigEntry<float> NormalizedRunSkillLevel { get; private set; }

		public static ConfigEntry<bool> EnableGearCheck { get; private set; }

		public static ConfigEntry<bool> DisqualifyOnGearViolation { get; private set; }

		public static ConfigEntry<int> RequiredAntiStingCount { get; private set; }

		public static ConfigEntry<int> RequiredRatatoskCount { get; private set; }

		public static ConfigEntry<float> TriggerSweepStepDistance { get; private set; }

		public static ConfigEntry<string> AllowedHandItems { get; private set; }

		public static ConfigEntry<bool> MountainBiomeUnlocksFeatherCape { get; private set; }

		public static ConfigFile ConfigFile { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			ConfigFile = config;
			EnableBroadcasts = config.Bind<bool>("General", "EnableBroadcasts", true, "Broadcast race events to all players in chat.");
			EnableHud = config.Bind<bool>("General", "EnableHud", true, "Show live race overlay with time and place.");
			EnableLiveStandings = config.Bind<bool>("General", "EnableLiveStandings", true, "Show a live ranking panel during races.");
			EnableRaceSounds = config.Bind<bool>("Audio", "EnableRaceSounds", true, "Play local sounds for race start, checkpoints, and 1st-place finishes.");
			RaceSoundVolume = config.Bind<float>("Audio", "RaceSoundVolume", 1f, "Volume multiplier for RunningMan race sounds.");
			RaceStartSounds = config.Bind<string>("Audio", "RaceStartSounds", "sfx_offering,sfx_demister_start", "Comma-separated Valheim SFX prefab names played when the race starts (GO!).");
			CheckpointSound = config.Bind<string>("Audio", "CheckpointSound", "sfx_archery_target_hit", "Valheim SFX prefab name played when you reach a checkpoint.");
			FirstPlaceFinishSounds = config.Bind<string>("Audio", "FirstPlaceFinishSounds", "sfx_coins_placed,sfx_coins_placed,sfx_boar_love", "Comma-separated Valheim SFX prefab names played when you finish in 1st place (fallback).");
			UseCustomRaceSounds = config.Bind<bool>("Audio", "UseCustomRaceSounds", true, "Prefer custom audio files from the plugin Sounds folder when present.");
			CustomSoundsFolder = config.Bind<string>("Audio", "CustomSoundsFolder", "Sounds", "Subfolder next to RunningMan.dll (…/BepInEx/plugins/RunningMan/Sounds).");
			CustomStartSoundFile = config.Bind<string>("Audio", "CustomStartSoundFile", "start.mp3", "Race start sound file name (mp3, wav, or ogg).");
			CustomCheckpointSoundFile = config.Bind<string>("Audio", "CustomCheckpointSoundFile", "checkpoint.mp3", "Checkpoint sound file name (mp3, wav, or ogg).");
			CustomFinishSoundFile = config.Bind<string>("Audio", "CustomFinishSoundFile", "finish.mp3", "1st-place finish sound file name (mp3, wav, or ogg).");
			CustomCountdownSoundFile = config.Bind<string>("Audio", "CustomCountdownSoundFile", "countdown.mp3", "Countdown voice file played when an admin starts the race (e.g. 5-4-3-2-1).");
			CustomFalseStartSoundFile = config.Bind<string>("Audio", "CustomFalseStartSoundFile", "false_start.mp3", "Played only for a player who false-starts (stops their countdown audio).");
			LiveStandingsLimit = config.Bind<int>("General", "LiveStandingsLimit", 6, "Maximum runners shown in the live ranking panel.");
			DebugMode = config.Bind<bool>("General", "DebugMode", false, "Show start/finish/checkpoint gate markers in the world (synced from server).");
			GuiHotkey = config.Bind<KeyCode>("General", "GuiHotkey", (KeyCode)287, "Hotkey to open the RunningMan panel.");
			RegisterCheckpointHotkey = config.Bind<KeyCode>("General", "RegisterCheckpointHotkey", (KeyCode)288, "Hotkey to register a checkpoint at your position (admin). None disables.");
			AutoDetectNearestCheckpointHotkey = config.Bind<KeyCode>("General", "AutoDetectNearestCheckpointHotkey", (KeyCode)289, "Hotkey to auto-detect 1 checkpoint from the two nearest Standing Iron Torches (admin). None disables.");
			NormalizeRunSkill = config.Bind<bool>("General", "NormalizeRunSkill", true, "Clamp every runner's Run skill to NormalizedRunSkillLevel during a race.");
			NormalizedRunSkillLevel = config.Bind<float>("General", "NormalizedRunSkillLevel", 50f, "Run skill level used for all participants while a race is active (0-100).");
			CountdownSeconds = config.Bind<int>("General", "CountdownSeconds", 5, "Seconds counted down before a registered race starts.");
			RequireRegistration = config.Bind<bool>("General", "RequireRegistration", true, "Only registered participants can race after the countdown.");
			StartTriggerDistance = config.Bind<float>("Detection", "StartTriggerDistance", 6f, "Maximum horizontal distance from the start gate line to trigger a start.");
			RequireStartingArea = config.Bind<bool>("Detection", "RequireStartingArea", true, "Block countdown unless every registered runner is inside the starting grid.");
			StartingAreaOffset = config.Bind<float>("Detection", "StartingAreaOffset", 1f, "How far before the start line (meters) the starting grid is centered.");
			StartingAreaDepth = config.Bind<float>("Detection", "StartingAreaDepth", 4f, "Depth of the starting grid along the approach (meters).");
			StartingAreaSidePadding = config.Bind<float>("Detection", "StartingAreaSidePadding", 1.5f, "Extra width beyond the start gate endpoints for the starting grid.");
			DisqualifyOnFalseStart = config.Bind<bool>("Detection", "DisqualifyOnFalseStart", true, "Remove a runner from the event if they cross the start line during countdown.");
			CheckpointDistance = config.Bind<float>("Detection", "CheckpointDistance", 2.5f, "Maximum horizontal distance from a checkpoint gate line to register passage.");
			GateEndPadding = config.Bind<float>("Detection", "GateEndPadding", 0f, "How far past gate endpoints (meters) still counts as inside. 0 = must pass between A and B.");
			CheckpointVerticalDistance = config.Bind<float>("Detection", "CheckpointVerticalDistance", 15f, "Maximum vertical distance above/below a checkpoint gate line to register passage.");
			GateVerticalDistance = config.Bind<float>("Detection", "GateVerticalDistance", 8f, "Maximum vertical distance above/below start/finish gate lines.");
			FinishTriggerDistance = config.Bind<float>("Detection", "FinishTriggerDistance", 8f, "Maximum horizontal distance from the finish gate line to trigger completion.");
			UpdateInterval = config.Bind<float>("Detection", "UpdateInterval", 0.05f, "Seconds between race position checks (lower = better for fast runners).");
			TriggerSweepStepDistance = config.Bind<float>("Detection", "TriggerSweepStepDistance", 1.5f, "Sub-step size (meters) when checking movement between ticks so fast runners do not skip gates.");
			GateRegistrationWidth = config.Bind<float>("Detection", "GateRegistrationWidth", 6f, "Default gate width when registering from player position.");
			CountdownFontSize = config.Bind<int>("General", "CountdownFontSize", 72, "Font size for the on-screen race countdown.");
			CheckpointHudFontSize = config.Bind<int>("General", "CheckpointHudFontSize", 11, "Font size for the checkpoint progress line on the race HUD.");
			SavePath = config.Bind<string>("Storage", "SavePath", "RunningMan/", "Subfolder under BepInEx/config/ for race JSON files.");
			ExportFormat = config.Bind<string>("Storage", "ExportFormat", "json", "Export format identifier.");
			LeaderboardLimit = config.Bind<int>("Storage", "LeaderboardLimit", 10, "Number of entries shown in /run worldrecords.");
			WorldRecordsLimit = config.Bind<int>("Storage", "WorldRecordsLimit", 5, "Number of world record entries shown on the HUD and WR bulletin Signs (default 5).");
			AutoDetectTorchPairMinDistance = config.Bind<float>("AutoDetect", "TorchPairMinDistance", 3f, "Minimum spacing between paired Standing Iron Torches.");
			AutoDetectTorchPairMaxDistance = config.Bind<float>("AutoDetect", "TorchPairMaxDistance", 25f, "Maximum spacing between paired Standing Iron Torches.");
			AutoDetectSearchRadius = config.Bind<float>("AutoDetect", "SearchRadius", 500f, "Search radius for auto-detection commands.");
			AutoDetectNearestSearchRadius = config.Bind<float>("AutoDetect", "NearestSearchRadius", 40f, "Search radius when auto-detecting a single checkpoint from the two nearest torches.");
			EndpointSnapRadius = config.Bind<float>("AutoDetect", "EndpointSnapRadius", 14f, "How far endpoint edit mode looks for snap targets (Standing Iron Torches).");
			EndpointSnapAngle = config.Bind<float>("AutoDetect", "EndpointSnapAngle", 22f, "Max look angle (degrees) for endpoint snap-to-torch.");
			EnableGearCheck = config.Bind<bool>("GearCheck", "EnableGearCheck", true, "Validate marathon loadout at start and during the race.");
			DisqualifyOnGearViolation = config.Bind<bool>("GearCheck", "DisqualifyOnViolation", true, "Remove runners who equip non-approved gear mid-race.");
			RequiredAntiStingCount = config.Bind<int>("GearCheck", "RequiredAntiStingCount", 1, "Anti-Sting Concoctions (MeadBugRepellent) required in inventory at start.");
			RequiredRatatoskCount = config.Bind<int>("GearCheck", "RequiredRatatoskCount", 2, "Tonic of Ratatosk (MeadHasty) required in inventory at start.");
			AllowedHandItems = config.Bind<string>("GearCheck", "AllowedHandItems", "", "Unused (kept for compatibility). Hand items are not restricted during races.");
			MountainBiomeUnlocksFeatherCape = config.Bind<bool>("GearCheck", "MountainBiomeUnlocksFeatherCape", false, "Unused (kept for config compatibility). Feather Cape is always allowed; Troll Hide is not.");
		}
	}
	public static class GearRules
	{
		public const string HelmetTroll = "HelmetTrollLeather";

		public const string ChestTroll = "ArmorTrollLeatherChest";

		public const string LegsTroll = "ArmorTrollLeatherLegs";

		public const string CapeFeather = "CapeFeather";

		public const string AntiSting = "MeadBugRepellent";

		public const string TonicRatatosk = "MeadHasty";

		public const string Salad = "Salad";

		public const string BloodPudding = "BloodPudding";

		public const string MushroomOmelette = "MushroomOmelette";
	}
	public static class GearValidator
	{
		public sealed class GearCheckResult
		{
			public bool IsValid;

			public readonly List<string> Issues = new List<string>();

			public string Format(bool multiline = true)
			{
				if (IsValid)
				{
					return "Gear check passed.";
				}
				if (multiline)
				{
					return "Gear check failed:\n- " + string.Join("\n- ", Issues);
				}
				return string.Join("; ", Issues);
			}
		}

		public static AllowedGearRules GetActiveRules()
		{
			if (ValheimUtil.IsServerAuthority())
			{
				return JsonStorage.AllowedGear ?? AllowedGearRules.CreateDefaults();
			}
			return RaceNetSync.ClientAllowedGear ?? AllowedGearRules.CreateDefaults();
		}

		public static GearCheckResult CheckStartGear(Player player)
		{
			GearCheckResult gearCheckResult = new GearCheckResult
			{
				IsValid = true
			};
			if ((Object)(object)player == (Object)null || !ModConfig.EnableGearCheck.Value)
			{
				return gearCheckResult;
			}
			AllowedGearRules activeRules = GetActiveRules();
			ValidateArmor(player, activeRules, gearCheckResult);
			ValidateConsumables(player, activeRules, gearCheckResult, atStart: true);
			gearCheckResult.IsValid = gearCheckResult.Issues.Count == 0;
			return gearCheckResult;
		}

		public static GearCheckResult CheckRuntimeGear(Player player, bool ignored = true)
		{
			GearCheckResult gearCheckResult = new GearCheckResult
			{
				IsValid = true
			};
			if ((Object)(object)player == (Object)null || !ModConfig.EnableGearCheck.Value)
			{
				return gearCheckResult;
			}
			AllowedGearRules activeRules = GetActiveRules();
			ValidateArmor(player, activeRules, gearCheckResult);
			gearCheckResult.IsValid = gearCheckResult.Issues.Count == 0;
			return gearCheckResult;
		}

		private static void ValidateArmor(Player player, AllowedGearRules rules, GearCheckResult result)
		{
			RequireSlot(((Humanoid)player).m_helmetItem, rules.Helmet, "helmet", result);
			RequireSlot(((Humanoid)player).m_chestItem, rules.Chest, "chest", result);
			RequireSlot(((Humanoid)player).m_legItem, rules.Legs, "legs", result);
			ValidateCape(((Humanoid)player).m_shoulderItem, rules.Cape, result);
		}

		private static void ValidateCape(ItemData cape, string allowedCape, GearCheckResult result)
		{
			if (cape == null)
			{
				return;
			}
			string prefabName = GetPrefabName(cape);
			if (!string.IsNullOrEmpty(prefabName))
			{
				if (string.IsNullOrWhiteSpace(allowedCape))
				{
					result.Issues.Add("Disallowed cape: " + GetDisplayName(cape) + " (no cape allowed).");
				}
				else if (!string.Equals(prefabName, allowedCape.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					result.Issues.Add("Disallowed cape: " + GetDisplayName(cape) + " (allowed: " + allowedCape.Trim() + " only, or no cape).");
				}
			}
		}

		private static void RequireSlot(ItemData item, string requiredPrefab, string slot, GearCheckResult result)
		{
			if (string.IsNullOrWhiteSpace(requiredPrefab))
			{
				if (item != null)
				{
					result.Issues.Add("Slot " + slot + " must be empty (have " + GetDisplayName(item) + ").");
				}
			}
			else if (item == null)
			{
				result.Issues.Add("Missing required " + slot + ": " + requiredPrefab.Trim() + ".");
			}
			else if (!string.Equals(GetPrefabName(item), requiredPrefab.Trim(), StringComparison.OrdinalIgnoreCase))
			{
				result.Issues.Add("Wrong " + slot + ": " + GetDisplayName(item) + " (required " + requiredPrefab.Trim() + ").");
			}
		}

		private static void ValidateConsumables(Player player, AllowedGearRules rules, GearCheckResult result, bool atStart)
		{
			if (atStart)
			{
				Inventory inventory = ((Humanoid)player).GetInventory();
				if (inventory == null)
				{
					result.Issues.Add("Could not read inventory.");
					return;
				}
				RequireConsumableCount(inventory, rules.AntiStingPrefab, rules.RequiredAntiSting, "Anti-Sting", result);
				RequireConsumableCount(inventory, rules.RatatoskPrefab, rules.RequiredRatatosk, "Tonic of Ratatosk", result);
				RequireConsumableCount(inventory, rules.SaladPrefab, rules.RequiredSalad, "Salad", result);
				RequireConsumableCount(inventory, rules.BloodPuddingPrefab, rules.RequiredBloodPudding, "Blood Pudding", result);
				RequireConsumableCount(inventory, rules.MushroomOmelettePrefab, rules.RequiredMushroomOmelette, "Mushroom Omelette", result);
			}
		}

		private static void RequireConsumableCount(Inventory inventory, string prefab, int required, string label, GearCheckResult result)
		{
			if (required > 0 && !string.IsNullOrWhiteSpace(prefab))
			{
				int num = CountPrefabInInventory(inventory, prefab.Trim());
				if (num < required)
				{
					result.Issues.Add($"Need {required} {label} ({prefab.Trim()}) (have {num}).");
				}
			}
		}

		private static int CountPrefabInInventory(Inventory inventory, string prefabName)
		{
			if (inventory == null || string.IsNullOrEmpty(prefabName))
			{
				return 0;
			}
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName);
				string text = ((!((Object)(object)itemPrefab != (Object)null)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_name);
				if (!string.IsNullOrEmpty(text))
				{
					return inventory.CountItems(text, -1, true);
				}
			}
			int num = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && string.Equals(GetPrefabName(allItem), prefabName, StringComparison.OrdinalIgnoreCase))
				{
					num += Math.Max(1, allItem.m_stack);
				}
			}
			return num;
		}

		public static string GetPrefabName(ItemData item)
		{
			if (item == null)
			{
				return string.Empty;
			}
			if ((Object)(object)item.m_dropPrefab != (Object)null)
			{
				return ((Object)item.m_dropPrefab).name;
			}
			return item.m_shared?.m_name ?? string.Empty;
		}

		private static string GetDisplayName(ItemData item)
		{
			string prefabName = GetPrefabName(item);
			if (!string.IsNullOrEmpty(prefabName))
			{
				return prefabName;
			}
			return item?.m_shared?.m_name ?? "unknown item";
		}

		public static bool IsConsumableItem(ItemData item)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			if (item?.m_shared == null)
			{
				return false;
			}
			if ((int)item.m_shared.m_itemType == 2)
			{
				return true;
			}
			if (item.m_shared.m_food > 0.01f)
			{
				return true;
			}
			return (Object)(object)item.m_shared.m_consumeStatusEffect != (Object)null;
		}

		public static bool IsAllowedRaceConsumable(ItemData item)
		{
			if (!IsConsumableItem(item))
			{
				return true;
			}
			AllowedGearRules activeRules = GetActiveRules();
			return IsAllowedRaceConsumablePrefab(GetPrefabName(item), activeRules);
		}

		public static bool IsAllowedRaceConsumablePrefab(string prefabName, AllowedGearRules rules = null)
		{
			if (string.IsNullOrWhiteSpace(prefabName))
			{
				return false;
			}
			if (rules == null)
			{
				rules = GetActiveRules();
			}
			if (!string.Equals(prefabName, rules.AntiStingPrefab, StringComparison.OrdinalIgnoreCase) && !string.Equals(prefabName, rules.RatatoskPrefab, StringComparison.OrdinalIgnoreCase) && !string.Equals(prefabName, rules.SaladPrefab, StringComparison.OrdinalIgnoreCase) && !string.Equals(prefabName, rules.BloodPuddingPrefab, StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(prefabName, rules.MushroomOmelettePrefab, StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static bool IsFoodItem(ItemData item)
		{
			if (item?.m_shared != null)
			{
				return item.m_shared.m_food > 0.01f;
			}
			return false;
		}

		public static string FormatRequiredLoadout()
		{
			return GetActiveRules().Format();
		}

		public static AllowedGearRules CaptureFromPlayer(Player player)
		{
			AllowedGearRules allowedGearRules = GetActiveRules().Clone();
			if ((Object)(object)player == (Object)null)
			{
				return allowedGearRules;
			}
			allowedGearRules.Helmet = GetPrefabName(((Humanoid)player).m_helmetItem);
			allowedGearRules.Chest = GetPrefabName(((Humanoid)player).m_chestItem);
			allowedGearRules.Legs = GetPrefabName(((Humanoid)player).m_legItem);
			allowedGearRules.Cape = GetPrefabName(((Humanoid)player).m_shoulderItem);
			List<string> list = new List<string>();
			AddUniqueHandPrefab(list, GetPrefabName(((Humanoid)player).m_rightItem));
			AddUniqueHandPrefab(list, GetPrefabName(((Humanoid)player).m_leftItem));
			allowedGearRules.AllowedHandItems = string.Join(",", list);
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory != null)
			{
				if (!string.IsNullOrWhiteSpace(allowedGearRules.AntiStingPrefab))
				{
					allowedGearRules.RequiredAntiSting = CountPrefabInInventory(inventory, allowedGearRules.AntiStingPrefab);
				}
				if (!string.IsNullOrWhiteSpace(allowedGearRules.RatatoskPrefab))
				{
					allowedGearRules.RequiredRatatosk = CountPrefabInInventory(inventory, allowedGearRules.RatatoskPrefab);
				}
				if (!string.IsNullOrWhiteSpace(allowedGearRules.SaladPrefab))
				{
					allowedGearRules.RequiredSalad = CountPrefabInInventory(inventory, allowedGearRules.SaladPrefab);
				}
				if (!string.IsNullOrWhiteSpace(allowedGearRules.BloodPuddingPrefab))
				{
					allowedGearRules.RequiredBloodPudding = CountPrefabInInventory(inventory, allowedGearRules.BloodPuddingPrefab);
				}
				if (!string.IsNullOrWhiteSpace(allowedGearRules.MushroomOmelettePrefab))
				{
					allowedGearRules.RequiredMushroomOmelette = CountPrefabInInventory(inventory, allowedGearRules.MushroomOmelettePrefab);
				}
			}
			return allowedGearRules;
		}

		private static void AddUniqueHandPrefab(List<string> names, string prefab)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				return;
			}
			foreach (string name in names)
			{
				if (string.Equals(name, prefab, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
			}
			names.Add(prefab);
		}
	}
	internal enum HudPanelId
	{
		Runner,
		Standings,
		RaceBanner,
		Finish,
		Registered,
		WorldRecords
	}
	internal static class HudPanelLayout
	{
		private const float DragTitleHeight = 20f;

		private const int PanelCount = 6;

		private static readonly Rect[] Positions = (Rect[])(object)new Rect[6];

		private static readonly bool[] HasPosition = new bool[6];

		private static readonly bool[] PanelVisible = new bool[6];

		private static int _draggingId = -1;

		private static GUIStyle _dragTitleStyle;

		private static GUIStyle _closeButtonStyle;

		private static bool _loaded;

		public static float TitleBarHeight => 20f;

		public static HudPanelId Normalize(HudPanelId id)
		{
			if (id != HudPanelId.Finish)
			{
				return id;
			}
			return HudPanelId.RaceBanner;
		}

		public static bool ShouldDrawPanel(HudPanelId id, bool layoutMode = false)
		{
			EnsureLoaded();
			return PanelVisible[(int)Normalize(id)];
		}

		public static bool IsPanelVisible(HudPanelId id)
		{
			EnsureLoaded();
			return PanelVisible[(int)Normalize(id)];
		}

		public static void SetPanelVisible(HudPanelId id, bool visible)
		{
			EnsureLoaded();
			int num = (int)Normalize(id);
			PanelVisible[num] = visible;
			PlayerPrefs.SetInt(PrefKey(num, "Visible"), visible ? 1 : 0);
			PlayerPrefs.Save();
		}

		public static void ShowAllPanels()
		{
			for (int i = 0; i < 6; i++)
			{
				if (i != 3)
				{
					SetPanelVisible((HudPanelId)i, visible: true);
				}
			}
		}

		public static string GetPanelTitle(HudPanelId id)
		{
			return Normalize(id) switch
			{
				HudPanelId.Runner => "Race stats", 
				HudPanelId.Standings => "Live standings", 
				HudPanelId.RaceBanner => "Race banner", 
				HudPanelId.Registered => "Registration", 
				_ => id.ToString(), 
			};
		}

		public static IEnumerable<HudPanelId> ToggleablePanels()
		{
			yield return HudPanelId.Runner;
			yield return HudPanelId.Standings;
			yield return HudPanelId.RaceBanner;
			yield return HudPanelId.Registered;
		}

		public static bool Resolve(HudPanelId id, Rect defaultRect, bool draggable, string title, out Rect panelRect)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: 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_005d: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected I4, but got Unknown
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: 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_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoaded();
			id = Normalize(id);
			int num = (int)id;
			Rect val = (HasPosition[num] ? Positions[num] : defaultRect);
			((Rect)(ref val)).width = ((Rect)(ref defaultRect)).width;
			((Rect)(ref val)).height = ((Rect)(ref defaultRect)).height + (draggable ? 20f : 0f);
			if (!draggable)
			{
				Positions[num] = val;
				panelRect = val;
				return true;
			}
			EnsureDragStyle();
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(((Rect)(ref val)).x, ((Rect)(ref val)).y, ((Rect)(ref val)).width, 20f);
			float num2 = 22f;
			GUI.Label(new Rect(((Rect)(ref val2)).x + 8f, ((Rect)(ref val2)).y, ((Rect)(ref val2)).width - num2 - 10f, ((Rect)(ref val2)).height), title + "  (drag)", _dragTitleStyle);
			Rect val3 = default(Rect);
			((Rect)(ref val3))..ctor(((Rect)(ref val2)).xMax - num2 - 2f, ((Rect)(ref val2)).y + 1f, num2, ((Rect)(ref val2)).height - 2f);
			if (GUI.Button(val3, "X", _closeButtonStyle))
			{
				SetPanelVisible(id, visible: false);
				panelRect = val;
				return false;
			}
			Event current = Event.current;
			EventType type = current.type;
			switch ((int)type)
			{
			case 0:
				if (((Rect)(ref val2)).Contains(current.mousePosition) && !((Rect)(ref val3)).Contains(current.mousePosition))
				{
					_draggingId = num;
					current.Use();
				}
				break;
			case 3:
				if (_draggingId == num)
				{
					((Rect)(ref val)).x = ((Rect)(ref val)).x + current.delta.x;
					((Rect)(ref val)).y = ((Rect)(ref val)).y + current.delta.y;
					current.Use();
				}
				break;
			case 1:
				if (_draggingId == num)
				{
					_draggingId = -1;
					SavePosition(num, val);
					current.Use();
				}
				break;
			}
			Positions[num] = val;
			HasPosition[num] = true;
			panelRect = val;
			return true;
		}

		public static Rect ContentRect(Rect panelRect, bool draggable)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			if (!draggable)
			{
				return panelRect;
			}
			return new Rect(((Rect)(ref panelRect)).x, ((Rect)(ref panelRect)).y + 20f, ((Rect)(ref panelRect)).width, ((Rect)(ref panelRect)).height - 20f);
		}

		public static void ResetAll()
		{
			for (int i = 0; i < HasPosition.Length; i++)
			{
				HasPosition[i] = false;
				PanelVisible[i] = true;
				PlayerPrefs.DeleteKey(PrefKey(i, "X"));
				PlayerPrefs.DeleteKey(PrefKey(i, "Y"));
				PlayerPrefs.DeleteKey(PrefKey(i, "Visible"));
			}
			PlayerPrefs.Save();
		}

		private static void EnsureLoaded()
		{
			//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)
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			for (int i = 0; i < HasPosition.Length; i++)
			{
				PanelVisible[i] = !PlayerPrefs.HasKey(PrefKey(i, "Visible")) || PlayerPrefs.GetInt(PrefKey(i, "Visible")) == 1;
				if (PlayerPrefs.HasKey(PrefKey(i, "X")))
				{
					Positions[i] = new Rect(PlayerPrefs.GetFloat(PrefKey(i, "X")), PlayerPrefs.GetFloat(PrefKey(i, "Y")), 0f, 0f);
					HasPosition[i] = true;
				}
			}
		}

		private static void SavePosition(int index, Rect rect)
		{
			PlayerPrefs.SetFloat(PrefKey(index, "X"), ((Rect)(ref rect)).x);
			PlayerPrefs.SetFloat(PrefKey(index, "Y"), ((Rect)(ref rect)).y);
			PlayerPrefs.Save();
		}

		private static string PrefKey(int index, string suffix)
		{
			return $"RunningMan.Hud.{index}.{suffix}";
		}

		private static void EnsureDragStyle()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_009b: 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_00aa: Expected O, but got Unknown
			//IL_00af: Expected O, but got Unknown
			if (_dragTitleStyle == null)
			{
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = 12,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)3,
					clipping = (TextClipping)0,
					padding = new RectOffset(0, 0, 2, 2)
				};
				val.normal.textColor = new Color(0.75f, 0.85f, 1f);
				_dragTitleStyle = val;
				_closeButtonStyle = new GUIStyle(GUI.skin.button)
				{
					fontSize = 11,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4,
					padding = new RectOffset(0, 0, 0, 0),
					margin = new RectOffset(0, 0, 0, 0)
				};
			}
		}
	}
	public static class Leaderboard
	{
		public static void RecordCompletedRun(RaceRecord record)
		{
			RaceDatabase database = JsonStorage.Database;
			if (string.IsNullOrEmpty(record.TrackId))
			{
				record.TrackId = TrackIdentity.GetId(JsonStorage.Track);
			}
			database.Runs.Add(record);
			string playerId = record.PlayerId;
			database.LastRuns[playerId] = record;
			if (!database.PersonalBests.TryGetValue(playerId, out var value) || record.TotalTimeMs < value.TotalTimeMs)
			{
				database.PersonalBests[playerId] = record;
			}
			JsonStorage.SaveDatabase();
		}

		public static RaceRecord GetPersonalBest(long playerId)
		{
			JsonStorage.Database.PersonalBests.TryGetValue(playerId.ToString(), out var value);
			return value;
		}

		public static RaceRecord GetLastRun(long playerId)
		{
			JsonStorage.Database.LastRuns.TryGetValue(playerId.ToString(), out var value);
			return value;
		}

		public static IReadOnlyList<RaceRecord> GetWorldRecords(TrackConfig track, int limit)
		{
			string trackId = TrackIdentity.GetId(track);
			if (string.IsNullOrEmpty(trackId))
			{
				return new List<RaceRecord>();
			}
			return (from record in JsonStorage.Database.Runs
				where string.Equals(record.TrackId, trackId, StringComparison.OrdinalIgnoreCase)
				orderby record.TotalTimeMs
				select record).Take(limit).ToList();
		}

		public static int ClearRecordsForTrackId(string trackId)
		{
			if (string.IsNullOrEmpty(trackId))
			{
				return 0;
			}
			RaceDatabase database = JsonStorage.Database;
			int result = database.Runs.RemoveAll((RaceRecord record) => string.Equals(record.TrackId, trackId, StringComparison.OrdinalIgnoreCase));
			PruneDerivedRecords(database, trackId);
			JsonStorage.SaveDatabase();
			return result;
		}

		public static int ClearWorldRecords(TrackConfig track)
		{
			return ClearRecordsForTrackId(TrackIdentity.GetId(track));
		}

		public static int ClearAllWorldRecords()
		{
			RaceDatabase database = JsonStorage.Database;
			int count = database.Runs.Count;
			database.Runs.Clear();
			database.PersonalBests.Clear();
			database.LastRuns.Clear();
			JsonStorage.SaveDatabase();
			return count;
		}

		public static string FormatWorldRecords(TrackConfig track, int limit)
		{
			string displayName = TrackIdentity.GetDisplayName(track);
			IReadOnlyList<RaceRecord> worldRecords = GetWorldRecords(track, limit);
			if (worldRecords.Count == 0)
			{
				return "No world records for " + displayName + " yet.";
			}
			List<string> list = new List<string> { displayName + " — World Records:" };
			for (int i = 0; i < worldRecords.Count; i++)
			{
				RaceRecord raceRecord = worldRecords[i];
				list.Add($"{i + 1}. {raceRecord.Player} - {raceRecord.TotalTime} ({raceRecord.Date})");
			}
			return string.Join("\n", list);
		}

		public static List<WorldRecordEntry> BuildWorldRecordEntries(TrackConfig track, int limit)
		{
			List<WorldRecordEntry> list = new List<WorldRecordEntry>();
			IReadOnlyList<RaceRecord> worldRecords = GetWorldRecords(track, limit);
			for (int i = 0; i < worldRecords.Count; i++)
			{
				RaceRecord raceRecord = worldRecords[i];
				list.Add(new WorldRecordEntry
				{
					Place = i + 1,
					PlayerName = raceRecord.Player,
					Time = raceRecord.TotalTime,
					TimeMs = raceRecord.TotalTimeMs,
					Date = raceRecord.Date
				});
			}
			return list;
		}

		public static string FormatPersonalBest(RaceRecord record)
		{
			if (record == null)
			{
				return "No personal best recorded yet.";
			}
			return record.Player + " PB: " + record.TotalTime + " on " + record.Date;
		}

		public static string FormatLastRun(RaceRecord record)
		{
			if (record == null)
			{
				return "No completed runs recorded yet.";
			}
			return record.Player + " last run: " + record.TotalTime + " on " + record.Date;
		}

		private static void PruneDerivedRecords(RaceDatabase database, string clearedTrackId)
		{
			Dictionary<string, List<RaceRecord>> dictionary = (from record in database.Runs
				group record by record.PlayerId).ToDictionary((IGrouping<string, RaceRecord> group) => group.Key, (IGrouping<string, RaceRecord> group) => group.OrderBy((RaceRecord record) => record.TotalTimeMs).ToList());
			Dictionary<string, RaceRecord> dictionary2 = new Dictionary<string, RaceRecord>(StringComparer.Ordinal);
			Dictionary<string, RaceRecord> dictionary3 = new Dictionary<string, RaceRecord>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, List<RaceRecord>> item in dictionary)
			{
				if (item.Value.Count != 0)
				{
					dictionary2[item.Key] = item.Value[0];
					dictionary3[item.Key] = item.Value[item.Value.Count - 1];
				}
			}
			database.PersonalBests = dictionary2;
			database.LastRuns = dictionary3;
		}
	}
	public sealed class RaceClientGearMonitor : MonoBehaviour
	{
		private float _nextCheckTime;

		private bool _reportedViolation;

		private void Update()
		{
			if (!ModConfig.EnableGearCheck.Value || (Object)(object)Player.m_localPlayer == (Object)null || Time.time < _nextCheckTime)
			{
				return;
			}
			_nextCheckTime = Time.time + Mathf.Max(0.25f, ModConfig.UpdateInterval.Value);
			RunnerSnapshot localRunner = RaceNetSync.GetLocalRunner();
			if (localRunner == null || localRunner.StartUtcTicks <= 0 || localRunner.Finished || localRunner.Disqualified)
			{
				_reportedViolation = false;
			}
			else
			{
				if (RaceNetSync.ClientState == null || RaceNetSync.ClientState.Phase != 3 || _reportedViolation)
				{
					return;
				}
				GearValidator.GearCheckResult gearCheckResult = GearValidator.CheckRuntimeGear(Player.m_localPlayer);
				if (!gearCheckResult.IsValid)
				{
					_reportedViolation = true;
					string text = ((gearCheckResult.Issues.Count > 0) ? gearCheckResult.Issues[0] : "illegal gear change");
					localRunner.Disqualified = true;
					localRunner.DisqualifiedReason = text;
					if (localRunner.FinishTimeMs <= 0)
					{
						localRunner.FinishTimeMs = (long)(DateTime.UtcNow - new DateTime(localRunner.StartUtcTicks, DateTimeKind.Utc)).TotalMilliseconds;
					}
					RaceGui.ShowYellowHud("DISQUALIFIED: " + text);
					RaceGui.ShowInfoPanel("Disqualified", text, 12f);
					RaceNetSync.SendGearViolation(text);
				}
			}
		}
	}
	public sealed class RaceClientPositionReporter : MonoBehaviour
	{
		private float _nextSendTime;

		private void Update()
		{
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: 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)
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || Time.time < _nextSendTime)
			{
				return;
			}
			RaceStateSnapshot clientState = RaceNetSync.ClientState;
			if (clientState == null || clientState.Phase != 3)
			{
				_nextSendTime = Time.time + 0.5f;
				return;
			}
			RunnerSnapshot localRunner = RaceNetSync.GetLocalRunner();
			if (localRunner == null || localRunner.StartUtcTicks <= 0 || localRunner.Finished || localRunner.Disqualified)
			{
				_nextSendTime = Time.time + 0.5f;
				return;
			}
			_nextSendTime = Time.time + Mathf.Clamp(ModConfig.UpdateInterval.Value, 0.05f, 0.2f);
			Vector3 position = ((Component)Player.m_localPlayer).transform.position;
			RaceNetSync.SendRunnerPosition(Player.m_localPlayer.GetPlayerID(), position);
		}
	}
	public enum RaceSoundSlot
	{
		Start,
		Checkpoint,
		Finish,
		Countdown,
		FalseStart
	}
	public static class RaceCustomAudio
	{
		private static readonly Dictionary<RaceSoundSlot, AudioClip> Clips = new Dictionary<RaceSoundSlot, AudioClip>();

		private static readonly HashSet<RaceSoundSlot> LoadAttempted = new HashSet<RaceSoundSlot>();

		private static readonly HashSet<RaceSoundSlot> Loading = new HashSet<RaceSoundSlot>();

		private static MonoBehaviour _host;

		private static AudioSource _oneShotSource;

		private static AudioSource _streamSource;

		private static bool _folderEnsured;

		public static void Initialize(MonoBehaviour host)
		{
			_host = host;
			_oneShotSource = ((Component)host).gameObject.AddComponent<AudioSource>();
			_oneShotSource.playOnAwake = false;
			_oneShotSource.spatialBlend = 0f;
			_oneShotSource.spatialize = false;
			_oneShotSource.bypassEffects = true;
			_oneShotSource.bypassListenerEffects = true;
			_oneShotSource.bypassReverbZones = true;
			_streamSource = ((Component)host).gameObject.AddComponent<AudioSource>();
			_streamSource.playOnAwake = false;
			_streamSource.spatialBlend = 0f;
			_streamSource.spatialize = false;
			_streamSource.loop = false;
			_streamSource.bypassEffects = true;
			_streamSource.bypassListenerEffects = true;
			_streamSource.bypassReverbZones = true;
			EnsureSoundsFolder();
		}

		public static string GetSoundsFolderPath()
		{
			string text = ModConfig.CustomSoundsFolder.Value?.Trim() ?? "Sounds";
			text = text.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
			string runningManPluginDirectory = GetRunningManPluginDirectory();
			if (Path.IsPathRooted(text))
			{
				return text;
			}
			return Path.Combine(runningManPluginDirectory, text);
		}

		private static string GetRunningManPluginDirectory()
		{
			try
			{
				string location = typeof(RunningManPlugin).Assembly.Location;
				if (!string.IsNullOrEmpty(location))
				{
					string directoryName = Path.GetDirectoryName(location);
					if (!string.IsNullOrEmpty(directoryName))
					{
						return directoryName;
					}
				}
			}
			catch
			{
			}
			return Path.Combine(Paths.PluginPath, "RunningMan");
		}

		public static void PreloadAll()
		{
			if (!((Object)(object)_host == (Object)null) && ModConfig.UseCustomRaceSounds.Value)
			{
				_host.StartCoroutine(PreloadRoutine());
			}
		}

		public static bool TryPlay(RaceSoundSlot slot, float volume, Action playFallback)
		{
			if (!ModConfig.UseCustomRaceSounds.Value)
			{
				return false;
			}
			if (Clips.TryGetValue(slot, out var value) && (Object)(object)value != (Object)null)
			{
				PlayClip(value, volume);
				return true;
			}
			if (LoadAttempted.Contains(slot))
			{
				return false;
			}
			if ((Object)(object)_host == (Object)null)
			{
				return false;
			}
			if (!HasCustomFile(slot))
			{
				LoadAttempted.Add(slot);
				return false;
			}
			_host.StartCoroutine(LoadAndPlay(slot, volume, playFallback));
			return true;
		}

		public static bool HasCustomFile(RaceSoundSlot slot)
		{
			string path;
			return TryResolveFilePath(GetConfiguredFileName(slot), out path);
		}

		private static IEnumerator PreloadRoutine()
		{
			yield return LoadClip(RaceSoundSlot.Start);
			yield return LoadClip(RaceSoundSlot.Checkpoint);
			yield return LoadClip(RaceSoundSlot.Finish);
			yield return LoadClip(RaceSoundSlot.Countdown);
			yield return LoadClip(RaceSoundSlot.FalseStart);
		}

		private static IEnumerator LoadAndPlay(RaceSoundSlot slot, float volume, Action playFallback)
		{
			if (!Loading.Contains(slot))
			{
				Loading.Add(slot);
				yield return LoadClip(slot);
				Loading.Remove(slot);
				if (Clips.TryGetValue(slot, out var value) && (Object)(object)value != (Object)null)
				{
					PlayClip(value, volume);
				}
				else
				{
					playFallback?.Invoke();
				}
			}
		}

		private static IEnumerator LoadClip(RaceSoundSlot slot)
		{
			if (LoadAttempted.Contains(slot))
			{
				yield break;
			}
			LoadAttempted.Add(slot);
			string configuredFileName = GetConfiguredFileName(slot);
			if (!TryResolveFilePath(configuredFileName, out var path))
			{
				RunningManPlugin.Log.LogInfo((object)$"RunningMan: no custom sound for {slot} (expected {configuredFileName} in {GetSoundsFolderPath()}).");
				yield break;
			}
			string text = "file:///" + path.Replace("\\", "/");
			AudioType audioType = GetAudioType(path);
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(text, audioType);
			try
			{
				yield return request.SendWebRequest();
				if (!string.IsNullOrEmpty(request.error))
				{
					RunningManPlugin.Log.LogWarning((object)("RunningMan: failed to load custom sound '" + path + "': " + request.error));
					yield break;
				}
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				if ((Object)(object)content == (Object)null)
				{
					RunningManPlugin.Log.LogWarning((object)("RunningMan: custom sound '" + path + "' loaded empty."));
					yield break;
				}
				((Object)content).name = Path.GetFileName(path);
				Clips[slot] = content;
				RunningManPlugin.Log.LogInfo((object)$"RunningMan: loaded custom sound '{((Object)content).name}' for {slot}.");
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public static bool TryPlayStream(RaceSoundSlot slot, float volume)
		{
			if (!ModConfig.UseCustomRaceSounds.Value || (Object)(object)_streamSource == (Object)null)
			{
				return false;
			}
			if (Clips.TryGetValue(slot, out var value) && (Object)(object)value != (Object)null)
			{
				PlayStream(value, volume);
				return true;
			}
			if (LoadAttempted.Contains(slot) || (Object)(object)_host == (Object)null || !HasCustomFile(slot))
			{
				return false;
			}
			_host.StartCoroutine(LoadAndPlayStream(slot, volume));
			return true;
		}

		public static void StopStream()
		{
			if ((Object)(object)_streamSource != (Object)null && _streamSource.isPlaying)
			{
				_streamSource.Stop();
			}
		}

		private static IEnumerator LoadAndPlayStream(RaceSoundSlot slot, float volume)
		{
			if (!Loading.Contains(slot))
			{
				Loading.Add(slot);
				yield return LoadClip(slot);
				Loading.Remove(slot);
				if (Clips.TryGetValue(slot, out var value) && (Object)(object)value != (Object)null)
				{
					PlayStream(value, volume);
				}
			}
		}

		private static void PlayStream(AudioClip clip, float volume)
		{
			if (!((Object)(object)_streamSource == (Object)null) && !((Object)(object)clip == (Object)null))
			{
				_streamSource.Stop();
				_streamSource.clip = clip;
				_streamSource.volume = Mathf.Clamp(volume, 0f, 2f);
				_streamSource.Play();
			}
		}

		private static void PlayClip(AudioClip clip, float volume)
		{
			if (!((Object)(object)_oneShotSource == (Object)null) && !((Object)(object)clip == (Object)null))
			{
				_oneShotSource.PlayOneShot(clip, Mathf.Clamp(volume, 0f, 2f));
			}
		}

		private static string GetConfiguredFileName(RaceSoundSlot slot)
		{
			return slot switch
			{
				RaceSoundSlot.Start => ModConfig.CustomStartSoundFile.Value, 
				RaceSoundSlot.Checkpoint => ModConfig.CustomCheckpointSoundFile.Value, 
				RaceSoundSlot.Finish => ModConfig.CustomFinishSoundFile.Value, 
				RaceSoundSlot.Countdown => ModConfig.CustomCountdownSoundFile.Value, 
				RaceSoundSlot.FalseStart => ModConfig.CustomFalseStartSoundFile.Value, 
				_ => string.Empty, 
			};
		}

		private static bool TryResolveFilePath(string configuredName, out string path)
		{
			path = null;
			if (string.IsNullOrWhiteSpace(configuredName))
			{
				return false;
			}
			string soundsFolderPath = GetSoundsFolderPath();
			string text = configuredName.Trim();
			string text2 = Path.Combine(soundsFolderPath, text);
			if (File.Exists(text2))
			{
				path = text2;
				return true;
			}
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
			string[] array = new string[3] { ".mp3", ".wav", ".ogg" };
			foreach (string text3 in array)
			{
				text2 = Path.Combine(soundsFolderPath, fileNameWithoutExtension + text3);
				if (File.Exists(text2))
				{
					path = text2;
					return true;
				}
			}
			return false;
		}

		private static AudioType GetAudioType(string path)
		{
			string text = Path.GetExtension(path)?.ToLowerInvariant();
			if (!(text == ".ogg"))
			{
				if (text == ".wav")
				{
					return (AudioType)20;
				}
				return (AudioType)13;
			}
			return (AudioType)14;
		}

		private static void EnsureSoundsFolder()
		{
			if (_folderEnsured)
			{
				return;
			}
			_folderEnsured = true;
			try
			{
				string soundsFolderPath = GetSoundsFolderPath();
				Directory.CreateDirectory(soundsFolderPath);
				RunningManPlugin.Log.LogInfo((object)("RunningMan custom sounds folder: " + soundsFolderPath + " (add start.mp3, checkpoint.mp3, finish.mp3, countdown.mp3, false_start.mp3)"));
			}
			catch (Exception ex)
			{
				RunningManPlugin.Log.LogWarning((object)("RunningMan: could not create sounds folder: " + ex.Message));
			}
		}
	}
	public sealed class RaceDebugDrawer : MonoBehaviour
	{
		private static Texture2D _lineTexture;

		private static GUIStyle _labelStyle;

		private static readonly Color CheckpointColor = new Color(1f, 0.92f, 0.16f);

		private void OnEnable()
		{
			RaceNetSync.TrackUpdated += MarkDirty;
			RaceNetSync.StateUpdated += MarkDirty;
		}

		private void OnDisable()
		{
			RaceNetSync.TrackUpdated -= MarkDirty;
			RaceNetSync.StateUpdated -= MarkDirty;
		}

		private static void MarkDirty()
		{
		}

		private void OnGUI()
		{
			//IL_000d: Unkno