Decompiled source of SceneSwitcher v1.0.0

SceneSwitcher.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using SceneSwitcher.Core;
using SceneSwitcher.Ui;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("SceneSwitcher")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SceneSwitcher")]
[assembly: AssemblyTitle("SceneSwitcher")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace SceneSwitcher
{
	internal static class CrashReporter
	{
		public const string ErrorCode = "CRM001 - Crimson's hamster stopped running";

		public static void Report(Exception exception, string context)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogError((object)string.Format("{0} [{1}]\n{2}", "CRM001 - Crimson's hamster stopped running", context, exception));
			}
			try
			{
				Feedback.Fail("CRM001 - Crimson's hamster stopped running");
			}
			catch
			{
			}
		}

		public static bool Guard(string context, Action action)
		{
			try
			{
				action();
				return true;
			}
			catch (Exception exception)
			{
				Report(exception, context);
				return false;
			}
		}
	}
	internal static class Feedback
	{
		public static void Ok(string message)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Show(message, (MenuClickEffectType)1, Color.green);
		}

		public static void Info(string message, float time = 3f)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Show(message, (MenuClickEffectType)1, Color.white, time);
		}

		public static void Fail(string message)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Show(message, (MenuClickEffectType)2, Color.red);
		}

		private static void Show(string message, MenuClickEffectType effect, Color flash, float time = 3f)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			Plugin.Log.LogInfo((object)("/scene: " + message));
			if (Object.op_Implicit((Object)(object)MenuManager.instance))
			{
				MenuManager.instance.MenuEffectClick(effect, (MenuPage)null, 1f, 1f, true, true);
			}
			if (Object.op_Implicit((Object)(object)MissionUI.instance))
			{
				SemiFunc.UIFocusText(message, Color.white, flash, time);
			}
		}
	}
	[BepInPlugin("abzu.SceneSwitcher", "SceneSwitcher", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "abzu.SceneSwitcher";

		public const string PluginName = "SceneSwitcher";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			if (CrashReporter.Guard("startup", delegate
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Expected O, but got Unknown
				_harmony = new Harmony("abzu.SceneSwitcher");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				Settings.Bind(((BaseUnityPlugin)this).Config);
				SceneWindow.Create(Settings.WindowToggleKey);
			}))
			{
				Log.LogInfo((object)"SceneSwitcher v1.0.0 loaded.");
				Log.LogInfo((object)"Todo inició correctamente, chupala Crimson");
			}
		}
	}
	internal static class SceneLauncher
	{
		public static void ReturnToLobbyMenu()
		{
			if (CheckCanSwitch())
			{
				Feedback.Ok("Returning to the lobby menu...");
				SceneSwitchService.ReturnToLobbyMenu();
			}
		}

		public static void Launch(Level level, int? levelNumber, string targetName = null)
		{
			if (CheckCanSwitch())
			{
				if (!Object.op_Implicit((Object)(object)level))
				{
					Feedback.Fail("Level not available.");
					return;
				}
				string text = targetName ?? LevelNameResolver.DisplayName(((Object)level).name);
				string text2 = (levelNumber.HasValue ? $" (level {levelNumber.Value})" : string.Empty);
				Feedback.Ok("Launching " + text + text2 + "...");
				SceneSwitchService.LaunchLevel(level, levelNumber);
			}
		}

		public static void LaunchRandom(Level level, int? levelNumber, string kind)
		{
			if (!Object.op_Implicit((Object)(object)level))
			{
				Feedback.Fail("No " + kind + " available.");
			}
			else
			{
				Launch(level, levelNumber);
			}
		}

		private static bool CheckCanSwitch()
		{
			if (SceneSwitchService.CanSwitch(out var reason))
			{
				return true;
			}
			Feedback.Fail(reason);
			return false;
		}
	}
	internal static class SceneSwitchService
	{
		private static readonly MethodInfo ResetLevelProgressMethod = AccessTools.Method(typeof(DebugCommandHandler), "ResetLevelProgress", (Type[])null, (Type[])null);

		public static IEnumerable<Level> Maps => RunManager.instance.levels.Where((Level l) => Object.op_Implicit((Object)(object)l));

		public static IEnumerable<Level> Arenas => RunManager.instance.levelArena.Where((Level l) => Object.op_Implicit((Object)(object)l));

		public static IEnumerable<Level> Shops => RunManager.instance.levelShop.Where((Level l) => Object.op_Implicit((Object)(object)l));

		public static Level Truck => RunManager.instance.levelLobby;

		public static bool CanSwitch(out string reason)
		{
			RunManager instance = RunManager.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				reason = "Game is not ready.";
				return false;
			}
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				reason = "Only the host can change scenes.";
				return false;
			}
			if (SemiFunc.IsSplashScreen() || SemiFunc.IsMainMenu() || SemiFunc.RunIsTutorial())
			{
				reason = "Not available in this scene.";
				return false;
			}
			if (instance.restarting)
			{
				reason = "A scene change is already in progress.";
				return false;
			}
			reason = null;
			return true;
		}

		public static void ReturnToLobbyMenu()
		{
			RunManager instance = RunManager.instance;
			if (Settings.Progress.Value == ProgressMode.RevertToLastSave)
			{
				ResetLevelProgress();
			}
			if (!SemiFunc.RunIsLobbyMenu())
			{
				instance.loadLevel = instance.saveLevel;
			}
			if (SemiFunc.RunIsArena())
			{
				instance.levelCurrent = instance.levelLobby;
			}
			instance.ChangeLevel(false, false, (ChangeLevelType)3);
		}

		public static void LaunchLevel(Level level, int? levelNumber)
		{
			RunManager instance = RunManager.instance;
			if (Settings.Progress.Value != ProgressMode.KeepCurrent && (!SemiFunc.IsLevelShop(level) || !SemiFunc.RunIsLevel()))
			{
				ResetLevelProgress();
			}
			if (levelNumber.HasValue)
			{
				instance.levelsCompleted = levelNumber.Value - 1;
				SemiFunc.StatSetRunLevel(instance.levelsCompleted);
			}
			instance.debugLevel = level;
			try
			{
				instance.ChangeLevel(false, false, (ChangeLevelType)0);
			}
			finally
			{
				instance.debugLevel = null;
			}
		}

		public static Level PickRandomMap()
		{
			return PickRandom(Maps);
		}

		public static Level PickRandomShop()
		{
			return PickRandom(Shops);
		}

		private static Level PickRandom(IEnumerable<Level> levels)
		{
			List<Level> list = levels.ToList();
			List<Level> list2 = list.Where((Level l) => (Object)(object)l != (Object)(object)RunManager.instance.levelCurrent).ToList();
			List<Level> list3 = ((list2.Count > 0) ? list2 : list);
			if (list3.Count <= 0)
			{
				return null;
			}
			return list3[Random.Range(0, list3.Count)];
		}

		private static void ResetLevelProgress()
		{
			if (Object.op_Implicit((Object)(object)DebugCommandHandler.instance) && ResetLevelProgressMethod != null)
			{
				ResetLevelProgressMethod.Invoke(DebugCommandHandler.instance, null);
			}
			else
			{
				Plugin.Log.LogWarning((object)"DebugCommandHandler.ResetLevelProgress not available; progress not reset.");
			}
		}
	}
	internal enum ProgressMode
	{
		RevertToLastSave,
		KeepCurrent
	}
	internal static class Settings
	{
		public static ConfigEntry<Key> WindowToggleKey { get; private set; }

		public static ConfigEntry<ProgressMode> Progress { get; private set; }

		public static void Bind(ConfigFile config)
		{
			WindowToggleKey = config.Bind<Key>("Window", "ToggleKey", (Key)70, "Key that opens/closes the Scene Switcher window (host only). Set to None to disable the window.");
			Progress = config.Bind<ProgressMode>("Saves", "ProgressOnSceneChange", ProgressMode.RevertToLastSave, "What happens to the run's progress in saved (private) lobbies when you switch scene. RevertToLastSave: go back to the last save first. KeepCurrent: keep money, upgrades and level as they are now. Going from a map to a shop always keeps progress. Arenas are the Game Over screen: entering one deletes the save file, as in vanilla.");
		}
	}
}
namespace SceneSwitcher.Ui
{
	internal sealed class SceneWindow : MonoBehaviour
	{
		private const int MaxLevel = 50;

		private const float ReferenceHeight = 1080f;

		private const float BlockTime = 0.1f;

		private static readonly List<InputKey> NoControls = new List<InputKey>();

		private ConfigEntry<Key> _toggleKey;

		private bool _visible;

		private Rect _windowRect = new Rect(40f, 40f, 340f, 640f);

		private Vector2 _scroll;

		private int _level;

		private int? SelectedLevel
		{
			get
			{
				if (_level != 0)
				{
					return _level;
				}
				return null;
			}
		}

		public static void Create(ConfigEntry<Key> toggleKey)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SceneSwitcher.SceneWindow");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<SceneWindow>()._toggleKey = toggleKey;
		}

		private void Update()
		{
			if (!CrashReporter.Guard("window", UpdateWindow))
			{
				Shutdown();
			}
		}

		private void Shutdown()
		{
			Close();
			((Behaviour)this).enabled = false;
		}

		private void UpdateWindow()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return;
			}
			Key value = _toggleKey.Value;
			if ((int)value != 0 && ((ButtonControl)current[value]).wasPressedThisFrame && SemiFunc.NoTextInputsActive())
			{
				Toggle();
			}
			if (_visible)
			{
				if (((ButtonControl)current.escapeKey).wasPressedThisFrame || !SceneSwitchService.CanSwitch(out var _))
				{
					Close();
				}
				else
				{
					BlockGame();
				}
			}
		}

		private void Toggle()
		{
			if (_visible)
			{
				Close();
				return;
			}
			if (!SceneSwitchService.CanSwitch(out var reason))
			{
				Feedback.Fail(reason);
				return;
			}
			_visible = true;
			BlockGame();
		}

		private void Close()
		{
			_visible = false;
			Cursor.visible = false;
		}

		private static void BlockGame()
		{
			if (Object.op_Implicit((Object)(object)CursorManager.instance))
			{
				CursorManager.instance.Unlock(0.1f);
			}
			Cursor.visible = true;
			if (Object.op_Implicit((Object)(object)MenuCursor.instance))
			{
				MenuCursor.instance.OverrideHide(0.1f);
			}
			if (Object.op_Implicit((Object)(object)InputManager.instance))
			{
				InputManager.instance.DisableMovement(0.1f);
				InputManager.instance.DisableAiming(0.1f);
				InputManager.instance.DisableControlsExcept(0.1f, NoControls);
			}
		}

		private void OnGUI()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (_visible)
			{
				float num = (float)Screen.height / 1080f;
				GUI.matrix = Matrix4x4.Scale(new Vector3(num, num, 1f));
				_windowRect = GUILayout.Window(((Object)this).GetInstanceID(), _windowRect, new WindowFunction(DrawWindow), "Scene Switcher (host)", Array.Empty<GUILayoutOption>());
			}
		}

		private void DrawWindow(int windowId)
		{
			try
			{
				DrawWindowContents();
			}
			catch (Exception ex) when (!(ex is ExitGUIException))
			{
				CrashReporter.Report(ex, "window GUI");
				Shutdown();
			}
		}

		private void DrawWindowContents()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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)
			GUILayout.Label((_level == 0) ? "Level: keep current" : $"Level: {_level}", Array.Empty<GUILayoutOption>());
			_level = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)_level, 0f, 50f, Array.Empty<GUILayoutOption>()));
			GUILayout.Space(6f);
			_scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Lobby menu", Array.Empty<GUILayoutOption>()))
			{
				Run(SceneLauncher.ReturnToLobbyMenu);
			}
			if (GUILayout.Button("Truck", Array.Empty<GUILayoutOption>()))
			{
				Run(delegate
				{
					SceneLauncher.Launch(SceneSwitchService.Truck, SelectedLevel, "the truck");
				});
			}
			Section("Shops");
			if (GUILayout.Button("Random shop", Array.Empty<GUILayoutOption>()))
			{
				Run(delegate
				{
					SceneLauncher.LaunchRandom(SceneSwitchService.PickRandomShop(), SelectedLevel, "shops");
				});
			}
			foreach (Level shop in SceneSwitchService.Shops)
			{
				if (GUILayout.Button(LevelNameResolver.DisplayName(((Object)shop).name), Array.Empty<GUILayoutOption>()))
				{
					Run(delegate
					{
						SceneLauncher.Launch(shop, SelectedLevel);
					});
				}
			}
			Section("Maps");
			if (GUILayout.Button("Random map", Array.Empty<GUILayoutOption>()))
			{
				Run(delegate
				{
					SceneLauncher.LaunchRandom(SceneSwitchService.PickRandomMap(), SelectedLevel, "maps");
				});
			}
			foreach (Level map in SceneSwitchService.Maps)
			{
				if (GUILayout.Button(LevelNameResolver.DisplayName(((Object)map).name), Array.Empty<GUILayoutOption>()))
				{
					Run(delegate
					{
						SceneLauncher.Launch(map, SelectedLevel);
					});
				}
			}
			Section("Arenas");
			foreach (Level arena in SceneSwitchService.Arenas)
			{
				if (GUILayout.Button(LevelNameResolver.DisplayName(((Object)arena).name), Array.Empty<GUILayoutOption>()))
				{
					Run(delegate
					{
						SceneLauncher.Launch(arena, null);
					});
				}
			}
			GUILayout.EndScrollView();
			if (GUILayout.Button("Close", Array.Empty<GUILayoutOption>()))
			{
				Close();
			}
			GUI.DragWindow();
		}

		private static void Section(string title)
		{
			GUILayout.Space(8f);
			GUILayout.Label(title, Array.Empty<GUILayoutOption>());
		}

		private void Run(Action action)
		{
			Close();
			CrashReporter.Guard("window button", action);
		}
	}
}
namespace SceneSwitcher.Patches
{
	[HarmonyPatch(typeof(LevelGenerator), "GenerateDoneApply")]
	internal static class LevelCatalogLogger
	{
		private static bool _logged;

		[HarmonyPostfix]
		private static void Postfix()
		{
			if (!_logged && !((Object)(object)RunManager.instance == (Object)null))
			{
				_logged = true;
				CrashReporter.Guard("level catalog", LogCatalog);
			}
		}

		private static void LogCatalog()
		{
			RunManager instance = RunManager.instance;
			ManualLogSource log = Plugin.Log;
			Level levelCurrent = instance.levelCurrent;
			log.LogInfo((object)$"Current level: {((levelCurrent != null) ? ((Object)levelCurrent).name : null)} | levelsCompleted: {instance.levelsCompleted}");
			ManualLogSource log2 = Plugin.Log;
			Level levelLobby = instance.levelLobby;
			string obj = ((levelLobby != null) ? ((Object)levelLobby).name : null);
			Level levelLobbyMenu = instance.levelLobbyMenu;
			log2.LogInfo((object)("Lobby: " + obj + " | Lobby menu: " + ((levelLobbyMenu != null) ? ((Object)levelLobbyMenu).name : null)));
			Plugin.Log.LogInfo((object)("Arenas: " + string.Join(", ", from l in instance.levelArena
				where Object.op_Implicit((Object)(object)l)
				select ((Object)l).name)));
			Plugin.Log.LogInfo((object)("Shops: " + string.Join(", ", from l in instance.levelShop
				where Object.op_Implicit((Object)(object)l)
				select ((Object)l).name)));
			Plugin.Log.LogInfo((object)("Run levels: " + string.Join(", ", from l in instance.levels
				where Object.op_Implicit((Object)(object)l)
				select ((Object)l).name)));
		}
	}
}
namespace SceneSwitcher.Core
{
	public enum ResolveStatus
	{
		Found,
		NotFound,
		Ambiguous
	}
	public sealed class ResolveResult
	{
		public ResolveStatus Status { get; }

		public string Match { get; }

		public IReadOnlyList<string> Candidates { get; }

		private ResolveResult(ResolveStatus status, string match, IReadOnlyList<string> candidates)
		{
			Status = status;
			Match = match;
			Candidates = candidates;
		}

		public static ResolveResult Found(string match)
		{
			return new ResolveResult(ResolveStatus.Found, match, Array.Empty<string>());
		}

		public static ResolveResult NotFound()
		{
			return new ResolveResult(ResolveStatus.NotFound, null, Array.Empty<string>());
		}

		public static ResolveResult Ambiguous(IReadOnlyList<string> candidates)
		{
			return new ResolveResult(ResolveStatus.Ambiguous, null, candidates);
		}
	}
	public static class LevelNameResolver
	{
		public const string LevelPrefix = "Level - ";

		public static string DisplayName(string levelName)
		{
			if (string.IsNullOrEmpty(levelName))
			{
				return string.Empty;
			}
			if (!levelName.StartsWith("Level - ", StringComparison.OrdinalIgnoreCase))
			{
				return levelName;
			}
			return levelName.Substring("Level - ".Length);
		}

		public static string Normalize(string name)
		{
			string[] value = DisplayName(name?.Trim()).Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			return string.Join(" ", value).ToLowerInvariant();
		}

		public static ResolveResult Resolve(string query, IEnumerable<string> levelNames)
		{
			string normalizedQuery = Normalize(query);
			if (normalizedQuery.Length == 0)
			{
				return ResolveResult.NotFound();
			}
			List<string> source = levelNames.Where((string n) => !string.IsNullOrEmpty(n)).Distinct().ToList();
			List<string> list = source.Where((string n) => Normalize(n) == normalizedQuery).ToList();
			if (list.Count > 0)
			{
				if (list.Count != 1)
				{
					return ResolveResult.Ambiguous(list);
				}
				return ResolveResult.Found(list[0]);
			}
			List<string> list2 = source.Where((string n) => Normalize(n).StartsWith(normalizedQuery, StringComparison.Ordinal)).ToList();
			if (list2.Count > 0)
			{
				if (list2.Count != 1)
				{
					return ResolveResult.Ambiguous(list2);
				}
				return ResolveResult.Found(list2[0]);
			}
			List<string> list3 = source.Where((string n) => Normalize(n).Contains(normalizedQuery)).ToList();
			if (list3.Count > 0)
			{
				if (list3.Count != 1)
				{
					return ResolveResult.Ambiguous(list3);
				}
				return ResolveResult.Found(list3[0]);
			}
			return ResolveResult.NotFound();
		}
	}
	public enum SceneAction
	{
		Help,
		List,
		LobbyMenu,
		Truck,
		Arena,
		Map,
		RandomMap,
		Shop,
		RandomShop,
		Crimson
	}
	public sealed class SceneRequest
	{
		public SceneAction Action { get; set; }

		public string Query { get; set; }

		public string RawQuery { get; set; }

		public int? LevelNumber { get; set; }

		public string Error { get; set; }
	}
	public static class SceneCommandParser
	{
		public const string Usage = "/scene lobby | truck [level] | shop [zone|random] [level] | map <name|random> [level] | arena <fight|race> | list";

		private const string ShopPrefix = "Shop ";

		private static readonly Dictionary<string, string> ArenaAliases = new Dictionary<string, string>
		{
			{ "fight", "Arena Fight" },
			{ "disposal", "Arena Fight" },
			{ "race", "Arena Race" },
			{ "cars", "Arena Race" },
			{ "race cars", "Arena Race" },
			{ "racecars", "Arena Race" }
		};

		public static SceneRequest Parse(string[] args)
		{
			if (args == null || args.Length == 0)
			{
				return new SceneRequest
				{
					Action = SceneAction.Help
				};
			}
			string text = args[0].ToLowerInvariant();
			string[] rest = args.Skip(1).ToArray();
			switch (text)
			{
			case "help":
				return new SceneRequest
				{
					Action = SceneAction.Help
				};
			case "crimson":
				return new SceneRequest
				{
					Action = SceneAction.Crimson
				};
			case "list":
				return new SceneRequest
				{
					Action = SceneAction.List
				};
			case "lobby":
			case "menu":
				return new SceneRequest
				{
					Action = SceneAction.LobbyMenu
				};
			case "truck":
				return ParseTruck(rest);
			case "arena":
				return ParseArena(rest);
			case "map":
				return ParseMap(rest);
			case "shop":
				return ParseShop(rest);
			default:
				return new SceneRequest
				{
					Action = SceneAction.Help,
					Error = "Unknown option '" + args[0] + "'."
				};
			}
		}

		private static SceneRequest ParseArena(string[] rest)
		{
			if (rest.Length == 0)
			{
				return new SceneRequest
				{
					Action = SceneAction.Arena,
					Error = "Missing arena: fight or race."
				};
			}
			string text = string.Join(" ", rest);
			if (ArenaAliases.TryGetValue(text.ToLowerInvariant(), out var value))
			{
				text = value;
			}
			return new SceneRequest
			{
				Action = SceneAction.Arena,
				Query = text,
				RawQuery = text
			};
		}

		private static SceneRequest ParseTruck(string[] rest)
		{
			SceneRequest sceneRequest = new SceneRequest
			{
				Action = SceneAction.Truck
			};
			if (rest.Length > 1 || (rest.Length == 1 && !int.TryParse(rest[0], out var _)))
			{
				sceneRequest.Error = "Usage: /scene truck [level].";
				return sceneRequest;
			}
			if (rest.Length == 1)
			{
				SetLevelNumber(sceneRequest, int.Parse(rest[0]));
			}
			return sceneRequest;
		}

		private static SceneRequest ParseMap(string[] rest)
		{
			if (rest.Length == 0)
			{
				return new SceneRequest
				{
					Action = SceneAction.Map,
					Error = "Missing map name."
				};
			}
			return ParseNamedTarget(rest, SceneAction.Map, SceneAction.RandomMap);
		}

		private static SceneRequest ParseShop(string[] rest)
		{
			if (rest.Length == 0)
			{
				return new SceneRequest
				{
					Action = SceneAction.RandomShop
				};
			}
			if (rest.Length == 1 && int.TryParse(rest[0], out var result))
			{
				SceneRequest obj = new SceneRequest
				{
					Action = SceneAction.RandomShop
				};
				SetLevelNumber(obj, result);
				return obj;
			}
			SceneRequest sceneRequest = ParseNamedTarget(rest, SceneAction.Shop, SceneAction.RandomShop);
			if (sceneRequest.Error == null && sceneRequest.Action == SceneAction.Shop)
			{
				sceneRequest.Query = WithShopPrefix(sceneRequest.Query);
				sceneRequest.RawQuery = WithShopPrefix(sceneRequest.RawQuery);
			}
			return sceneRequest;
		}

		private static SceneRequest ParseNamedTarget(string[] rest, SceneAction action, SceneAction randomAction)
		{
			SceneRequest sceneRequest = new SceneRequest
			{
				Action = action,
				RawQuery = string.Join(" ", rest)
			};
			string[] value = rest;
			if (rest.Length > 1 && int.TryParse(rest[^1], out var result))
			{
				if (!SetLevelNumber(sceneRequest, result))
				{
					return sceneRequest;
				}
				value = rest.Take(rest.Length - 1).ToArray();
			}
			sceneRequest.Query = string.Join(" ", value);
			if (sceneRequest.Query.Equals("random", StringComparison.OrdinalIgnoreCase))
			{
				sceneRequest.Action = randomAction;
			}
			return sceneRequest;
		}

		private static bool SetLevelNumber(SceneRequest request, int number)
		{
			if (number < 1)
			{
				request.Error = "Level number must be 1 or higher.";
				return false;
			}
			request.LevelNumber = number;
			return true;
		}

		private static string WithShopPrefix(string query)
		{
			string text = LevelNameResolver.DisplayName(query.Trim());
			if (!text.StartsWith("Shop ", StringComparison.OrdinalIgnoreCase))
			{
				return "Shop " + text;
			}
			return text;
		}

		public static ResolveResult ResolveTarget(SceneRequest request, IEnumerable<string> levelNames, out int? levelNumber)
		{
			List<string> list = levelNames.ToList();
			levelNumber = request.LevelNumber;
			if (request.LevelNumber.HasValue)
			{
				string fullQuery = LevelNameResolver.Normalize(request.RawQuery);
				List<string> list2 = list.Where((string n) => LevelNameResolver.Normalize(n) == fullQuery).ToList();
				if (list2.Count == 1)
				{
					levelNumber = null;
					return ResolveResult.Found(list2[0]);
				}
			}
			ResolveResult resolveResult = LevelNameResolver.Resolve(request.Query, list);
			if (resolveResult.Status == ResolveStatus.NotFound && request.LevelNumber.HasValue)
			{
				ResolveResult resolveResult2 = LevelNameResolver.Resolve(request.RawQuery, list);
				if (resolveResult2.Status != ResolveStatus.NotFound)
				{
					levelNumber = null;
					return resolveResult2;
				}
			}
			return resolveResult;
		}
	}
}
namespace SceneSwitcher.Commands
{
	[HarmonyPatch(typeof(DebugCommandHandler), "Start")]
	internal static class SceneChatCommand
	{
		private const string CommandName = "scene";

		private const string CrimsonLine = "guatona culia fea";

		[HarmonyPostfix]
		private static void Postfix(DebugCommandHandler __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)DebugCommandHandler.instance))
			{
				CrashReporter.Guard("command registration", delegate
				{
					//IL_002a: Unknown result type (might be due to invalid IL or missing references)
					//IL_0034: Expected O, but got Unknown
					__instance.Register(new ChatCommand("scene", "Host only. Switch scene: /scene lobby | truck [level] | shop [zone|random] [level] | map <name|random> [level] | arena <fight|race> | list", (Action<bool, string[]>)Execute, (Func<bool, string, string[], List<string>>)Suggest, (Func<bool>)null, false));
					Plugin.Log.LogInfo((object)"Registered chat command /scene.");
				});
			}
		}

		private static void Execute(bool isDebugConsole, string[] args)
		{
			CrashReporter.Guard("/scene " + string.Join(" ", args), delegate
			{
				ExecuteRequest(args);
			});
		}

		private static void ExecuteRequest(string[] args)
		{
			SceneRequest sceneRequest = SceneCommandParser.Parse(args);
			if (sceneRequest.Error != null)
			{
				Feedback.Fail(sceneRequest.Error + " Usage: /scene lobby | truck [level] | shop [zone|random] [level] | map <name|random> [level] | arena <fight|race> | list");
				return;
			}
			switch (sceneRequest.Action)
			{
			case SceneAction.Help:
				Feedback.Info("/scene lobby | truck [level] | shop [zone|random] [level] | map <name|random> [level] | arena <fight|race> | list");
				return;
			case SceneAction.List:
				ListLevels();
				return;
			case SceneAction.Crimson:
				if (Object.op_Implicit((Object)(object)PlayerAvatar.instance))
				{
					PlayerAvatar.instance.ChatMessageSend("guatona culia fea", (string)null);
				}
				return;
			}
			if (!SceneSwitchService.CanSwitch(out var reason))
			{
				Feedback.Fail(reason);
				return;
			}
			switch (sceneRequest.Action)
			{
			case SceneAction.LobbyMenu:
				SceneLauncher.ReturnToLobbyMenu();
				break;
			case SceneAction.Truck:
				SceneLauncher.Launch(SceneSwitchService.Truck, sceneRequest.LevelNumber, "the truck");
				break;
			case SceneAction.Arena:
				LaunchResolved(sceneRequest, SceneSwitchService.Arenas.ToList(), "arena");
				break;
			case SceneAction.Map:
				LaunchResolved(sceneRequest, SceneSwitchService.Maps.ToList(), "map");
				break;
			case SceneAction.RandomMap:
				SceneLauncher.LaunchRandom(SceneSwitchService.PickRandomMap(), sceneRequest.LevelNumber, "maps");
				break;
			case SceneAction.Shop:
				LaunchResolved(sceneRequest, SceneSwitchService.Shops.ToList(), "shop");
				break;
			case SceneAction.RandomShop:
				SceneLauncher.LaunchRandom(SceneSwitchService.PickRandomShop(), sceneRequest.LevelNumber, "shops");
				break;
			}
		}

		private static void LaunchResolved(SceneRequest request, List<Level> candidates, string kind)
		{
			int? levelNumber;
			ResolveResult result = SceneCommandParser.ResolveTarget(request, candidates.Select((Level l) => ((Object)l).name), out levelNumber);
			switch (result.Status)
			{
			case ResolveStatus.NotFound:
				Feedback.Fail("No " + kind + " matches '" + request.Query + "'. Try /scene list.");
				break;
			case ResolveStatus.Ambiguous:
				Feedback.Fail("'" + request.Query + "' matches: " + string.Join(", ", result.Candidates.Select(LevelNameResolver.DisplayName)));
				break;
			default:
				SceneLauncher.Launch(candidates.First((Level l) => ((Object)l).name == result.Match), levelNumber);
				break;
			}
		}

		private static void ListLevels()
		{
			if (!Object.op_Implicit((Object)(object)RunManager.instance))
			{
				Feedback.Fail("Game is not ready.");
				return;
			}
			string text = string.Join(", ", SceneSwitchService.Maps.Select((Level l) => LevelNameResolver.DisplayName(((Object)l).name)));
			string text2 = string.Join(", ", SceneSwitchService.Arenas.Select((Level l) => LevelNameResolver.DisplayName(((Object)l).name)));
			string text3 = string.Join(", ", SceneSwitchService.Shops.Select((Level l) => ShopZone(((Object)l).name)));
			Feedback.Info("Maps: " + text + "\nShops: " + text3 + "\nArenas: " + text2, 8f);
		}

		internal static string ShopZone(string levelName)
		{
			string text = LevelNameResolver.Normalize(levelName);
			if (!text.StartsWith("shop "))
			{
				return text;
			}
			return text.Substring("shop ".Length);
		}

		private static List<string> Suggest(bool isDebugConsole, string partial, string[] args)
		{
			IEnumerable<string> source = ((args.Length <= 1) ? new string[7] { "lobby", "truck", "shop", "map", "arena", "list", "help" } : ((args[0].ToLowerInvariant() == "shop" && Object.op_Implicit((Object)(object)RunManager.instance)) ? SceneSwitchService.Shops.Select((Level l) => ShopZone(((Object)l).name)).Prepend("random") : ((args[0].ToLowerInvariant() == "arena") ? new string[2] { "fight", "race" } : ((!(args[0].ToLowerInvariant() == "map") || !Object.op_Implicit((Object)(object)RunManager.instance)) ? Enumerable.Empty<string>() : SceneSwitchService.Maps.Select((Level l) => LevelNameResolver.Normalize(((Object)l).name)).Prepend("random")))));
			return source.Where((string o) => o.StartsWith(partial.ToLowerInvariant())).ToList();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}