Decompiled source of GRACEPLUS v1.4.6

GRACEPlus.dll

Decompiled 2 weeks 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 BepInEx;
using BepInEx.Configuration;
using BombRushMP.Common;
using BombRushMP.Common.Packets;
using BombRushMP.Plugin;
using BombRushMP.Plugin.Gamemodes;
using CommonAPI;
using CommonAPI.Phone;
using GRACEPlus.Patches;
using GRACEPlus.Phone;
using HarmonyLib;
using Reptile;
using Reptile.Phone;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
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("GRACEPlus")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("GRACEPlus")]
[assembly: AssemblyTitle("GRACEPlus")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace GRACEPlus
{
	public enum TimerUILocation
	{
		Left,
		Center,
		Right
	}
	[BepInPlugin("com.snail.graceplus", "GRACEPlus", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class GRACEPlusPlugin : BaseUnityPlugin
	{
		private static Harmony _harmony;

		private static string _recordsFilePath;

		private static string _teamRecordsFilePath;

		private static string _customStatsFilePath;

		private static string _mapStatsFolder;

		private static Dictionary<int, double> _bestTimes = new Dictionary<int, double>();

		private static Dictionary<int, double> _bestTeamTimes = new Dictionary<int, double>();

		private static int _customWins = 0;

		private static int _customLosses = 0;

		private static int _customGamesPlayed = 0;

		private static int _customSoloWins = 0;

		private static int _customTeamWins = 0;

		private static int _customTeamGamesPlayed = 0;

		private static bool _gameCancelled = false;

		private static int _maxPossibleScore = 0;

		public static ConfigEntry<string> FirstWinMessage;

		public static ConfigEntry<string> NewRecordMessage;

		public static ConfigEntry<string> WinMessage;

		public static ConfigEntry<string> LoseMessage;

		public static ConfigEntry<string> FirstWinColor;

		public static ConfigEntry<string> NewRecordColor;

		public static ConfigEntry<string> WinColor;

		public static ConfigEntry<string> LoseColor;

		public static ConfigEntry<bool> SaveAllMatchResults;

		public static ConfigEntry<bool> TrackMapStats;

		public static ConfigEntry<bool> HideChatDuringRace;

		public static ConfigEntry<bool> HideLobbyUIDuringRace;

		public static ConfigEntry<bool> HideMinimapDuringRace;

		public static ConfigEntry<bool> HideTagCounterDuringRace;

		public static ConfigEntry<bool> HideRaceTimer;

		public static ConfigEntry<string> TagCounterColor;

		public static ConfigEntry<bool> UseCustomTagCounterColor;

		public static ConfigEntry<string> CountdownColor;

		public static ConfigEntry<bool> UseCustomCountdownColor;

		public static ConfigEntry<string> TimerColor;

		public static ConfigEntry<bool> UseCustomTimerColor;

		public static ConfigEntry<string> ScoreTextColor;

		public static ConfigEntry<bool> UseCustomScoreTextColor;

		public static ConfigEntry<string> WinCounterColor;

		public static ConfigEntry<bool> UseCustomWinCounterColor;

		public static ConfigEntry<bool> ShowLobbyWinCounter;

		public static ConfigEntry<TimerUILocation> TagCounterLocation;

		public static ConfigEntry<TimerUILocation> TimerLocation;

		public static ConfigEntry<string> LobbyNameText;

		public static ConfigEntry<string> LobbyNameColor;

		public static ConfigEntry<bool> UseCustomLobbyName;

		public static ConfigEntry<bool> UseCustomGraffitiIndicator;

		public static ConfigEntry<bool> CustomGraffitiOutline;

		public static ConfigEntry<string> CustomGraffitiOutlineHexColor;

		public static ConfigEntry<bool> UseConfigHexForGraffitiOutline;

		public static ConfigEntry<string> GraffitiOutlinePreset;

		public static ConfigEntry<bool> HideAllAutomatedMessages;

		public static ConfigEntry<bool> LogEvents;

		public static ConfigEntry<bool> AutoStartGRaceRounds;

		public static ConfigEntry<int> AutoStartDelaySeconds;

		internal static GRACEPlusPlugin Instance;

		private static Coroutine _autoStartCoroutine;

		private static Coroutine _quickStartCoroutine;

		private static string _debugLogFilePath;

		public static string GracePlusFolderPath { get; private set; }

		public static string ModFolder { get; private set; }

		public static string GetAppIconPath()
		{
			if (string.IsNullOrEmpty(ModFolder))
			{
				return null;
			}
			try
			{
				string path = Path.Combine(ModFolder, "backendTextures", "AppIcon");
				if (!Directory.Exists(path))
				{
					return null;
				}
				string[] files = Directory.GetFiles(path, "*.png", SearchOption.TopDirectoryOnly);
				return (files.Length != 0) ? files[0] : null;
			}
			catch
			{
				return null;
			}
		}

		private void Awake()
		{
			//IL_0633: Unknown result type (might be due to invalid IL or missing references)
			//IL_063d: Expected O, but got Unknown
			//IL_0647: Unknown result type (might be due to invalid IL or missing references)
			//IL_0651: Expected O, but got Unknown
			Instance = this;
			ModFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			GracePlusFolderPath = Path.Combine(Paths.BepInExRootPath, "GRACEPlus");
			Directory.CreateDirectory(GracePlusFolderPath);
			AppGRACEPlus.Initialize();
			FirstWinMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "FirstWinMessage", "FIRST {gamemode}WIN! GRACE Ended IN {time} Seconds on Stage {stage}!", "Message for first win on a stage. Variables: {gamemode}, {time}, {stage}");
			NewRecordMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "NewRecordMessage", "NEW {gamemode}RECORD! GRACE Ended IN {time} Seconds on Stage {stage}! (Previous: {previous}s)", "Message for new record. Variables: {gamemode}, {time}, {stage}, {previous}");
			WinMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "WinMessage", "{gamemode}GRACE Ended IN {time} Seconds! You WON! (Best: {best}s)", "Message for winning without a new record. Variables: {gamemode}, {time}, {best}");
			LoseMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "LoseMessage", "{gamemode}GRACE Ended IN {time} Seconds! You LOST!", "Message for losing a race. Variables: {gamemode}, {time}");
			FirstWinColor = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "FirstWinColor", "yellow", "Color for first win messages (red, green, blue, yellow, white)");
			NewRecordColor = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "NewRecordColor", "yellow", "Color for new record messages (red, green, blue, yellow, white)");
			WinColor = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "WinColor", "green", "Color for win messages (red, green, blue, yellow, white)");
			LoseColor = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "LoseColor", "yellow", "Color for lose messages (red, green, blue, yellow, white)");
			HideAllAutomatedMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("Messages", "HideAllAutomatedMessages", false, "When enabled, the mod will not send any automated messages to chat (win/lose/record messages, auto-start notifications). Responses to chat commands like /mapstats and /avgtime will still be shown.");
			SaveAllMatchResults = ((BaseUnityPlugin)this).Config.Bind<bool>("Features", "SaveAllMatchResults", false, "Save all match results to daily text files in the mod folder");
			TrackMapStats = ((BaseUnityPlugin)this).Config.Bind<bool>("Features", "TrackMapStats", false, "Track detailed statistics for each map including wins, losses, and multiplayer status");
			HideChatDuringRace = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "HideChatDuringRace", false, "Hide BombRushMP's chat UI during GRACE races for a cleaner screen");
			HideLobbyUIDuringRace = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "HideLobbyUIDuringRace", false, "Hide BombRushMP's lobby UI during GRACE races for a cleaner screen");
			HideMinimapDuringRace = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "HideMinimapDuringRace", false, "Hide BombRushMP's minimap during GRACE races for a cleaner screen");
			HideTagCounterDuringRace = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "HideTagCounterDuringRace", false, "Hide the graffiti spots counter during GRACE races for a cleaner screen");
			HideRaceTimer = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "HideRaceTimer", true, "When enabled (default), the BombRushMP race timer is shown. When disabled, the race timer is hidden during GRACE races.");
			TagCounterColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "TagCounterColor", "red", "Color for the graffiti spots counter (red, green, blue, yellow, white, cyan, magenta, black, gray)");
			UseCustomTagCounterColor = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomTagCounterColor", false, "Enable custom color for the graffiti spots counter during GRACE races");
			UseCustomCountdownColor = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomCountdownColor", false, "Enable custom color for the countdown timer during GRACE races");
			CountdownColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "CountdownColor", "red", "Color for the countdown timer (red, green, blue, yellow, white, cyan, magenta, black, gray)");
			UseCustomTimerColor = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomTimerColor", false, "Enable custom color for the race timer text (elapsed time) during GRACE races. Uses TimerColor in RGB.");
			TimerColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "TimerColor", "255,255,255", "Race timer text color in RGB. Format: R,G,B (e.g. 255,128,0 for orange) or R,G,B,A for opacity. Values 0-255. Only used when UseCustomTimerColor is enabled.");
			UseCustomScoreTextColor = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomScoreTextColor", false, "Enable custom color for score text in lobby UI");
			ScoreTextColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "ScoreTextColor", "red", "Color for score text in lobby UI (red, green, blue, yellow, white, cyan, magenta, black, gray)");
			UseCustomWinCounterColor = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomWinCounterColor", false, "Enable custom color for the win counter (the number in parentheses) in lobby UI. Uses WinCounterColor in RGB.");
			WinCounterColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "WinCounterColor", "255,200,100", "Win counter color in RGB (the number shown next to score, e.g. (3)). Format: R,G,B (e.g. 255,200,100 for gold). Values 0-255. Only used when UseCustomWinCounterColor is enabled.");
			ShowLobbyWinCounter = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "ShowLobbyWinCounter", true, "Show win counter (e.g. (3)) next to score in lobby UI. When false, only the score is shown.");
			TagCounterLocation = ((BaseUnityPlugin)this).Config.Bind<TimerUILocation>("UI Options", "TagCounterLocation", TimerUILocation.Center, "Screen position for the tag counter (e.g. 3/10). Options: Left, Center, Right.");
			TimerLocation = ((BaseUnityPlugin)this).Config.Bind<TimerUILocation>("UI Options", "TimerLocation", TimerUILocation.Center, "Screen position for the race timer (elapsed time). Options: Left, Center, Right.");
			UseCustomLobbyName = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomLobbyName", false, "Enable custom lobby name and color");
			LobbyNameText = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "LobbyNameText", "GRace Plus Lobby", "Custom text for lobby name");
			LobbyNameColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "LobbyNameColor", "yellow", "Color for lobby name text (red, green, blue, yellow, white, cyan, magenta, black, gray)");
			CustomGraffitiOutline = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "CustomGraffitiOutline", false, "When enabled, race graffiti spots use the custom outline color from Custom Graffiti Outline Hex Color during GRACE races.");
			CustomGraffitiOutlineHexColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "Custom Graffiti Outline Hex Color", "#FF0000", "Hex color for the graffiti spot outline when CustomGraffitiOutline is enabled and Use Config Hex is on (e.g. #FF0000 or FF0000). Default is red.");
			UseConfigHexForGraffitiOutline = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseConfigHexForGraffitiOutline", false, "When enabled, use Custom Graffiti Outline Hex Color from this config file and ignore the in-app Graffiti Outline Color preset.");
			GraffitiOutlinePreset = ((BaseUnityPlugin)this).Config.Bind<string>("UI Options", "GraffitiOutlinePreset", "normal", "In-app preset: normal, orange, red, blue, pink, purple, green, white, black. Only used when UseConfigHexForGraffitiOutline is false.");
			UseCustomGraffitiIndicator = ((BaseUnityPlugin)this).Config.Bind<bool>("UI Options", "UseCustomGraffitiIndicator", false, "When enabled, use the first PNG in GRACEPlus/Textures/GraffitiSign as the on-screen graffiti race indicator instead of BombRushMP's default.");
			string gracePlusFolderPath = GracePlusFolderPath;
			Directory.CreateDirectory(gracePlusFolderPath);
			Directory.CreateDirectory(Path.Combine(gracePlusFolderPath, "Textures"));
			Directory.CreateDirectory(Path.Combine(gracePlusFolderPath, "Textures", "GraffitiSign"));
			_recordsFilePath = Path.Combine(gracePlusFolderPath, "grace_records.txt");
			_teamRecordsFilePath = Path.Combine(gracePlusFolderPath, "grace_team_records.txt");
			_debugLogFilePath = Path.Combine(gracePlusFolderPath, "debug_log.txt");
			string text = Path.Combine(gracePlusFolderPath, "savefile");
			Directory.CreateDirectory(text);
			_customStatsFilePath = Path.Combine(text, "custom_grace_stats.txt");
			_mapStatsFolder = Path.Combine(gracePlusFolderPath, "Map Stats");
			Directory.CreateDirectory(_mapStatsFolder);
			LoadRecords();
			LoadTeamRecords();
			LoadCustomStats();
			LogEvents = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogEvents", false, "Enable detailed event logging to debug_log.txt for troubleshooting");
			AutoStartGRaceRounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Lobby", "AutoStartGRaceRounds", false, "Automatically start the next GRACE round after a round ends (host only)");
			AutoStartDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Lobby", "AutoStartDelaySeconds", 5, new ConfigDescription("Seconds to wait before auto-starting the next round (when AutoStartGRaceRounds is enabled)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 60), Array.Empty<object>()));
			_harmony = new Harmony("com.snail.graceplus");
			_harmony.PatchAll();
			BlockStageSwitchDuringRacePatch.Apply(_harmony);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		public static void ShowNotification(string message)
		{
			ConfigEntry<bool> hideAllAutomatedMessages = HideAllAutomatedMessages;
			if (hideAllAutomatedMessages == null || !hideAllAutomatedMessages.Value)
			{
				ChatUI instance = ChatUI.Instance;
				if (instance != null)
				{
					instance.AddMessage(message);
				}
			}
		}

		public static void ShowCommandResponse(string message)
		{
			ChatUI instance = ChatUI.Instance;
			if (instance != null)
			{
				instance.AddMessage(message);
			}
		}

		public static void RunMapStatsCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				if (!TrackMapStats.Value && !HasMapStatsFile(stage))
				{
					ShowCommandResponse("<color=yellow>Enable Map Stat Tracking in config, run some games on this map, and then you will have stats to see here</color>");
					return;
				}
				double? averageTime = GetAverageTime(stage);
				double? bestTime = GetBestTime(stage);
				double? averageTime2 = GetAverageTime(stage, isTeamMode: true);
				double? bestTime2 = GetBestTime(stage, isTeamMode: true);
				var (num, num2, num3, num4) = GetMapStats(stage);
				if (averageTime.HasValue || bestTime.HasValue || averageTime2.HasValue || bestTime2.HasValue || num > 0 || num2 > 0 || num3 > 0)
				{
					ShowCommandResponse($"<color=green>=== Stage {stage} Statistics ===</color>");
					if (averageTime.HasValue)
					{
						ShowCommandResponse($"<color=green>Avg Win Time: {averageTime.Value:F1}s</color>");
					}
					else
					{
						ShowCommandResponse("<color=yellow>Avg Win Time: No wins recorded</color>");
					}
					if (bestTime.HasValue)
					{
						ShowCommandResponse($"<color=green>Best Time: {bestTime.Value:F1}s</color>");
					}
					else
					{
						ShowCommandResponse("<color=yellow>Best Time: No record set</color>");
					}
					if (num > 0 || num2 > 0)
					{
						ShowCommandResponse($"<color=green>Multiplayer Win Ratio: {num4:F1}%</color>");
						ShowCommandResponse($"<color=green>Multiplayer Wins: {num}</color>");
						ShowCommandResponse($"<color=green>Multiplayer Losses: {num2}</color>");
					}
					else
					{
						ShowCommandResponse("<color=yellow>Multiplayer Win Ratio: No multiplayer games</color>");
						ShowCommandResponse("<color=yellow>Multiplayer Wins: 0</color>");
						ShowCommandResponse("<color=yellow>Multiplayer Losses: 0</color>");
					}
					ShowCommandResponse($"<color=green>Solo Games: {num3}</color>");
					if (averageTime2.HasValue || bestTime2.HasValue)
					{
						ShowCommandResponse("<color=yellow>=== Team Mode ===</color>");
						if (averageTime2.HasValue)
						{
							ShowCommandResponse($"<color=yellow>Avg Team Time: {averageTime2.Value:F1}s</color>");
						}
						else
						{
							ShowCommandResponse("<color=yellow>Avg Team Time: No wins recorded</color>");
						}
						if (bestTime2.HasValue)
						{
							ShowCommandResponse($"<color=yellow>Best Team Time: {bestTime2.Value:F1}s</color>");
						}
						else
						{
							ShowCommandResponse("<color=yellow>Best Team Time: No record set</color>");
						}
					}
				}
				else
				{
					ShowCommandResponse("<color=yellow>Enable Map Stat Tracking in config, run some games on this map, and then you will have stats to see here</color>");
				}
			}
			else
			{
				ShowCommandResponse("<color=red>You must be in a lobby to use /mapstats!</color>");
			}
		}

		public static void RunGraceStatsCommand()
		{
			MPSaveData instance = MPSaveData.Instance;
			if (instance?.Stats != null)
			{
				MPStats stats = instance.Stats;
				uint value = 0u;
				uint value2 = 0u;
				uint value3 = 0u;
				stats.GamemodesWon.TryGetValue((GamemodeIDs)1, out value);
				stats.GamemodesPlayed.TryGetValue((GamemodeIDs)1, out value2);
				stats.GamemodesPlayedAlone.TryGetValue((GamemodeIDs)1, out value3);
				uint elitesPlayedAgainst = stats.ElitesPlayedAgainst;
				uint elitesBeaten = stats.ElitesBeaten;
				ShowCommandResponse("<color=yellow>=== ACN Stats: ===</color>");
				ShowCommandResponse($"<color=yellow>GRACE Wins: {value}</color>");
				ShowCommandResponse($"<color=yellow>GRACE Games Played: {value2}</color>");
				ShowCommandResponse($"<color=yellow>GRACE Solo Games: {value3}</color>");
				ShowCommandResponse($"<color=yellow>Elites Played Against: {elitesPlayedAgainst}</color>");
				ShowCommandResponse($"<color=yellow>Elites Beaten: {elitesBeaten}</color>");
				(int wins, int losses, int gamesPlayed, int soloWins, int teamWins, int teamGamesPlayed) customStats = GetCustomStats();
				int item = customStats.wins;
				int item2 = customStats.losses;
				int item3 = customStats.soloWins;
				int item4 = customStats.teamWins;
				int item5 = customStats.teamGamesPlayed;
				int num = item + item2;
				double num2 = ((num > 0) ? ((double)item / (double)num * 100.0) : 0.0);
				double num3 = ((item5 > 0) ? ((double)item4 / (double)item5 * 100.0) : 0.0);
				ShowCommandResponse("<color=green>=== GRACEPlus Custom Stats: ===</color>");
				ShowCommandResponse($"<color=green>Solo Wins: {item3}</color>");
				ShowCommandResponse($"<color=green>Multiplayer Wins: {item}</color>");
				ShowCommandResponse($"<color=green>Multiplayer Losses: {item2}</color>");
				ShowCommandResponse($"<color=green>Multiplayer Games: {num}</color>");
				ShowCommandResponse($"<color=green>Multiplayer Win Ratio: {num2:F1}%</color>");
				ShowCommandResponse($"<color=green>Team Wins: {item4}</color>");
				ShowCommandResponse($"<color=green>Team Games: {item5}</color>");
				ShowCommandResponse($"<color=green>Team Win Ratio: {num3:F1}%</color>");
			}
		}

		public static void RunGraceHelpCommand()
		{
			ShowCommandResponse("<color=green>=== GRACEPlus Commands ===</color>");
			ShowCommandResponse("<color=white>/showbest - Show your best time for current stage</color>");
			ShowCommandResponse("<color=white>/timeclear - Clear best time for current stage</color>");
			ShowCommandResponse("<color=white>/timeclearall - Clear all saved best times</color>");
			ShowCommandResponse("<color=white>/teamtimeclear - Clear team best time for current stage</color>");
			ShowCommandResponse("<color=white>/teamtimeclearall - Clear all saved team best times</color>");
			ShowCommandResponse("<color=white>/gracestats - Show lifetime GRACE statistics</color>");
			ShowCommandResponse("<color=white>/gracestatsclear - Clear custom statistics</color>");
			ShowCommandResponse("<color=white>/avgtime - Show average win time for current stage</color>");
			ShowCommandResponse("<color=white>/mapstats - Show detailed stats for current stage</color>");
			ShowCommandResponse("<color=white>/clearmapstats - Clear map stats for current stage</color>");
			ShowCommandResponse("<color=white>/clearmapstatsall - Clear all map statistics</color>");
			ShowCommandResponse("<color=white>/start - Start the round (host only)</color>");
			ShowCommandResponse("<color=white>/end - End the round (host only)</color>");
			ShowCommandResponse("<color=white>/leave - Leave the lobby</color>");
			ShowCommandResponse("<color=white>/gracehelp - Show this help message</color>");
		}

		public static Color? GetEffectiveOutlineColor()
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			if (CustomGraffitiOutline == null || !CustomGraffitiOutline.Value)
			{
				return null;
			}
			if (UseConfigHexForGraffitiOutline != null && UseConfigHexForGraffitiOutline.Value && CustomGraffitiOutlineHexColor != null && !string.IsNullOrWhiteSpace(CustomGraffitiOutlineHexColor.Value))
			{
				string text = CustomGraffitiOutlineHexColor.Value.Trim();
				if (!text.StartsWith("#"))
				{
					text = "#" + text;
				}
				Color value = default(Color);
				if (ColorUtility.TryParseHtmlString(text, ref value))
				{
					return value;
				}
			}
			string text2 = ((GraffitiOutlinePreset != null) ? GraffitiOutlinePreset.Value : "normal");
			if (string.IsNullOrEmpty(text2) || string.Equals(text2, "normal", StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			string presetOutlineHex = GetPresetOutlineHex(text2);
			Color value2 = default(Color);
			if (presetOutlineHex != null && ColorUtility.TryParseHtmlString(presetOutlineHex, ref value2))
			{
				return value2;
			}
			return null;
		}

		public static string GetCustomGraffitiSignPath()
		{
			if (UseCustomGraffitiIndicator == null || !UseCustomGraffitiIndicator.Value)
			{
				return null;
			}
			try
			{
				string path = Path.Combine(Path.Combine(Paths.BepInExRootPath, "GRACEPlus"), "Textures", "GraffitiSign");
				if (!Directory.Exists(path))
				{
					return null;
				}
				string[] files = Directory.GetFiles(path, "*.png", SearchOption.TopDirectoryOnly);
				return (files.Length != 0) ? files[0] : null;
			}
			catch
			{
				return null;
			}
		}

		public static string GetPresetOutlineHex(string preset)
		{
			if (string.IsNullOrEmpty(preset))
			{
				return null;
			}
			return preset.ToLowerInvariant() switch
			{
				"orange" => "#FFA500", 
				"red" => "#FF0000", 
				"blue" => "#2080F0", 
				"pink" => "#FF69B4", 
				"purple" => "#9932CC", 
				"green" => "#00FF00", 
				"white" => "#FFFFFF", 
				"black" => "#111111", 
				_ => null, 
			};
		}

		public static void ScheduleAutoStartIfEnabled()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Invalid comparison between Unknown and I4
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Invalid comparison between Unknown and I4
			if (!AutoStartGRaceRounds.Value || (Object)(object)Instance == (Object)null)
			{
				return;
			}
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null && val.LobbyState.HostId == instance.LocalID && ((int)val.LobbyState.Gamemode == 1 || (int)val.LobbyState.Gamemode == 2))
			{
				int delaySeconds = Math.Max(1, Math.Min(AutoStartDelaySeconds.Value, 60));
				if (_autoStartCoroutine != null)
				{
					((MonoBehaviour)Instance).StopCoroutine(_autoStartCoroutine);
				}
				_autoStartCoroutine = ((MonoBehaviour)Instance).StartCoroutine(AutoStartCoroutine(delaySeconds));
			}
		}

		private static IEnumerator AutoStartCoroutine(int delaySeconds)
		{
			ShowNotification($"<color=yellow>Auto-starting next round in {delaySeconds} seconds...</color>");
			yield return (object)new WaitForSecondsRealtime((float)delaySeconds);
			_autoStartCoroutine = null;
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null && val.LobbyState.HostId == instance.LocalID)
			{
				if (val.InGame)
				{
					ShowNotification("<color=yellow>Round already started, skipping auto-start.</color>");
					yield break;
				}
				instance.ClientLobbyManager.StartGame();
				ShowNotification("<color=green>Auto-started next round!</color>");
			}
		}

		public static void ScheduleQuickStartGame()
		{
			if (!((Object)(object)Instance == (Object)null))
			{
				if (_quickStartCoroutine != null)
				{
					((MonoBehaviour)Instance).StopCoroutine(_quickStartCoroutine);
					_quickStartCoroutine = null;
				}
				_quickStartCoroutine = ((MonoBehaviour)Instance).StartCoroutine(QuickStartBufferCoroutine());
			}
		}

		private static IEnumerator QuickStartBufferCoroutine()
		{
			yield return (object)new WaitForSecondsRealtime(1f);
			_quickStartCoroutine = null;
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null && val.LobbyState.HostId == instance.LocalID && ((int)val.LobbyState.Gamemode == 1 || (int)val.LobbyState.Gamemode == 2) && !val.InGame)
			{
				instance.ClientLobbyManager.StartGame();
			}
		}

		public static void ShowConfigurableMessage(ConfigEntry<string> messageTemplate, ConfigEntry<string> color, params (string key, string value)[] variables)
		{
			ConfigEntry<bool> hideAllAutomatedMessages = HideAllAutomatedMessages;
			if ((hideAllAutomatedMessages == null || !hideAllAutomatedMessages.Value) && messageTemplate != null && color != null && !string.IsNullOrWhiteSpace(messageTemplate.Value))
			{
				string text = messageTemplate.Value;
				for (int i = 0; i < variables.Length; i++)
				{
					(string key, string value) tuple = variables[i];
					string item = tuple.key;
					string item2 = tuple.value;
					string oldValue = "{" + item + "}";
					text = text.Replace(oldValue, item2);
				}
				string text2 = "<color=" + color.Value + ">" + text + "</color>";
				ChatUI instance = ChatUI.Instance;
				if (instance != null)
				{
					instance.AddMessage(text2);
				}
			}
		}

		public static Color ParseColor(string colorName)
		{
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: 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_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: 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_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			switch (colorName.ToLower())
			{
			case "red":
				return Color.red;
			case "green":
				return Color.green;
			case "blue":
				return Color.blue;
			case "yellow":
				return Color.yellow;
			case "white":
				return Color.white;
			case "cyan":
				return Color.cyan;
			case "magenta":
				return Color.magenta;
			case "black":
				return Color.black;
			case "gray":
			case "grey":
				return Color.gray;
			default:
				return Color.white;
			}
		}

		public static Color ParseColorRgb(string rgb)
		{
			//IL_0008: 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_003d: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrWhiteSpace(rgb))
			{
				return Color.white;
			}
			string[] array = rgb.Split(new char[1] { ',' });
			if (array.Length < 3)
			{
				return Color.white;
			}
			if (!byte.TryParse(array[0].Trim(), out var result))
			{
				return Color.white;
			}
			if (!byte.TryParse(array[1].Trim(), out var result2))
			{
				return Color.white;
			}
			if (!byte.TryParse(array[2].Trim(), out var result3))
			{
				return Color.white;
			}
			float num = 1f;
			if (array.Length >= 4 && byte.TryParse(array[3].Trim(), out var result4))
			{
				num = (float)(int)result4 / 255f;
			}
			return new Color((float)(int)result / 255f, (float)(int)result2 / 255f, (float)(int)result3 / 255f, num);
		}

		private static void LoadRecords()
		{
			if (!File.Exists(_recordsFilePath))
			{
				return;
			}
			string[] array = File.ReadAllLines(_recordsFilePath);
			foreach (string text in array)
			{
				if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#"))
				{
					string[] array2 = text.Split(new char[1] { '=' });
					if (array2.Length == 2 && int.TryParse(array2[0].Trim(), out var result) && double.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
					{
						_bestTimes[result] = result2;
					}
				}
			}
		}

		private static void SaveRecords()
		{
			List<string> list = new List<string> { "# GRACEPlus Best Times (Solo Mode)", "# Format: StageID=TimeInSeconds", "# Generated automatically - do not edit manually", "" };
			foreach (KeyValuePair<int, double> bestTime in _bestTimes)
			{
				list.Add(string.Format("{0}={1}", bestTime.Key, bestTime.Value.ToString("F1", CultureInfo.InvariantCulture)));
			}
			File.WriteAllLines(_recordsFilePath, list);
		}

		private static void LoadTeamRecords()
		{
			if (!File.Exists(_teamRecordsFilePath))
			{
				return;
			}
			string[] array = File.ReadAllLines(_teamRecordsFilePath);
			foreach (string text in array)
			{
				if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#"))
				{
					string[] array2 = text.Split(new char[1] { '=' });
					if (array2.Length == 2 && int.TryParse(array2[0].Trim(), out var result) && double.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
					{
						_bestTeamTimes[result] = result2;
					}
				}
			}
		}

		private static void SaveTeamRecords()
		{
			List<string> list = new List<string> { "# GRACEPlus Best Times (Team Mode)", "# Format: StageID=TimeInSeconds", "# Generated automatically - do not edit manually", "" };
			foreach (KeyValuePair<int, double> bestTeamTime in _bestTeamTimes)
			{
				list.Add(string.Format("{0}={1}", bestTeamTime.Key, bestTeamTime.Value.ToString("F1", CultureInfo.InvariantCulture)));
			}
			File.WriteAllLines(_teamRecordsFilePath, list);
		}

		public static bool CheckAndUpdateBestTime(int stageId, double timeInSeconds, bool isTeamMode, out double? previousBest)
		{
			bool flag = false;
			previousBest = null;
			Dictionary<int, double> dictionary = (isTeamMode ? _bestTeamTimes : _bestTimes);
			if (!dictionary.ContainsKey(stageId))
			{
				dictionary[stageId] = timeInSeconds;
				flag = true;
			}
			else if (timeInSeconds < dictionary[stageId])
			{
				previousBest = dictionary[stageId];
				dictionary[stageId] = timeInSeconds;
				flag = true;
			}
			if (flag)
			{
				if (isTeamMode)
				{
					SaveTeamRecords();
				}
				else
				{
					SaveRecords();
				}
			}
			return flag;
		}

		public static double? GetBestTime(int stageId, bool isTeamMode = false)
		{
			Dictionary<int, double> dictionary = (isTeamMode ? _bestTeamTimes : _bestTimes);
			if (!dictionary.ContainsKey(stageId))
			{
				return null;
			}
			return dictionary[stageId];
		}

		public static bool ClearBestTime(int stageId, bool isTeamMode = false)
		{
			Dictionary<int, double> dictionary = (isTeamMode ? _bestTeamTimes : _bestTimes);
			if (dictionary.ContainsKey(stageId))
			{
				dictionary.Remove(stageId);
				if (isTeamMode)
				{
					SaveTeamRecords();
				}
				else
				{
					SaveRecords();
				}
				return true;
			}
			return false;
		}

		public static int ClearAllBestTimes(bool isTeamMode = false)
		{
			Dictionary<int, double> dictionary = (isTeamMode ? _bestTeamTimes : _bestTimes);
			int count = dictionary.Count;
			if (count > 0)
			{
				dictionary.Clear();
				if (isTeamMode)
				{
					SaveTeamRecords();
					return count;
				}
				SaveRecords();
			}
			return count;
		}

		private static string GetDailyMatchResultsFilePath()
		{
			string path = Path.Combine(Paths.BepInExRootPath, "GRACEPlus");
			string text = DateTime.Now.ToString("yyyy-MM-dd");
			return Path.Combine(path, "Graces_" + text + ".txt");
		}

		public static void SaveMatchResult(int stageId, double timeInSeconds, bool won, bool isTeamMode, bool isNewRecord, double? previousBest)
		{
			if (!SaveAllMatchResults.Value)
			{
				return;
			}
			string dailyMatchResultsFilePath = GetDailyMatchResultsFilePath();
			string text = DateTime.Now.ToString("HH:mm:ss");
			string text2 = (isTeamMode ? "Team GRACE" : "Solo GRACE");
			string text3 = (won ? "WIN" : "LOSS");
			string text4 = "";
			if (won && isNewRecord)
			{
				text4 = ((!previousBest.HasValue) ? " [FIRST WIN!]" : $" [NEW RECORD! Previous: {previousBest.Value:F1}s]");
			}
			else if (won)
			{
				double? bestTime = GetBestTime(stageId);
				if (bestTime.HasValue)
				{
					text4 = $" [Best: {bestTime.Value:F1}s]";
				}
			}
			string text5 = $"[{text}] Stage {stageId} | {text2} | {text3} | {timeInSeconds:F1}s{text4}";
			if (!File.Exists(dailyMatchResultsFilePath))
			{
				List<string> contents = new List<string>
				{
					"# GRACEPlus Daily Match Results",
					$"# Date: {DateTime.Now:yyyy-MM-dd}",
					"# Format: [Time] Stage ID | Gamemode | Result | Duration | Additional Info",
					""
				};
				File.WriteAllLines(dailyMatchResultsFilePath, contents);
			}
			File.AppendAllText(dailyMatchResultsFilePath, text5 + Environment.NewLine);
		}

		private static void LoadCustomStats()
		{
			if (!File.Exists(_customStatsFilePath))
			{
				return;
			}
			string[] array = File.ReadAllLines(_customStatsFilePath);
			foreach (string text in array)
			{
				if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#"))
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { '=' });
				if (array2.Length != 2)
				{
					continue;
				}
				string text2 = array2[0].Trim();
				if (int.TryParse(array2[1].Trim(), out var result))
				{
					switch (text2)
					{
					case "Wins":
						_customWins = result;
						break;
					case "Losses":
						_customLosses = result;
						break;
					case "GamesPlayed":
						_customGamesPlayed = result;
						break;
					case "SoloWins":
						_customSoloWins = result;
						break;
					case "TeamWins":
						_customTeamWins = result;
						break;
					case "TeamGamesPlayed":
						_customTeamGamesPlayed = result;
						break;
					}
				}
			}
		}

		private static void SaveCustomStats()
		{
			List<string> contents = new List<string>
			{
				"# GRACEPlus Custom Statistics",
				"# Format: StatName=Value",
				"# Generated automatically - do not edit manually",
				"",
				$"Wins={_customWins}",
				$"Losses={_customLosses}",
				$"GamesPlayed={_customGamesPlayed}",
				$"SoloWins={_customSoloWins}",
				$"TeamWins={_customTeamWins}",
				$"TeamGamesPlayed={_customTeamGamesPlayed}"
			};
			File.WriteAllLines(_customStatsFilePath, contents);
		}

		public static void IncrementCustomWins(bool isSolo, bool isTeamMode)
		{
			if (isTeamMode)
			{
				_customTeamWins++;
				_customTeamGamesPlayed++;
			}
			else if (isSolo)
			{
				_customSoloWins++;
				_customGamesPlayed++;
			}
			else
			{
				_customWins++;
				_customGamesPlayed++;
			}
			SaveCustomStats();
		}

		public static void IncrementCustomLosses(bool isTeamMode)
		{
			if (isTeamMode)
			{
				_customTeamGamesPlayed++;
			}
			else
			{
				_customLosses++;
				_customGamesPlayed++;
			}
			SaveCustomStats();
		}

		public static (int wins, int losses, int gamesPlayed, int soloWins, int teamWins, int teamGamesPlayed) GetCustomStats()
		{
			return (_customWins, _customLosses, _customGamesPlayed, _customSoloWins, _customTeamWins, _customTeamGamesPlayed);
		}

		public static void SetGameCancelled(bool cancelled)
		{
			_gameCancelled = cancelled;
		}

		public static bool IsGameCancelled()
		{
			return _gameCancelled;
		}

		public static void SetMaxPossibleScore(int maxScore)
		{
			_maxPossibleScore = maxScore;
		}

		public static int GetMaxPossibleScore()
		{
			return _maxPossibleScore;
		}

		public static void ResetMaxPossibleScore()
		{
			_maxPossibleScore = 0;
		}

		public static void SaveWinTime(int stageId, double timeInSeconds)
		{
			if (TrackMapStats.Value)
			{
				string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
				string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
				string text = $"{arg},{timeInSeconds:F1}";
				File.AppendAllText(path, text + Environment.NewLine);
			}
		}

		public static void SaveMapStats(int stageId, double timeInSeconds, bool won, bool isMultiplayer, bool isTeamMode = false)
		{
			if (TrackMapStats.Value)
			{
				string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
				string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
				string text2 = (won ? "WIN" : "LOSS");
				string text3 = (isMultiplayer ? "MULTIPLAYER" : "SOLO");
				string text4 = (isTeamMode ? "TEAM" : "NORMAL");
				string text5 = $"{text},{timeInSeconds:F1},{text2},{text3},{text4}";
				if (!File.Exists(path))
				{
					List<string> contents = new List<string>
					{
						"# GRACEPlus Map Statistics",
						$"# Stage: {stageId}",
						"# Format: Timestamp,Time,Result,GameType,ModeType",
						"# Result: WIN or LOSS",
						"# GameType: MULTIPLAYER or SOLO",
						"# ModeType: TEAM or NORMAL",
						""
					};
					File.WriteAllLines(path, contents);
				}
				File.AppendAllText(path, text5 + Environment.NewLine);
			}
		}

		public static double? GetAverageTime(int stageId, bool isTeamMode = false)
		{
			if (!TrackMapStats.Value)
			{
				return null;
			}
			string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
			if (!File.Exists(path))
			{
				return null;
			}
			string[] array = File.ReadAllLines(path);
			List<double> list = new List<double>();
			string text = (isTeamMode ? "TEAM" : "NORMAL");
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				if (string.IsNullOrWhiteSpace(text2) || text2.StartsWith("#"))
				{
					continue;
				}
				string[] array3 = text2.Split(new char[1] { ',' });
				if (array3.Length >= 3 && double.TryParse(array3[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && array3[2] == "WIN")
				{
					if (array3.Length >= 5 && array3[4] == text)
					{
						list.Add(result);
					}
					else if (array3.Length < 5 && !isTeamMode)
					{
						list.Add(result);
					}
				}
			}
			if (list.Count <= 0)
			{
				return null;
			}
			return list.Average();
		}

		public static int GetWinCount(int stageId)
		{
			if (!TrackMapStats.Value)
			{
				return 0;
			}
			string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
			if (!File.Exists(path))
			{
				return 0;
			}
			string[] array = File.ReadAllLines(path);
			int num = 0;
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#"))
				{
					string[] array3 = text.Split(new char[1] { ',' });
					if (array3.Length >= 3 && array3[2] == "WIN")
					{
						num++;
					}
				}
			}
			return num;
		}

		public static (int multiplayerWins, int multiplayerLosses, int soloGames, double multiplayerWinRatio) GetMapStats(int stageId)
		{
			if (!TrackMapStats.Value)
			{
				return (0, 0, 0, 0.0);
			}
			string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
			if (!File.Exists(path))
			{
				return (0, 0, 0, 0.0);
			}
			string[] array = File.ReadAllLines(path);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#"))
				{
					continue;
				}
				string[] array3 = text.Split(new char[1] { ',' });
				if (array3.Length < 4)
				{
					continue;
				}
				string text2 = array3[2];
				string text3 = array3[3];
				if (text3 == "MULTIPLAYER")
				{
					if (text2 == "WIN")
					{
						num++;
					}
					else if (text2 == "LOSS")
					{
						num2++;
					}
				}
				else if (text3 == "SOLO")
				{
					num3++;
				}
			}
			double item = 0.0;
			int num4 = num + num2;
			if (num4 > 0)
			{
				item = (double)num / (double)num4 * 100.0;
			}
			return (num, num2, num3, item);
		}

		public static bool HasMapStatsFile(int stageId)
		{
			return File.Exists(Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt"));
		}

		public static bool ClearMapStats(int stageId)
		{
			string path = Path.Combine(_mapStatsFolder, $"Stage_{stageId}.txt");
			if (File.Exists(path))
			{
				File.Delete(path);
				return true;
			}
			return false;
		}

		public static int ClearAllMapStats()
		{
			if (!Directory.Exists(_mapStatsFolder))
			{
				return 0;
			}
			string[] files = Directory.GetFiles(_mapStatsFolder, "Stage_*.txt");
			int num = 0;
			string[] array = files;
			foreach (string path in array)
			{
				try
				{
					File.Delete(path);
					num++;
				}
				catch
				{
				}
			}
			return num;
		}

		public static bool ClearCustomStats()
		{
			if (File.Exists(_customStatsFilePath))
			{
				File.Delete(_customStatsFilePath);
				_customWins = 0;
				_customLosses = 0;
				_customGamesPlayed = 0;
				_customSoloWins = 0;
				_customTeamWins = 0;
				_customTeamGamesPlayed = 0;
				return true;
			}
			return false;
		}

		public static void DebugLog(string message)
		{
			if (!LogEvents.Value)
			{
				return;
			}
			try
			{
				string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
				string text2 = "[" + text + "] " + message;
				File.AppendAllText(_debugLogFilePath, text2 + Environment.NewLine);
			}
			catch
			{
			}
		}

		public static void ClearDebugLog()
		{
			try
			{
				if (File.Exists(_debugLogFilePath))
				{
					File.Delete(_debugLogFilePath);
				}
				DebugLog("=== Debug Log Started ===");
			}
			catch
			{
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.snail.graceplus";

		public const string PLUGIN_NAME = "GRACEPlus";

		public const string PLUGIN_VERSION = "1.0.0";
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "GRACEPlus";

		public const string PLUGIN_NAME = "GRACEPlus";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace GRACEPlus.Phone
{
	public class AppGRACEPlus : CustomApp
	{
		private SimplePhoneButton _trackMapStatsButton;

		private SimplePhoneButton _autoRestartButton;

		private SimplePhoneButton _autoRestartDelayButton;

		private SimplePhoneButton _graffitiOutlineColorButton;

		private SimplePhoneButton _customGraffitiSignButton;

		private static bool _quickStartWaitingForLobby;

		private static readonly string[] GraffitiOutlinePresetOrder = new string[9] { "normal", "orange", "red", "blue", "pink", "purple", "green", "white", "black" };

		public static void Initialize()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			try
			{
				Sprite val = null;
				string appIconPath = GRACEPlusPlugin.GetAppIconPath();
				if (!string.IsNullOrEmpty(appIconPath) && File.Exists(appIconPath))
				{
					try
					{
						byte[] array = File.ReadAllBytes(appIconPath);
						Texture2D val2 = new Texture2D(1, 1);
						if (ImageConversion.LoadImage(val2, array))
						{
							((Texture)val2).wrapMode = (TextureWrapMode)1;
							val = TextureUtility.CreateSpriteFromTexture(val2);
						}
					}
					catch
					{
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					PhoneAPI.RegisterApp<AppGRACEPlus>("GRACE Plus", val);
				}
				else
				{
					PhoneAPI.RegisterApp<AppGRACEPlus>("GRACE Plus", (Sprite)null);
				}
			}
			catch
			{
			}
		}

		public override void OnAppInit()
		{
			((CustomApp)this).OnAppInit();
			((CustomApp)this).CreateIconlessTitleBar("GRACE Plus", 80f);
			base.ScrollView = PhoneScrollView.Create((CustomApp)(object)this, 275f, 1600f);
			PopulateMainScreen();
		}

		public override void OnAppEnable()
		{
			((App)this).OnAppEnable();
			if ((Object)(object)ClientController.Instance == (Object)null || !ClientController.Instance.Connected)
			{
				_quickStartWaitingForLobby = false;
				ClientController.PacketReceived = (Action<Packets, Packet>)Delegate.Remove(ClientController.PacketReceived, new Action<Packets, Packet>(OnPacketReceived_QuickStartLobbyCreation));
			}
			PopulateMainScreen();
		}

		private void PopulateMainScreen()
		{
			if (!((Object)(object)base.ScrollView == (Object)null))
			{
				base.ScrollView.RemoveAllButtons();
				SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Quick Start");
				((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
				{
					OnQuickStartPressed();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val);
				SimplePhoneButton val2 = PhoneUIUtility.CreateSimpleButton("Quick Lobby");
				((PhoneButton)val2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val2).OnConfirm, (Action)delegate
				{
					OnQuickLobbyPressed();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val2);
				SimplePhoneButton val3 = PhoneUIUtility.CreateSimpleButton("Game Settings");
				((PhoneButton)val3).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val3).OnConfirm, (Action)delegate
				{
					ShowGameSettingsView();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val3);
				SimplePhoneButton val4 = PhoneUIUtility.CreateSimpleButton("Stats");
				((PhoneButton)val4).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val4).OnConfirm, (Action)delegate
				{
					ShowStatsView();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val4);
				SimplePhoneButton val5 = PhoneUIUtility.CreateSimpleButton("Customize");
				((PhoneButton)val5).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val5).OnConfirm, (Action)delegate
				{
					ShowCustomizeView();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val5);
				SimplePhoneButton val6 = PhoneUIUtility.CreateSimpleButton("UI Options");
				((PhoneButton)val6).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val6).OnConfirm, (Action)delegate
				{
					ShowUIOptionsView();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val6);
				SimplePhoneButton val7 = PhoneUIUtility.CreateSimpleButton("Commands");
				((PhoneButton)val7).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val7).OnConfirm, (Action)delegate
				{
					ShowCommandsView();
				});
				base.ScrollView.AddButton((PhoneButton)(object)val7);
			}
		}

		private void ShowCommandsView()
		{
			base.ScrollView.RemoveAllButtons();
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Map Stats");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				GRACEPlusPlugin.RunMapStatsCommand();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
			SimplePhoneButton val2 = PhoneUIUtility.CreateSimpleButton("Grace Stats");
			((PhoneButton)val2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val2).OnConfirm, (Action)delegate
			{
				GRACEPlusPlugin.RunGraceStatsCommand();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val2);
			SimplePhoneButton val3 = PhoneUIUtility.CreateSimpleButton("Grace Help");
			((PhoneButton)val3).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val3).OnConfirm, (Action)delegate
			{
				GRACEPlusPlugin.RunGraceHelpCommand();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val3);
			SimplePhoneButton val4 = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val4).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val4).OnConfirm, (Action)delegate
			{
				PopulateMainScreen();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val4);
		}

		private static void OnQuickLobbyPressed()
		{
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance == (Object)null || !instance.Connected)
			{
				return;
			}
			ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
			if (clientLobbyManager != null && clientLobbyManager.CurrentLobby == null && clientLobbyManager.CanJoinLobby())
			{
				GamemodeSettings gamemodeSettings = GamemodeFactory.GetGamemodeSettings((GamemodeIDs)1);
				MPSaveData instance2 = MPSaveData.Instance;
				SavedGamemodeSettings val = ((instance2 != null) ? instance2.GetSavedSettings((GamemodeIDs)1) : null);
				if (val != null)
				{
					gamemodeSettings.ApplySaved(val);
				}
				clientLobbyManager.CreateLobby((GamemodeIDs)1, gamemodeSettings);
			}
		}

		private static void OnQuickStartPressed()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Invalid comparison between Unknown and I4
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Invalid comparison between Unknown and I4
			ClientController instance = ClientController.Instance;
			if ((Object)(object)instance == (Object)null || !instance.Connected)
			{
				return;
			}
			ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
			Lobby val = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			if (val != null)
			{
				bool num = val.LobbyState.HostId == instance.LocalID;
				bool flag = (int)val.LobbyState.Gamemode == 1 || (int)val.LobbyState.Gamemode == 2;
				if (num && flag && !val.InGame)
				{
					GRACEPlusPlugin.ScheduleQuickStartGame();
				}
			}
			else if (clientLobbyManager != null && clientLobbyManager.CanJoinLobby())
			{
				GamemodeSettings gamemodeSettings = GamemodeFactory.GetGamemodeSettings((GamemodeIDs)1);
				MPSaveData instance2 = MPSaveData.Instance;
				SavedGamemodeSettings val2 = ((instance2 != null) ? instance2.GetSavedSettings((GamemodeIDs)1) : null);
				if (val2 != null)
				{
					gamemodeSettings.ApplySaved(val2);
				}
				_quickStartWaitingForLobby = true;
				ClientController.PacketReceived = (Action<Packets, Packet>)Delegate.Combine(ClientController.PacketReceived, new Action<Packets, Packet>(OnPacketReceived_QuickStartLobbyCreation));
				clientLobbyManager.CreateLobby((GamemodeIDs)1, gamemodeSettings);
			}
		}

		private static void OnPacketReceived_QuickStartLobbyCreation(Packets packetId, Packet packet)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			if ((int)packetId == 14 && _quickStartWaitingForLobby)
			{
				_quickStartWaitingForLobby = false;
				ClientController.PacketReceived = (Action<Packets, Packet>)Delegate.Remove(ClientController.PacketReceived, new Action<Packets, Packet>(OnPacketReceived_QuickStartLobbyCreation));
				GRACEPlusPlugin.ScheduleQuickStartGame();
			}
		}

		private void ShowCustomizeView()
		{
			base.ScrollView.RemoveAllButtons();
			_graffitiOutlineColorButton = PhoneUIUtility.CreateSimpleButton(GraffitiOutlineColorLabel());
			SimplePhoneButton graffitiOutlineColorButton = _graffitiOutlineColorButton;
			((PhoneButton)graffitiOutlineColorButton).OnConfirm = (Action)Delegate.Combine(((PhoneButton)graffitiOutlineColorButton).OnConfirm, (Action)delegate
			{
				CycleGraffitiOutlinePreset();
				if ((Object)(object)_graffitiOutlineColorButton?.Label != (Object)null)
				{
					((TMP_Text)_graffitiOutlineColorButton.Label).text = GraffitiOutlineColorLabel();
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)_graffitiOutlineColorButton);
			_customGraffitiSignButton = PhoneUIUtility.CreateSimpleButton(CustomGraffitiSignLabel());
			SimplePhoneButton customGraffitiSignButton = _customGraffitiSignButton;
			((PhoneButton)customGraffitiSignButton).OnConfirm = (Action)Delegate.Combine(((PhoneButton)customGraffitiSignButton).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.UseCustomGraffitiIndicator != null)
				{
					GRACEPlusPlugin.UseCustomGraffitiIndicator.Value = !GRACEPlusPlugin.UseCustomGraffitiIndicator.Value;
					if ((Object)(object)_customGraffitiSignButton?.Label != (Object)null)
					{
						((TMP_Text)_customGraffitiSignButton.Label).text = CustomGraffitiSignLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)_customGraffitiSignButton);
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				PopulateMainScreen();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
		}

		private void ShowUIOptionsView()
		{
			base.ScrollView.RemoveAllButtons();
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Visible");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				ShowVisibleView();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
			SimplePhoneButton val2 = PhoneUIUtility.CreateSimpleButton("Locations");
			((PhoneButton)val2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val2).OnConfirm, (Action)delegate
			{
				ShowLocationsView();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val2);
			SimplePhoneButton val3 = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val3).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val3).OnConfirm, (Action)delegate
			{
				PopulateMainScreen();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val3);
		}

		private void ShowLocationsView()
		{
			base.ScrollView.RemoveAllButtons();
			SimplePhoneButton timerLocationButton = PhoneUIUtility.CreateSimpleButton(TimerLocationLabel());
			SimplePhoneButton obj = timerLocationButton;
			((PhoneButton)obj).OnConfirm = (Action)Delegate.Combine(((PhoneButton)obj).OnConfirm, (Action)delegate
			{
				CycleLocation(GRACEPlusPlugin.TimerLocation);
				if ((Object)(object)timerLocationButton?.Label != (Object)null)
				{
					((TMP_Text)timerLocationButton.Label).text = TimerLocationLabel();
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)timerLocationButton);
			SimplePhoneButton tagCounterLocationButton = PhoneUIUtility.CreateSimpleButton(TagCounterLocationLabel());
			SimplePhoneButton obj2 = tagCounterLocationButton;
			((PhoneButton)obj2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)obj2).OnConfirm, (Action)delegate
			{
				CycleLocation(GRACEPlusPlugin.TagCounterLocation);
				if ((Object)(object)tagCounterLocationButton?.Label != (Object)null)
				{
					((TMP_Text)tagCounterLocationButton.Label).text = TagCounterLocationLabel();
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)tagCounterLocationButton);
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				ShowUIOptionsView();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
		}

		private static void CycleLocation(ConfigEntry<TimerUILocation> entry)
		{
			if (entry != null)
			{
				entry.Value = entry.Value switch
				{
					TimerUILocation.Left => TimerUILocation.Center, 
					TimerUILocation.Center => TimerUILocation.Right, 
					_ => TimerUILocation.Left, 
				};
			}
		}

		private static string TimerLocationLabel()
		{
			return "Timer Location: " + ((GRACEPlusPlugin.TimerLocation == null) ? TimerUILocation.Center : GRACEPlusPlugin.TimerLocation.Value);
		}

		private static string TagCounterLocationLabel()
		{
			return "Tag Counter Location: " + ((GRACEPlusPlugin.TagCounterLocation == null) ? TimerUILocation.Center : GRACEPlusPlugin.TagCounterLocation.Value);
		}

		private void ShowVisibleView()
		{
			base.ScrollView.RemoveAllButtons();
			SimplePhoneButton showTimerButton = PhoneUIUtility.CreateSimpleButton(ShowTimerLabel());
			SimplePhoneButton obj = showTimerButton;
			((PhoneButton)obj).OnConfirm = (Action)Delegate.Combine(((PhoneButton)obj).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.HideRaceTimer != null)
				{
					GRACEPlusPlugin.HideRaceTimer.Value = !GRACEPlusPlugin.HideRaceTimer.Value;
					if ((Object)(object)showTimerButton?.Label != (Object)null)
					{
						((TMP_Text)showTimerButton.Label).text = ShowTimerLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)showTimerButton);
			SimplePhoneButton showLobbyWinCounterButton = PhoneUIUtility.CreateSimpleButton(ShowLobbyWinCounterLabel());
			SimplePhoneButton obj2 = showLobbyWinCounterButton;
			((PhoneButton)obj2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)obj2).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.ShowLobbyWinCounter != null)
				{
					GRACEPlusPlugin.ShowLobbyWinCounter.Value = !GRACEPlusPlugin.ShowLobbyWinCounter.Value;
					if ((Object)(object)showLobbyWinCounterButton?.Label != (Object)null)
					{
						((TMP_Text)showLobbyWinCounterButton.Label).text = ShowLobbyWinCounterLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)showLobbyWinCounterButton);
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				ShowUIOptionsView();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
		}

		private static void CycleGraffitiOutlinePreset()
		{
			if (GRACEPlusPlugin.GraffitiOutlinePreset != null)
			{
				string current = GRACEPlusPlugin.GraffitiOutlinePreset.Value ?? "normal";
				int num = Array.FindIndex(GraffitiOutlinePresetOrder, (string x) => string.Equals(x, current, StringComparison.OrdinalIgnoreCase));
				if (num < 0)
				{
					num = 0;
				}
				num = (num + 1) % GraffitiOutlinePresetOrder.Length;
				string text = GraffitiOutlinePresetOrder[num];
				GRACEPlusPlugin.GraffitiOutlinePreset.Value = text;
				if (GRACEPlusPlugin.CustomGraffitiOutline != null)
				{
					GRACEPlusPlugin.CustomGraffitiOutline.Value = !string.Equals(text, "normal", StringComparison.OrdinalIgnoreCase);
				}
			}
		}

		private static string GraffitiOutlineColorLabel()
		{
			if (GRACEPlusPlugin.UseConfigHexForGraffitiOutline != null && GRACEPlusPlugin.UseConfigHexForGraffitiOutline.Value)
			{
				return "Graffiti Outline Color: (config hex)";
			}
			string text = ((GRACEPlusPlugin.GraffitiOutlinePreset != null) ? GRACEPlusPlugin.GraffitiOutlinePreset.Value : "normal");
			return "Graffiti Outline Color: " + (string.IsNullOrEmpty(text) ? "normal" : text.ToLowerInvariant());
		}

		private static string CustomGraffitiSignLabel()
		{
			bool flag = GRACEPlusPlugin.UseCustomGraffitiIndicator != null && GRACEPlusPlugin.UseCustomGraffitiIndicator.Value;
			return "Custom Graffiti Sign: " + (flag ? "On" : "Off");
		}

		private static string ShowTimerLabel()
		{
			bool flag = GRACEPlusPlugin.HideRaceTimer != null && !GRACEPlusPlugin.HideRaceTimer.Value;
			return "Show Timer: " + (flag ? "On" : "Off");
		}

		private static string ShowLobbyWinCounterLabel()
		{
			bool flag = GRACEPlusPlugin.ShowLobbyWinCounter != null && GRACEPlusPlugin.ShowLobbyWinCounter.Value;
			return "Show Lobby Win Counter: " + (flag ? "On" : "Off");
		}

		private static int GetCurrentStageId()
		{
			//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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected I4, but got Unknown
			try
			{
				ClientController instance = ClientController.Instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
					obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
				}
				if (obj != null)
				{
					return instance.ClientLobbyManager.CurrentLobby.LobbyState.Stage;
				}
				Scene activeScene = SceneManager.GetActiveScene();
				if (!((Scene)(ref activeScene)).IsValid())
				{
					return -1;
				}
				string text = ((Scene)(ref activeScene)).name ?? "";
				if (string.IsNullOrEmpty(text))
				{
					return -1;
				}
				return (int)Utility.SceneNameToStage(text);
			}
			catch
			{
				return -1;
			}
		}

		private void ShowStatsView()
		{
			base.ScrollView.RemoveAllButtons();
			int stageId = GetCurrentStageId();
			double? bestTime = ((stageId >= 0) ? GRACEPlusPlugin.GetBestTime(stageId) : null);
			double? avgTime = ((stageId >= 0) ? GRACEPlusPlugin.GetAverageTime(stageId) : null);
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton(StatsBestTimeLabel(bestTime));
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
			SimplePhoneButton val2 = PhoneUIUtility.CreateSimpleButton(StatsAvgTimeLabel(avgTime));
			((PhoneButton)val2).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val2).OnConfirm, (Action)delegate
			{
			});
			base.ScrollView.AddButton((PhoneButton)(object)val2);
			SimplePhoneButton val3 = PhoneUIUtility.CreateSimpleButton("Clear Best Time");
			((PhoneButton)val3).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val3).OnConfirm, (Action)delegate
			{
				if (stageId >= 0)
				{
					GRACEPlusPlugin.ClearBestTime(stageId);
					ShowStatsView();
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)val3);
			SimplePhoneButton val4 = PhoneUIUtility.CreateSimpleButton("Clear AVG Time");
			((PhoneButton)val4).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val4).OnConfirm, (Action)delegate
			{
				if (stageId >= 0)
				{
					GRACEPlusPlugin.ClearMapStats(stageId);
					ShowStatsView();
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)val4);
			SimplePhoneButton val5 = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val5).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val5).OnConfirm, (Action)delegate
			{
				PopulateMainScreen();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val5);
		}

		private static string StatsBestTimeLabel(double? bestTime)
		{
			return "Best Time: " + (bestTime.HasValue ? $"{bestTime.Value:F1}s" : "—");
		}

		private static string StatsAvgTimeLabel(double? avgTime)
		{
			return "AVG Time: " + (avgTime.HasValue ? $"{avgTime.Value:F1}s" : "—");
		}

		private void ShowGameSettingsView()
		{
			base.ScrollView.RemoveAllButtons();
			_trackMapStatsButton = PhoneUIUtility.CreateSimpleButton(TrackMapStatsLabel());
			SimplePhoneButton trackMapStatsButton = _trackMapStatsButton;
			((PhoneButton)trackMapStatsButton).OnConfirm = (Action)Delegate.Combine(((PhoneButton)trackMapStatsButton).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.TrackMapStats != null)
				{
					GRACEPlusPlugin.TrackMapStats.Value = !GRACEPlusPlugin.TrackMapStats.Value;
					if ((Object)(object)_trackMapStatsButton?.Label != (Object)null)
					{
						((TMP_Text)_trackMapStatsButton.Label).text = TrackMapStatsLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)_trackMapStatsButton);
			_autoRestartButton = PhoneUIUtility.CreateSimpleButton(AutoRestartLabel());
			SimplePhoneButton autoRestartButton = _autoRestartButton;
			((PhoneButton)autoRestartButton).OnConfirm = (Action)Delegate.Combine(((PhoneButton)autoRestartButton).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.AutoStartGRaceRounds != null)
				{
					GRACEPlusPlugin.AutoStartGRaceRounds.Value = !GRACEPlusPlugin.AutoStartGRaceRounds.Value;
					if ((Object)(object)_autoRestartButton?.Label != (Object)null)
					{
						((TMP_Text)_autoRestartButton.Label).text = AutoRestartLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)_autoRestartButton);
			_autoRestartDelayButton = PhoneUIUtility.CreateSimpleButton(AutoRestartDelayLabel());
			SimplePhoneButton autoRestartDelayButton = _autoRestartDelayButton;
			((PhoneButton)autoRestartDelayButton).OnConfirm = (Action)Delegate.Combine(((PhoneButton)autoRestartDelayButton).OnConfirm, (Action)delegate
			{
				if (GRACEPlusPlugin.AutoStartDelaySeconds != null)
				{
					int autoRestartDelayForCycle = GetAutoRestartDelayForCycle();
					autoRestartDelayForCycle++;
					if (autoRestartDelayForCycle > 10)
					{
						autoRestartDelayForCycle = 1;
					}
					GRACEPlusPlugin.AutoStartDelaySeconds.Value = autoRestartDelayForCycle;
					if ((Object)(object)_autoRestartDelayButton?.Label != (Object)null)
					{
						((TMP_Text)_autoRestartDelayButton.Label).text = AutoRestartDelayLabel();
					}
				}
			});
			base.ScrollView.AddButton((PhoneButton)(object)_autoRestartDelayButton);
			SimplePhoneButton val = PhoneUIUtility.CreateSimpleButton("Back");
			((PhoneButton)val).OnConfirm = (Action)Delegate.Combine(((PhoneButton)val).OnConfirm, (Action)delegate
			{
				PopulateMainScreen();
			});
			base.ScrollView.AddButton((PhoneButton)(object)val);
		}

		private static string TrackMapStatsLabel()
		{
			bool flag = GRACEPlusPlugin.TrackMapStats != null && GRACEPlusPlugin.TrackMapStats.Value;
			return "Track Map Stats: " + (flag ? "On" : "Off");
		}

		private static string AutoRestartLabel()
		{
			bool flag = GRACEPlusPlugin.AutoStartGRaceRounds != null && GRACEPlusPlugin.AutoStartGRaceRounds.Value;
			return "Auto Restart: " + (flag ? "On" : "Off");
		}

		private static int GetAutoRestartDelayForCycle()
		{
			if (GRACEPlusPlugin.AutoStartDelaySeconds == null)
			{
				return 1;
			}
			int value = GRACEPlusPlugin.AutoStartDelaySeconds.Value;
			if (value < 1)
			{
				return 1;
			}
			if (value > 10)
			{
				return 10;
			}
			return value;
		}

		private static string AutoRestartDelayLabel()
		{
			return "Auto Restart Delay: " + ((GRACEPlusPlugin.AutoStartDelaySeconds == null) ? 1 : GetAutoRestartDelayForCycle()) + "s";
		}
	}
}
namespace GRACEPlus.Patches
{
	internal static class BlockStageSwitchDuringRacePatch
	{
		public const string BlockMessage = "<color=yellow>Can't change stage during a GRACE race. Finish or end the round first.</color>";

		public static bool ShouldBlock()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Invalid comparison between Unknown and I4
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val == null || !val.InGame)
			{
				return false;
			}
			if ((int)val.LobbyState.Gamemode != 1)
			{
				return (int)val.LobbyState.Gamemode == 2;
			}
			return true;
		}

		public static bool Prefix_SwitchStage1(Stage newStage)
		{
			if (!ShouldBlock())
			{
				return true;
			}
			GRACEPlusPlugin.ShowNotification("<color=yellow>Can't change stage during a GRACE race. Finish or end the round first.</color>");
			return false;
		}

		public static bool Prefix_SwitchStage2(Stage newStage, Stage fromStage)
		{
			if (!ShouldBlock())
			{
				return true;
			}
			GRACEPlusPlugin.ShowNotification("<color=yellow>Can't change stage during a GRACE race. Finish or end the round first.</color>");
			return false;
		}

		public static bool Prefix_ExitCurrentStage(Stage nextStage, Stage prevStageSpecifiedForSpawner)
		{
			if (!ShouldBlock())
			{
				return true;
			}
			GRACEPlusPlugin.ShowNotification("<color=yellow>Can't change stage during a GRACE race. Finish or end the round first.</color>");
			return false;
		}

		public static void Apply(Harmony harmony)
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Expected O, but got Unknown
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Expected O, but got Unknown
			if (harmony == null)
			{
				return;
			}
			Assembly assembly = null;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly2 in assemblies)
			{
				if (assembly2.GetType("Reptile.BaseModule") != null)
				{
					assembly = assembly2;
					break;
				}
			}
			if (assembly == null)
			{
				return;
			}
			Type type = assembly.GetType("Reptile.BaseModule");
			Type type2 = assembly.GetType("Reptile.StageManager");
			Type type3 = assembly.GetType("Reptile.Stage");
			if (type3 == null)
			{
				type3 = typeof(Stage);
			}
			if (type != null && type3 != null)
			{
				MethodInfo method = type.GetMethod("SwitchStage", new Type[1] { type3 });
				MethodInfo method2 = type.GetMethod("SwitchStage", new Type[2] { type3, type3 });
				if (method != null)
				{
					harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(BlockStageSwitchDuringRacePatch), "Prefix_SwitchStage1", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				if (method2 != null)
				{
					harmony.Patch((MethodBase)method2, new HarmonyMethod(typeof(BlockStageSwitchDuringRacePatch), "Prefix_SwitchStage2", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			if (type2 != null && type3 != null)
			{
				MethodInfo method3 = type2.GetMethod("ExitCurrentStage", new Type[2] { type3, type3 });
				if (method3 != null)
				{
					harmony.Patch((MethodBase)method3, new HarmonyMethod(typeof(BlockStageSwitchDuringRacePatch), "Prefix_ExitCurrentStage", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ChatUI), "ProcessLocalCommand")]
	public static class ChatCommandPatch
	{
		private static bool Prefix(ChatUI __instance, string message)
		{
			if (string.IsNullOrEmpty(message))
			{
				return true;
			}
			switch (message.ToLower())
			{
			case "/timeclear":
				HandleTimeClearCommand();
				return false;
			case "/teamtimeclear":
				HandleTeamTimeClearCommand();
				return false;
			case "/timeclearall":
				HandleTimeClearAllCommand();
				return false;
			case "/teamtimeclearall":
				HandleTeamTimeClearAllCommand();
				return false;
			case "/showbest":
				HandleShowBestCommand();
				return false;
			case "/gracestats":
				GRACEPlusPlugin.RunGraceStatsCommand();
				return false;
			case "/avgtime":
				HandleAvgTimeCommand();
				return false;
			case "/mapstats":
				GRACEPlusPlugin.RunMapStatsCommand();
				return false;
			case "/clearmapstats":
				HandleClearMapStatsCommand();
				return false;
			case "/clearmapstatsall":
				HandleClearMapStatsAllCommand();
				return false;
			case "/gracestatsclear":
				HandleGraceStatsClearCommand();
				return false;
			case "/gracehelp":
				GRACEPlusPlugin.RunGraceHelpCommand();
				return false;
			case "/end":
				HandleEndCommand();
				return false;
			case "/leave":
				HandleLeaveCommand();
				return false;
			case "/start":
				HandleStartCommand();
				return false;
			default:
				return true;
			}
		}

		private static void HandleTimeClearCommand()
		{
			//IL_0036: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Invalid comparison between Unknown and I4
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				GamemodeIDs gamemode = val.LobbyState.Gamemode;
				if ((int)gamemode == 1 || (int)gamemode == 2)
				{
					double? bestTime = GRACEPlusPlugin.GetBestTime(stage);
					if (bestTime.HasValue)
					{
						if (GRACEPlusPlugin.ClearBestTime(stage))
						{
							string arg = (((int)gamemode == 2) ? "Team GRACE" : "GRACE");
							GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared {arg} record for Stage {stage}! (Was: {bestTime.Value:F1}s)</color>");
						}
						else
						{
							GRACEPlusPlugin.ShowCommandResponse("<color=red>Failed to clear record!</color>");
						}
					}
					else
					{
						GRACEPlusPlugin.ShowCommandResponse($"<color=yellow>No record found for Stage {stage} to clear!</color>");
					}
				}
				else
				{
					GRACEPlusPlugin.ShowCommandResponse("<color=red>/timeclear can only be used in GRACE lobbies!</color>");
				}
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /timeclear!</color>");
			}
		}

		private static void HandleTimeClearAllCommand()
		{
			int num = GRACEPlusPlugin.ClearAllBestTimes();
			if (num > 0)
			{
				GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared ALL GRACE records! ({num} records deleted)</color>");
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>No records found to clear!</color>");
			}
		}

		private static void HandleTeamTimeClearCommand()
		{
			//IL_0036: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				GamemodeIDs gamemode = val.LobbyState.Gamemode;
				if ((int)gamemode == 1 || (int)gamemode == 2)
				{
					double? bestTime = GRACEPlusPlugin.GetBestTime(stage, isTeamMode: true);
					if (bestTime.HasValue)
					{
						if (GRACEPlusPlugin.ClearBestTime(stage, isTeamMode: true))
						{
							GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared Team GRACE record for Stage {stage}! (Was: {bestTime.Value:F1}s)</color>");
						}
						else
						{
							GRACEPlusPlugin.ShowCommandResponse("<color=red>Failed to clear team record!</color>");
						}
					}
					else
					{
						GRACEPlusPlugin.ShowCommandResponse($"<color=yellow>No team record found for Stage {stage} to clear!</color>");
					}
				}
				else
				{
					GRACEPlusPlugin.ShowCommandResponse("<color=red>/teamtimeclear can only be used in GRACE lobbies!</color>");
				}
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /teamtimeclear!</color>");
			}
		}

		private static void HandleTeamTimeClearAllCommand()
		{
			int num = GRACEPlusPlugin.ClearAllBestTimes(isTeamMode: true);
			if (num > 0)
			{
				GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared ALL Team GRACE records! ({num} records deleted)</color>");
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>No team records found to clear!</color>");
			}
		}

		private static void HandleShowBestCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				double? bestTime = GRACEPlusPlugin.GetBestTime(stage);
				double? bestTime2 = GRACEPlusPlugin.GetBestTime(stage, isTeamMode: true);
				if (bestTime.HasValue || bestTime2.HasValue)
				{
					GRACEPlusPlugin.ShowCommandResponse($"<color=green>=== Stage {stage} Best Times ===</color>");
					if (bestTime.HasValue)
					{
						GRACEPlusPlugin.ShowCommandResponse($"<color=green>Solo: {bestTime.Value:F1}s</color>");
					}
					else
					{
						GRACEPlusPlugin.ShowCommandResponse("<color=yellow>Solo: No record set</color>");
					}
					if (bestTime2.HasValue)
					{
						GRACEPlusPlugin.ShowCommandResponse($"<color=green>Team: {bestTime2.Value:F1}s</color>");
					}
					else
					{
						GRACEPlusPlugin.ShowCommandResponse("<color=yellow>Team: No record set</color>");
					}
				}
				else
				{
					GRACEPlusPlugin.ShowCommandResponse("<color=yellow>No best time saved yet for this map</color>");
				}
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /showbest!</color>");
			}
		}

		private static void HandleAvgTimeCommand()
		{
			if (!GRACEPlusPlugin.TrackMapStats.Value)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>Map statistics tracking is disabled! Enable 'Track Map Stats' in config.</color>");
				return;
			}
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				double? averageTime = GRACEPlusPlugin.GetAverageTime(stage);
				int winCount = GRACEPlusPlugin.GetWinCount(stage);
				if (averageTime.HasValue && winCount > 0)
				{
					GRACEPlusPlugin.ShowCommandResponse($"<color=green>Stage {stage} Average: {averageTime.Value:F1}s ({winCount} wins)</color>");
				}
				else
				{
					GRACEPlusPlugin.ShowCommandResponse($"<color=yellow>No win times recorded yet for Stage {stage}</color>");
				}
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /avgtime!</color>");
			}
		}

		private static void HandleClearMapStatsCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val != null)
			{
				int stage = val.LobbyState.Stage;
				if (GRACEPlusPlugin.ClearMapStats(stage))
				{
					GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared map statistics for Stage {stage}!</color>");
				}
				else
				{
					GRACEPlusPlugin.ShowCommandResponse("<color=yellow>There is no map stats to clear</color>");
				}
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /clearmapstats!</color>");
			}
		}

		private static void HandleClearMapStatsAllCommand()
		{
			int num = GRACEPlusPlugin.ClearAllMapStats();
			if (num > 0)
			{
				GRACEPlusPlugin.ShowCommandResponse($"<color=red>Cleared ALL map statistics! ({num} files deleted)</color>");
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>There are no map stats to clear</color>");
			}
		}

		private static void HandleGraceStatsClearCommand()
		{
			if (GRACEPlusPlugin.ClearCustomStats())
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>Cleared all custom GRACE statistics!</color>");
			}
			else
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>No custom GRACE statistics to clear</color>");
			}
		}

		private static void HandleEndCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val == null)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /end!</color>");
				return;
			}
			if (val.LobbyState.HostId != instance.LocalID)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>Only the host can end the round!</color>");
				return;
			}
			if (!val.InGame)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>No round is currently in progress!</color>");
				return;
			}
			instance.ClientLobbyManager.EndGame();
			GRACEPlusPlugin.ShowCommandResponse("<color=green>Ending the round...</color>");
		}

		private static void HandleLeaveCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			if (obj == null)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>You are not in a lobby!</color>");
				return;
			}
			instance.ClientLobbyManager.LeaveLobby();
			GRACEPlusPlugin.ShowCommandResponse("<color=green>Leaving lobby...</color>");
		}

		private static void HandleStartCommand()
		{
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			Lobby val = (Lobby)obj;
			if (val == null)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>You must be in a lobby to use /start!</color>");
				return;
			}
			if (val.LobbyState.HostId != instance.LocalID)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=red>Only the host can start the round!</color>");
				return;
			}
			if (val.InGame)
			{
				GRACEPlusPlugin.ShowCommandResponse("<color=yellow>A round is already in progress! Use /end to end it first.</color>");
				return;
			}
			instance.ClientLobbyManager.StartGame();
			GRACEPlusPlugin.ShowCommandResponse("<color=green>Starting the round...</color>");
		}
	}
	[HarmonyPatch(typeof(UIScreenIndicators), "Awake")]
	internal static class CustomGraffitiIndicatorPatch
	{
		private static readonly FieldInfo IndicatorField = typeof(UIScreenIndicators).GetField("_indicator", BindingFlags.Instance | BindingFlags.NonPublic);

		[HarmonyPostfix]
		private static void Postfix(UIScreenIndicators __instance)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			if ((Object)(object)__instance == (Object)null || IndicatorField == null)
			{
				return;
			}
			string customGraffitiSignPath = GRACEPlusPlugin.GetCustomGraffitiSignPath();
			if (string.IsNullOrEmpty(customGraffitiSignPath) || !File.Exists(customGraffitiSignPath))
			{
				return;
			}
			object? value = IndicatorField.GetValue(__instance);
			GameObject val = (GameObject)((value is GameObject) ? value : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			RawImage component = val.GetComponent<RawImage>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			try
			{
				byte[] array = File.ReadAllBytes(customGraffitiSignPath);
				Texture2D val2 = new Texture2D(2, 2);
				if (ImageConversion.LoadImage(val2, array))
				{
					((Texture)val2).filterMode = (FilterMode)1;
					val2.Apply();
					component.texture = (Texture)(object)val2;
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(GraffitiRace), "OnEnd")]
	internal class GraffitiRaceCancellationPatch
	{
		private static void Prefix(GraffitiRace __instance, bool cancelled)
		{
			GRACEPlusPlugin.SetGameCancelled(cancelled);
			GRACEPlusPlugin.DebugLog($"EVENT: Race ending - Cancelled: {cancelled}");
		}
	}
	[HarmonyPatch(typeof(GraffitiRace), "OnPacketReceived_InGame")]
	internal class GraffitiRaceTimerPatch
	{
		private static bool isRaceActive;

		private static void Postfix(GraffitiRace __instance, Packets packetId, Packet packet)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			if ((int)packetId == 19 && !isRaceActive)
			{
				isRaceActive = true;
				GRACEPlusPlugin.SetGameCancelled(cancelled: false);
				GRACEPlusPlugin.DebugLog("EVENT: Race started (ServerGamemodeBegin received)");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(GraffitiRace), "OnEnd")]
		private static void OnEnd_Postfix(GraffitiRace __instance)
		{
			if (!isRaceActive)
			{
				return;
			}
			double num = Math.Round(Traverse.Create((object)__instance).Field("_countdownTimer").GetValue<float>(), 1);
			isRaceActive = false;
			GRACEPlusPlugin.DebugLog($"EVENT: Race ended - Duration: {num}s (from BombRushMP timer)");
			ClientController instance = ClientController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager;
				obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null);
			}
			if (obj != null)
			{
				Lobby currentLobby = instance.ClientLobbyManager.CurrentLobby;
				ushort localID = instance.LocalID;
				int stage = currentLobby.LobbyState.Stage;
				GRACEPlusPlugin.DebugLog($"INFO: Local Player ID: {localID}, Stage ID: {stage}");
				GRACEPlusPlugin.DebugLog($"INFO: Team Based: {((Gamemode)__instance).TeamBased}");
				bool flag = false;
				int maxPossibleScore = GRACEPlusPlugin.GetMaxPossibleScore();
				GRACEPlusPlugin.DebugLog($"INFO: Max Possible Score: {maxPossibleScore}");
				if (maxPossibleScore > 0)
				{
					if (((Gamemode)__instance).TeamBased)
					{
						if (currentLobby.LobbyState.Players.ContainsKey(localID))
						{
							byte team = currentLobby.LobbyState.Players[localID].Team;
							float scoreForTeam = currentLobby.LobbyState.GetScoreForTeam(team);
							GRACEPlusPlugin.DebugLog($"INFO: User Team: {team}, Team Score: {scoreForTeam}");
							flag = scoreForTeam >= (float)maxPossibleScore && scoreForTeam > 0f;
							GRACEPlusPlugin.DebugLog($"CALC: userTeamScore ({scoreForTeam}) >= maxPossibleScore ({maxPossibleScore}) && userTeamScore > 0 = {flag}");
						}
						else
						{
							GRACEPlusPlugin.DebugLog($"ERROR: Local player ID {localID} not found in lobby players!");
						}
					}
					else if (currentLobby.LobbyState.Players.ContainsKey(localID))
					{
						float score = currentLobby.LobbyState.Players[localID].Score;
						GRACEPlusPlugin.DebugLog($"INFO: User Score: {score}");
						flag = score >= (float)maxPossibleScore && score > 0f;
						GRACEPlusPlugin.DebugLog($"CALC: userScore ({score}) >= maxPossibleScore ({maxPossibleScore}) && userScore > 0 = {flag}");
					}
					else
					{
						GRACEPlusPlugin.DebugLog($"ERROR: Local player ID {localID} not found in lobby players!");
					}
				}
				else
				{
					flag = false;
					GRACEPlusPlugin.DebugLog("CALC: maxPossibleScore is 0, setting userWon = false");
				}
				GRACEPlusPlugin.DebugLog($"RESULT: User Won = {flag}");
				string text = (((Gamemode)__instance).TeamBased ? "Team " : "");
				if (flag)
				{
					double? previousBest;
					bool flag2 = GRACEPlusPlugin.CheckAndUpdateBestTime(stage, num, ((Gamemode)__instance).TeamBased, out previousBest);
					GRACEPlusPlugin.SaveMatchResult(stage, num, won: true, ((Gamemode)__instance).TeamBased, flag2, previousBest);
					if (GRACEPlusPlugin.TrackMapStats.Value)
					{
						bool isMultiplayer = currentLobby.LobbyState.Players.Count > 1;
						GRACEPlusPlugin.SaveMapStats(stage, num, won: true, isMultiplayer, ((Gamemode)__instance).TeamBased);
					}
					if (!GRACEPlusPlugin.IsGameCancelled())
					{
						GRACEPlusPlugin.IncrementCustomWins(currentLobby.LobbyState.Players.Count == 1, ((Gamemode)__instance).TeamBased);
					}
					if (flag2)
					{
						if (previousBest.HasValue)
						{
							GRACEPlusPlugin.ShowConfigurableMessage(GRACEPlusPlugin.NewRecordMessage, GRACEPlusPlugin.NewRecordColor, ("gamemode", text.ToUpper()), ("time", num.ToString()), ("stage", stage.ToString()), ("previous", previousBest.Value.ToString("F1")));
						}
						else
						{
							GRACEPlusPlugin.ShowConfigurableMessage(GRACEPlusPlugin.FirstWinMessage, GRACEPlusPlugin.FirstWinColor, ("gamemode", text.ToUpper()), ("time", num.ToString()), ("stage", stage.ToString()));
						}
					}
					else
					{
						double? bestTime = GRACEPlusPlugin.GetBestTime(stage, ((Gamemode)__instance).TeamBased);
						GRACEPlusPlugin.ShowConfigurableMessage(GRACEPlusPlugin.WinMessage, GRACEPlusPlugin.WinColor, ("gamemode", text), ("time", num.ToString()), ("best", bestTime?.ToString("F1") ?? "N/A"));
					}
				}
				else
				{
					GRACEPlusPlugin.SaveMatchResult(stage, num, won: false, ((Gamemode)__instance).TeamBased, isNewRecord: false, null);
					if (GRACEPlusPlugin.TrackMapStats.Value)
					{
						bool isMultiplayer2 = currentLobby.LobbyState.Players.Count > 1;
						GRACEPlusPlugin.SaveMapStats(stage, num, won: false, isMultiplayer2, ((Gamemode)__instance).TeamBased);
					}
					if (!GRACEPlusPlugin.IsGameCancelled())
					{
						GRACEPlusPlugin.IncrementCustomLosses(((Gamemode)__instance).TeamBased);
					}
					GRACEPlusPlugin.ShowConfigurableMessage(GRACEPlusPlugin.LoseMessage, GRACEPlusPlugin.LoseColor, ("gamemode", text), ("time", num.ToString()));
				}
			}
			else
			{
				string arg = (((Gamemode)__instance).TeamBased ? "Team " : "");
				GRACEPlusPlugin.ShowNotification($"<color=green>{arg}GRACE Ended IN {num} Seconds!</color>");
				GRACEPlusPlugin.DebugLog("INFO: No lobby data available, showing basic message");
			}
			GRACEPlusPlugin.ResetMaxPossibleScore();
			GRACEPlusPlugin.DebugLog("EVENT: Max possible score reset to 0");
			GRACEPlusPlugin.ScheduleAutoStartIfEnabled();
		}
	}
	[HarmonyPatch(typeof(GraffitiRace), "OnReceive_GraffitiRaceData")]
	internal class GraffitiRaceDataReceivePatch
	{
		private static void Postfix(GraffitiRace __instance, ClientGraffitiRaceGSpots packet)
		{
			int num = GRACEPlusPlugin.GetMaxPossibleScore() + packet.GraffitiSpots.Count;
			GRACEPlusPlugin.SetMaxPossibleScore(num);
			GRACEPlusPlugin.DebugLog($"EVENT: OnReceive_GraffitiRaceData - Added {packet.GraffitiSpots.Count} spots, Total max score: {num}");
		}
	}
	[HarmonyPatch(typeof(GraffitiRace))]
	internal static class GraffitiOutlinePatch
	{
		private static readonly int ShaderPropColor = Shader.PropertyToID("_Color");

		private static readonly int ShaderPropBaseColor = Shader.PropertyToID("_BaseColor");

		internal static readonly FieldInfo OriginalProgressField = typeof(GraffitiRace).GetField("_originalProgress", BindingFlags.Instance | BindingFlags.NonPublic);

		[HarmonyPostfix]
		[HarmonyPatch("OnReceive_GraffitiRaceData", new Type[] { typeof(ClientGraffitiRaceGSpots) })]
		private static void OnReceive_GraffitiRaceData_Postfix(GraffitiRace __instance)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			Color? effectiveOutlineColor = GRACEPlusPlugin.GetEffectiveOutlineColor();
			if (!effectiveOutlineColor.HasValue)
			{
				return;
			}
			object obj = OriginalProgressField?.GetValue(__instance);
			if (obj == null || !(obj is IDictionary dictionary))
			{
				return;
			}
			Color value = effectiveOutlineColor.Value;
			foreach (DictionaryEntry item in dictionary)
			{
				object key = item.Key;
				GraffitiSpot val = (GraffitiSpot)((key is GraffitiSpot) ? key : null);
				if (val != null && !((Object)(object)val == (Object)null))
				{
					ApplyOutlineColorToSpot(val, value);
				}
			}
			EnsureOnEnableReapplyOnRaceSpots(__instance, dictionary);
		}

		private static void EnsureOnEnableReapplyOnRaceSpots(GraffitiRace grafRace, IDictionary idict)
		{
			foreach (DictionaryEntry item in idict)
			{
				object key = item.Key;
				GraffitiSpot val = (GraffitiSpot)((key is GraffitiSpot) ? key : null);
				if (val != null && !((Object)(object)((val != null) ? ((Component)val).gameObject : null) == (Object)null) && !((Object)(object)((Component)val).gameObject.GetComponent<GraffitiOutlineOnEnableReapply>() != (Object)null))
				{
					((Component)val).gameObject.AddComponent<GraffitiOutlineOnEnableReapply>().SetRace(grafRace);
				}
			}
		}

		internal static void ApplyOutlineColorToSpot(GraffitiSpot spot, Color color)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)((spot != null) ? ((Component)spot).gameObject : null) == (Object)null)
			{
				return;
			}
			Renderer[] componentsInChildren = ((Component)spot).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.material == (Object)null))
				{
					Material material = val.material;
					int num = -1;
					if (material.HasProperty(ShaderPropColor))
					{
						num = ShaderPropColor;
					}
					else if (material.HasProperty(ShaderPropBaseColor))
					{
						num = ShaderPropBaseColor;
					}
					if (num >= 0)
					{
						material.SetColor(num, color);
					}
				}
			}
		}
	}
	internal sealed class GraffitiOutlineOnEnableReapply : MonoBehaviour
	{
		private GraffitiRace _race;

		public void SetRace(GraffitiRace grafRace)
		{
			_race = grafRace;
		}

		private void OnEnable()
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			Color? effectiveOutlineColor = GRACEPlusPlugin.GetEffectiveOutlineColor();
			if (!effectiveOutlineColor.HasValue || _race == null)
			{
				return;
			}
			GraffitiSpot component = ((Component)this).GetComponent<GraffitiSpot>();
			if (!((Object)(object)component == (Object)null))
			{
				object obj = GraffitiOutlinePatch.OriginalProgressField?.GetValue(_race);
				if (obj != null && obj is IDictionary dictionary && dictionary.Contains(component))
				{
					GraffitiOutlinePatch.ApplyOutlineColorToSpot(component, effectiveOutlineColor.Value);
				}
			}
		}
	}
	[HarmonyPatch(typeof(LobbyUI), "UpdateUI")]
	internal class LobbyUIUpdatePatch
	{
		private static void Postfix(LobbyUI __instance)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				FieldInfo field = typeof(LobbyUI).GetField("_canvas", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field == null || (Object)/*isinst with value type is only supported in some contexts*/ == (Object)null || !GRACEPlusPlugin.UseCustomLobbyName.Value)
				{
					return;
				}
				FieldInfo field2 = typeof(LobbyUI).GetField("_lobbyName", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field2 != null)
				{
					object? value = field2.GetValue(__instance);
					TextMeshProUGUI val = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
					if ((Object)(object)val != (Object)null)
					{
						((TMP_Text)val).text = GRACEPlusPlugin.LobbyNameText.Value;
						((Graphic)val).color = GRACEPlusPlugin.ParseColor(GRACEPlusPlugin.LobbyNameColor.Value);
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(LobbyPlayerUI), "SetPlayer")]
	internal class LobbyPlayerUISetPlayerPatch
	{
		private static void Postfix(LobbyPlayerUI __instance)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object? obj = typeof(LobbyPlayerUI).GetField("_score", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(__instance);
				TextMeshProUGUI val = (TextMeshProUGUI)((obj is TextMeshProUGUI) ? obj : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				if (GRACEPlusPlugin.UseCustomScoreTextColor.Value)
				{
					((Graphic)val).color = GRACEPlusPlugin.ParseColor(GRACEPlusPlugin.ScoreTextColor.Value);
				}
				object obj2 = typeof(LobbyPlayerUI).GetField("_lobbyPlayer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(__instance);
				if (obj2 == null)
				{
					return;
				}
				FieldInfo field = obj2.GetType().GetField("Wins", BindingFlags.Instance | BindingFlags.Public);
				int num = ((field != null) ? ((int)(field.GetValue(obj2) ?? ((object)0))) : 0);
				string text = ((TMP_Text)val).text;
				string text2 = $" ({num})";
				if (text == null || !text.EndsWith(")"))
				{
					return;
				}
				int num2 = text.LastIndexOf(" (", StringComparison.Ordinal);
				if (num2 >= 0 && text.Substring(num2) == text2)
				{
					if (!GRACEPlusPlugin.ShowLobbyWinCounter.Value)
					{
						((TMP_Text)val).text = text.Substring(0, num2);
					}
					else if (GRACEPlusPlugin.UseCustomWinCounterColor.Value)
					{
						string text3 = ColorUtility.ToHtmlStringRGB(GRACEPlusPlugin.ParseColorRgb(GRACEPlusPlugin.WinCounterColor?.Value ?? "255,200,100"));
						((TMP_Text)val).text = text.Substring(0, num2) + " <color=#" + text3 + ">" + text2 + "</color>";
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(LobbyPlayerUI), "SetTeam")]
	internal class LobbyPlayerUISetTeamPatch
	{
		private static void Postfix(LobbyPlayerUI __instance)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				FieldInfo field = typeof(LobbyPlayerUI).GetField("_score", BindingFlags.Instance | BindingFlags.NonPublic);
				if (GRACEPlusPlugin.UseCustomScoreTextColor.Value && field != null)
				{
					object? value = field.GetValue(__instance);
					TextMeshProUGUI val = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
					if ((Object)(object)val != (Object)null)
					{
						((Graphic)val).color = GRACEPlusPlugin.ParseColor(GRACEPlusPlugin.ScoreTextColor.Value);
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(GraffitiRace), "OnPacketReceived_InGame")]
	internal