using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using REPOLib.Modules; using RepoLiveControl.Commands; using RepoLiveControl.Networking; using RepoLiveControl.Runtime; using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RepoCommandConsole")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Host-authoritative R.E.P.O. command console with fuzzy autocomplete and delegated client permissions.")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0+a45269048a6bbb6b3902670edb7323a8d213f586")] [assembly: AssemblyProduct("RepoCommandConsole")] [assembly: AssemblyTitle("RepoCommandConsole")] [assembly: AssemblyVersion("2.0.0.0")] namespace RepoLiveControl { internal sealed class CommandConsoleRuntime : IDisposable { private const string InputControlName = "RepoCommandConsole.Input"; private const int WindowId = 198042; private const int SuggestionLimit = 8; private readonly Plugin plugin; private readonly ConfigEntry toggleKey; private readonly ConfigEntry networkEventCode; private readonly List history = new List(); private readonly ConsoleInputGate inputGate = new ConsoleInputGate(); private Rect windowRect; private string input = "/"; private string result = "Ready. Type /help or use fuzzy autocomplete."; private IReadOnlyList suggestions = Array.AsReadOnly(new CompletionItem[0]); private CompletionCatalog catalog = CompletionCatalog.Empty; private int selectedSuggestion; private int pendingCaretPosition = -1; private int completionCaretPosition = 1; private bool open; private bool focusInput; private bool releaseGuiFocus; private bool stylesReady; private bool localPermissionKnown; private bool localPermissionGranted; private long observedPermissionSessionRevision; private float catalogRefreshAt; private GUIStyle windowStyle; private GUIStyle titleStyle; private GUIStyle hintStyle; private GUIStyle inputStyle; private GUIStyle suggestionStyle; private GUIStyle selectedSuggestionStyle; private GUIStyle resultStyle; private Texture2D windowBackground; private Texture2D selectedBackground; internal PermissionService Permissions { get; private set; } internal CommandNetworkRouter Network { get; private set; } internal string ToggleKeyLabel => ((object)toggleKey.Value/*cast due to .constrained prefix*/).ToString(); internal CommandConsoleRuntime(Plugin plugin) { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) this.plugin = plugin; toggleKey = ((BaseUnityPlugin)plugin).Config.Bind("Console", "ToggleKey", (KeyCode)283, "Key used to open and close the independent in-game command console."); networkEventCode = ((BaseUnityPlugin)plugin).Config.Bind("Networking", "PhotonEventCode", 198, "Fixed Photon custom event code shared by all clients (3-199). Change only if another mod collides."); int num = Mathf.Clamp(networkEventCode.Value, 3, 199); if (num != networkEventCode.Value) { networkEventCode.Value = num; ((BaseUnityPlugin)plugin).Config.Save(); } Permissions = new PermissionService(); observedPermissionSessionRevision = Permissions.SessionRevision; Network = new CommandNetworkRouter((byte)num, Permissions, SetResult); windowRect = new Rect(0f, 90f, 860f, 510f); } internal void Update() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) Network.Update(IsNetworkSessionSceneActive()); Bridge.PublishPermissionSessionRevision(Permissions.SessionRevision); if (observedPermissionSessionRevision != Permissions.SessionRevision) { observedPermissionSessionRevision = Permissions.SessionRevision; localPermissionKnown = false; localPermissionGranted = false; } if (inputGate.TryAccept(ConsoleInputAction.Toggle, Time.frameCount, Input.GetKeyDown(toggleKey.Value), IsInputSystemKeyPressedThisFrame(toggleKey.Value), guiPressedThisFrame: false)) { SetOpen(!open); } else { if (!open) { return; } if (TryAcceptInputAction(ConsoleInputAction.Close, (KeyCode)27, (KeyCode)0)) { SetOpen(value: false); return; } if (TryAcceptInputAction(ConsoleInputAction.AcceptCompletion, (KeyCode)9, (KeyCode)0)) { AcceptSelectedSuggestion(appendSpace: true); } else if (TryAcceptInputAction(ConsoleInputAction.SelectPrevious, (KeyCode)273, (KeyCode)0) && suggestions.Count > 0) { selectedSuggestion = (selectedSuggestion - 1 + suggestions.Count) % suggestions.Count; } else if (TryAcceptInputAction(ConsoleInputAction.SelectNext, (KeyCode)274, (KeyCode)0) && suggestions.Count > 0) { selectedSuggestion = (selectedSuggestion + 1) % suggestions.Count; } else if (TryAcceptInputAction(ConsoleInputAction.Submit, (KeyCode)13, (KeyCode)271)) { SubmitInput(); } try { SemiFunc.InputDisableMovement(); SemiFunc.InputDisableAiming(); SemiFunc.CursorUnlock(0.1f); if ((Object)(object)MenuManager.instance != (Object)null) { MenuManager.instance.TextInputActive(); } if ((Object)(object)PlayerController.instance != (Object)null) { PlayerController.instance.InputDisable(0.1f); } } catch { } if (Time.realtimeSinceStartup >= catalogRefreshAt) { RefreshCatalog(); catalogRefreshAt = Time.realtimeSinceStartup + 1f; } } } internal void OnGUI() { //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) ReleaseGuiFocusIfRequested(); if (open) { EnsureStyles(); float num = Mathf.Min(900f, Mathf.Max(540f, (float)Screen.width - 40f)); ((Rect)(ref windowRect)).width = num; ((Rect)(ref windowRect)).height = Mathf.Min(600f, Mathf.Max(440f, (float)Screen.height - 120f)); ((Rect)(ref windowRect)).x = Mathf.Clamp(((Rect)(ref windowRect)).x, 10f, Mathf.Max(10f, (float)Screen.width - num - 10f)); ((Rect)(ref windowRect)).y = Mathf.Clamp(((Rect)(ref windowRect)).y, 10f, Mathf.Max(10f, (float)Screen.height - ((Rect)(ref windowRect)).height - 10f)); if (((Rect)(ref windowRect)).x <= 0f) { ((Rect)(ref windowRect)).x = ((float)Screen.width - num) * 0.5f; } HandleKeyboardEvent(Event.current); if (!open) { ReleaseGuiFocusIfRequested(); } else { windowRect = GUI.Window(198042, windowRect, new WindowFunction(DrawWindow), string.Empty, windowStyle); } } } private void DrawWindow(int windowId) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Invalid comparison between Unknown and I4 //IL_03ec: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty()); GUILayout.Label("REPO COMMAND CONSOLE • " + RoleLabel(), titleStyle, Array.Empty()); GUILayout.Label(ToggleKeyLabel + " / Esc closes • ↑↓ selects • Tab accepts • Enter runs", hintStyle, Array.Empty()); GUILayout.Space(8f); GUI.SetNextControlName("RepoCommandConsole.Input"); string a = GUILayout.TextField(input, inputStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) }); bool num = !string.Equals(a, input, StringComparison.Ordinal); if (num) { input = a; } if (focusInput) { GUI.FocusControl("RepoCommandConsole.Input"); focusInput = false; } TextEditor focusedInputEditor = GetFocusedInputEditor(); if (pendingCaretPosition >= 0 && focusedInputEditor != null && (int)Event.current.type == 7) { int selectIndex = (focusedInputEditor.cursorIndex = Mathf.Clamp(pendingCaretPosition, 0, input.Length)); focusedInputEditor.selectIndex = selectIndex; pendingCaretPosition = -1; } int num3 = ((pendingCaretPosition >= 0) ? Mathf.Clamp(pendingCaretPosition, 0, input.Length) : ((focusedInputEditor != null) ? Mathf.Clamp(focusedInputEditor.cursorIndex, 0, input.Length) : Mathf.Clamp(completionCaretPosition, 0, input.Length))); bool flag = num3 != completionCaretPosition; completionCaretPosition = num3; if (num || flag) { selectedSuggestion = 0; RefreshSuggestions(); } GUILayout.Space(6f); GUILayout.Label("FUZZY AUTOCOMPLETE", hintStyle, Array.Empty()); if (suggestions.Count == 0) { GUILayout.Label("No completion for the active argument.", hintStyle, Array.Empty()); } else { for (int i = 0; i < suggestions.Count; i++) { CompletionItem completionItem = suggestions[i]; string obj = ((i == selectedSuggestion) ? "▶ " : " "); GUIStyle val = ((i == selectedSuggestion) ? selectedSuggestionStyle : suggestionStyle); if (GUILayout.Button(obj + completionItem.Value, val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { selectedSuggestion = i; AcceptSelectedSuggestion(appendSpace: true); } } } GUILayout.FlexibleSpace(); GUILayout.Label("RESULT", hintStyle, Array.Empty()); GUILayout.Label(result, resultStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.MinHeight(48f), GUILayout.MaxHeight(72f) }); if (history.Count > 0) { GUILayout.Label(string.Join("\n", history.ToArray()), hintStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MaxHeight(72f) }); } GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Help", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { input = "/help"; SubmitInput(); } if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { input = "/"; completionCaretPosition = input.Length; result = "Ready."; history.Clear(); RefreshSuggestions(); focusInput = true; } if (GUILayout.Button("Run", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { SubmitInput(); } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { SetOpen(value: false); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref windowRect)).width, 44f)); } private void HandleKeyboardEvent(Event current) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Invalid comparison between Unknown and I4 //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Invalid comparison between Unknown and I4 //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Invalid comparison between Unknown and I4 if (current == null || (int)current.type != 4) { return; } if (current.keyCode == toggleKey.Value) { if (inputGate.TryAccept(ConsoleInputAction.Toggle, Time.frameCount, legacyPressedThisFrame: false, inputSystemPressedThisFrame: false, guiPressedThisFrame: true)) { SetOpen(!open); } current.Use(); } else if ((int)current.keyCode == 27) { if (AcceptGuiInput(ConsoleInputAction.Close)) { SetOpen(value: false); } current.Use(); } else if ((int)current.keyCode == 9) { if (AcceptGuiInput(ConsoleInputAction.AcceptCompletion)) { AcceptSelectedSuggestion(appendSpace: true); } current.Use(); } else if ((int)current.keyCode == 273 && suggestions.Count > 0) { if (AcceptGuiInput(ConsoleInputAction.SelectPrevious)) { selectedSuggestion = (selectedSuggestion - 1 + suggestions.Count) % suggestions.Count; } current.Use(); } else if ((int)current.keyCode == 274 && suggestions.Count > 0) { if (AcceptGuiInput(ConsoleInputAction.SelectNext)) { selectedSuggestion = (selectedSuggestion + 1) % suggestions.Count; } current.Use(); } else if ((int)current.keyCode == 13 || (int)current.keyCode == 271) { if (AcceptGuiInput(ConsoleInputAction.Submit)) { SubmitInput(); } current.Use(); } } private static bool IsNetworkSessionSceneActive() { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return NetworkSessionSceneActivationPolicy.ShouldActivate(managerAvailable: false, currentLevelAvailable: false, isLobby: false, isGameplay: false, isShop: false, isArena: false); } Level levelCurrent = instance.levelCurrent; if ((Object)(object)levelCurrent == (Object)null) { return NetworkSessionSceneActivationPolicy.ShouldActivate(managerAvailable: true, currentLevelAvailable: false, isLobby: false, isGameplay: false, isShop: false, isArena: false); } return NetworkSessionSceneActivationPolicy.ShouldActivate(managerAvailable: true, currentLevelAvailable: true, (Object)(object)levelCurrent == (Object)(object)instance.levelLobby, ContainsLevel(instance.levels, levelCurrent), ContainsLevel(instance.levelShop, levelCurrent), ContainsLevel(instance.levelArena, levelCurrent)); } private static bool ContainsLevel(IList levels, Level current) { return levels?.Contains(current) ?? false; } private bool TryAcceptInputAction(ConsoleInputAction action, KeyCode primaryKey, KeyCode secondaryKey = (KeyCode)0) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) bool legacyPressedThisFrame = Input.GetKeyDown(primaryKey) || ((int)secondaryKey != 0 && Input.GetKeyDown(secondaryKey)); bool inputSystemPressedThisFrame = IsInputSystemKeyPressedThisFrame(primaryKey) || ((int)secondaryKey != 0 && IsInputSystemKeyPressedThisFrame(secondaryKey)); return inputGate.TryAccept(action, Time.frameCount, legacyPressedThisFrame, inputSystemPressedThisFrame, guiPressedThisFrame: false); } private bool AcceptGuiInput(ConsoleInputAction action) { return inputGate.TryAccept(action, Time.frameCount, legacyPressedThisFrame: false, inputSystemPressedThisFrame: false, guiPressedThisFrame: true); } private void SubmitInput() { string text = (input ?? string.Empty).Trim(); CommandParseResult commandParseResult = SlashCommandParser.Parse(text); if (!commandParseResult.Success) { SetResult("ERROR " + commandParseResult.ErrorMessage); focusInput = true; return; } AddHistory("> " + text); result = "PENDING Sending command to " + ((!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient) ? "host executor..." : "lobby host..."); try { if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient) { int requesterActorNumber = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : (-1)); long requiredSessionRevision = Permissions.SessionRevision; Bridge.Enqueue(new ControlRequest(text, CommandRequestSource.LocalConsole, requesterActorNumber, SetResult, requiredSessionRevision, () => Permissions.SessionRevision == requiredSessionRevision)); } else { Network.SendRequest(text); } } catch (Exception ex) { SetResult("ERROR " + ex.Message); } focusInput = true; } private void SetResult(string value) { result = (string.IsNullOrWhiteSpace(value) ? "ERROR Empty command response." : value); AddHistory(result); if (result.IndexOf("granted you", StringComparison.OrdinalIgnoreCase) >= 0) { localPermissionKnown = true; localPermissionGranted = true; } else if (result.IndexOf("revoked your", StringComparison.OrdinalIgnoreCase) >= 0 || result.IndexOf("has not granted", StringComparison.OrdinalIgnoreCase) >= 0) { localPermissionKnown = true; localPermissionGranted = false; } if (Plugin.Log != null) { Plugin.Log.LogInfo((object)("Console result: " + result)); } } private void AddHistory(string value) { if (!string.IsNullOrWhiteSpace(value)) { history.Insert(0, (value.Length > 140) ? (value.Substring(0, 140) + "…") : value); while (history.Count > 3) { history.RemoveAt(history.Count - 1); } } } private void AcceptSelectedSuggestion(bool appendSpace) { if (suggestions.Count != 0) { selectedSuggestion = Mathf.Clamp(selectedSuggestion, 0, suggestions.Count - 1); CompletionApplication completionApplication = CommandCompletionEngine.ApplyCompletion(input, suggestions[selectedSuggestion], appendSpace); input = completionApplication.Text; pendingCaretPosition = completionApplication.CaretPosition; completionCaretPosition = completionApplication.CaretPosition; selectedSuggestion = 0; RefreshSuggestions(); focusInput = true; } } private void RefreshCatalog() { List list = new List(); List list2 = new List(); bool flag = !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient; if (flag) { list.AddRange(Permissions.GetGrantCandidates()); list2.AddRange(Permissions.GetRevokeCandidates()); } catalog = new CompletionCatalog(RuntimeTargetCatalog.GetSelectors(includeAll: true), list, list2, flag); RefreshSuggestions(); } private void RefreshSuggestions() { try { suggestions = CommandCompletionEngine.GetCompletions(input, Mathf.Clamp(completionCaretPosition, 0, (input != null) ? input.Length : 0), catalog, 8); } catch (Exception ex) { suggestions = Array.AsReadOnly(new CompletionItem[0]); if (Plugin.Log != null) { Plugin.Log.LogWarning((object)("Could not refresh command suggestions: " + ex.Message)); } } if (selectedSuggestion >= suggestions.Count) { selectedSuggestion = 0; } } private string RoleLabel() { if (!PhotonNetwork.InRoom) { return "LOCAL HOST"; } if (PhotonNetwork.IsMasterClient) { return "LOBBY HOST"; } if (!localPermissionKnown) { return "CLIENT • PERMISSION UNKNOWN"; } if (!localPermissionGranted) { return "CLIENT • NOT GRANTED"; } return "CLIENT • PERMISSION GRANTED"; } private void SetOpen(bool value) { open = value; if (open) { if (string.IsNullOrWhiteSpace(input)) { input = "/"; } ((Rect)(ref windowRect)).x = ((float)Screen.width - ((Rect)(ref windowRect)).width) * 0.5f; ((Rect)(ref windowRect)).y = Mathf.Max(20f, (float)Screen.height * 0.08f); focusInput = true; pendingCaretPosition = input.Length; completionCaretPosition = input.Length; releaseGuiFocus = false; RefreshCatalog(); result = "Ready. Chat is not required; this console uses its own input path."; } else { releaseGuiFocus = true; } if (Plugin.Log != null) { Plugin.Log.LogInfo((object)("Command console " + (open ? "opened." : "closed."))); } } private static TextEditor GetFocusedInputEditor() { if (!string.Equals(GUI.GetNameOfFocusedControl(), "RepoCommandConsole.Input", StringComparison.Ordinal)) { return null; } object stateObject = GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); return (TextEditor)((stateObject is TextEditor) ? stateObject : null); } private unsafe static bool IsInputSystemKeyPressedThisFrame(KeyCode keyCode) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return false; } if (!Enum.TryParse(ConsoleToggleKeyMapping.ToInputSystemKeyName(((object)(*(KeyCode*)(&keyCode))/*cast due to .constrained prefix*/).ToString()), ignoreCase: true, out Key val) || (int)val == 0) { return false; } if (current[val] != null) { return ((ButtonControl)current[val]).wasPressedThisFrame; } return false; } private void ReleaseGuiFocusIfRequested() { if (releaseGuiFocus) { GUI.FocusControl((string)null); releaseGuiFocus = false; } } private void EnsureStyles() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Expected O, but got Unknown //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Expected O, but got Unknown //IL_02a5: Unknown result type (might be due to invalid IL or missing references) if (!stylesReady) { stylesReady = true; windowBackground = MakeTexture(new Color(0.035f, 0.045f, 0.055f, 0.97f)); selectedBackground = MakeTexture(new Color(0.16f, 0.24f, 0.19f, 0.98f)); windowStyle = new GUIStyle(GUI.skin.window); windowStyle.normal.background = windowBackground; windowStyle.padding = new RectOffset(20, 20, 16, 18); titleStyle = new GUIStyle(GUI.skin.label); titleStyle.fontSize = 22; titleStyle.fontStyle = (FontStyle)1; titleStyle.normal.textColor = new Color(1f, 0.86f, 0.12f); hintStyle = new GUIStyle(GUI.skin.label); hintStyle.fontSize = 13; hintStyle.wordWrap = true; hintStyle.normal.textColor = new Color(0.72f, 0.78f, 0.8f); inputStyle = new GUIStyle(GUI.skin.textField); inputStyle.fontSize = 20; inputStyle.padding = new RectOffset(10, 10, 7, 6); inputStyle.normal.textColor = Color.white; inputStyle.focused.textColor = Color.white; suggestionStyle = new GUIStyle(GUI.skin.button); suggestionStyle.alignment = (TextAnchor)3; suggestionStyle.fontSize = 15; suggestionStyle.normal.textColor = new Color(0.86f, 0.9f, 0.91f); selectedSuggestionStyle = new GUIStyle(suggestionStyle); selectedSuggestionStyle.normal.background = selectedBackground; selectedSuggestionStyle.normal.textColor = new Color(0.35f, 1f, 0.56f); selectedSuggestionStyle.fontStyle = (FontStyle)1; resultStyle = new GUIStyle(GUI.skin.box); resultStyle.alignment = (TextAnchor)0; resultStyle.fontSize = 14; resultStyle.wordWrap = true; resultStyle.padding = new RectOffset(10, 10, 8, 8); resultStyle.normal.textColor = Color.white; } } private static Texture2D MakeTexture(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); return val; } public void Dispose() { Network.Dispose(); Permissions.Reset(); if ((Object)(object)windowBackground != (Object)null) { Object.Destroy((Object)(object)windowBackground); } if ((Object)(object)selectedBackground != (Object)null) { Object.Destroy((Object)(object)selectedBackground); } } } [BepInPlugin("com.jameskieley.repo.commandconsole", "REPO Command Console", "2.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal const string PluginGuid = "com.jameskieley.repo.commandconsole"; internal const string PluginName = "REPO Command Console"; internal const string PluginVersion = "2.0.0"; private CommandConsoleRuntime commandConsole; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal CommandConsoleRuntime CommandConsole => commandConsole; private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; commandConsole = new CommandConsoleRuntime(this); Bridge.PublishPermissionSessionRevision(commandConsole.Permissions.SessionRevision); Bridge.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("REPO Command Console 2.0.0 loaded. Press " + commandConsole.ToggleKeyLabel + " to open the command console.")); } private void Update() { if (commandConsole != null) { commandConsole.Update(); } } private void OnGUI() { if (commandConsole != null) { commandConsole.OnGUI(); } } private void OnDestroy() { if (commandConsole != null) { commandConsole.Dispose(); } commandConsole = null; Instance = null; } } public static class Loader { public static void Load() { Bridge.Start(); } } internal sealed class ControlRequest { internal readonly string Command; internal readonly CommandRequestSource Source; internal readonly int RequesterActorNumber; internal readonly Action CompletionCallback; internal readonly long? RequiredSessionRevision; internal readonly Func AuthorizationValidator; internal readonly ManualResetEventSlim Completed = new ManualResetEventSlim(initialState: false); internal string Result = "ERROR No result was produced."; internal bool ExecutionContextBound; internal bool ExecutionStartedInRoom; internal object ExecutionRoomIdentity; internal int ExecutionMasterActorNumber = -1; internal long ExecutionSessionRevision = -1L; private int completionState; private int cancellationState; internal bool IsCancelled => Volatile.Read(in cancellationState) != 0; internal ControlRequest(string command) : this(command, CommandRequestSource.NamedPipe, -1, null, CapturePublishedSessionRevision(), null) { } internal ControlRequest(string command, CommandRequestSource source, int requesterActorNumber, Action completionCallback) : this(command, source, requesterActorNumber, completionCallback, null, null) { } internal ControlRequest(string command, CommandRequestSource source, int requesterActorNumber, Action completionCallback, long? requiredSessionRevision, Func authorizationValidator) { Command = command; Source = source; RequesterActorNumber = requesterActorNumber; CompletionCallback = completionCallback; RequiredSessionRevision = requiredSessionRevision; AuthorizationValidator = authorizationValidator; } internal void Complete(string result) { if (Interlocked.Exchange(ref completionState, 1) != 0) { return; } Result = result; Completed.Set(); if (CompletionCallback == null) { return; } try { CompletionCallback(result); } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogError((object)("Command completion callback failed: " + ex)); } } } internal void Cancel(string result) { Interlocked.Exchange(ref cancellationState, 1); if (Interlocked.Exchange(ref completionState, 1) == 0) { Result = result; Completed.Set(); } } private static long? CapturePublishedSessionRevision() { long publishedPermissionSessionRevision = Bridge.GetPublishedPermissionSessionRevision(); if (publishedPermissionSessionRevision < 0) { return null; } return publishedPermissionSessionRevision; } } internal enum CommandRequestSource { NamedPipe, LocalConsole, RemoteClient } internal enum SpawnKind { Enemy, Loot, Item, Cart } internal sealed class EnemyPlacementReservation { internal readonly Vector3 Position; internal readonly float HorizontalRadius; internal EnemyPlacementReservation(Vector3 position, float horizontalRadius) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Position = position; HorizontalRadius = horizontalRadius; } } internal sealed class EnemyClearanceVolume { internal readonly Vector3 CenterOffset; internal readonly Vector3 HalfExtents; internal readonly float HorizontalRadius; internal EnemyClearanceVolume(Vector3 centerOffset, Vector3 halfExtents, float horizontalRadius) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) CenterOffset = centerOffset; HalfExtents = halfExtents; HorizontalRadius = horizontalRadius; } } internal sealed class SpawnJob { internal readonly ControlRequest Request; internal readonly SpawnKind Kind; internal readonly string Selector; internal readonly string Placement; internal readonly int Requested; internal readonly Vector3 Anchor; internal readonly List ReservedPositions = new List(); internal readonly List EnemyReservations = new List(); internal int Spawned; internal bool Finished; internal readonly SpawnNameSummary NameSummary = new SpawnNameSummary(); internal SpawnJob(ControlRequest request, SpawnKind kind, string selector, string placement, int requested, Vector3 anchor) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) Request = request; Kind = kind; Selector = selector; Placement = placement; Requested = requested; Anchor = anchor; } } internal sealed class SpawnedObjectRecord { internal GameObject Instance; internal string Name; internal SpawnKind Kind; internal bool IsWeapon; } internal sealed class DuplicateLootJob { internal readonly ControlRequest Request; internal readonly List Prefabs; internal readonly Vector3 Anchor; internal readonly List Positions = new List(); internal int Spawned; internal bool Finished; internal DuplicateLootJob(ControlRequest request, List prefabs, Vector3 anchor) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Request = request; Prefabs = prefabs; Anchor = anchor; } } internal sealed class ItemBatchJob { internal readonly ControlRequest Request; internal readonly List Items; internal readonly List TypeNames; internal readonly string Placement; internal readonly int CountPerType; internal readonly Vector3 Anchor; internal readonly List ReservedPositions = new List(); internal int Spawned; internal bool Finished; internal ItemBatchJob(ControlRequest request, List items, List typeNames, string placement, int countPerType, Vector3 anchor) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Request = request; Items = items; TypeNames = typeNames; Placement = placement; CountPerType = countPerType; Anchor = anchor; } } internal sealed class BalancedItemJob { internal readonly ControlRequest Request; internal readonly List Items; internal readonly List TypeNames; internal readonly Dictionary TypeCounts; internal readonly string Placement; internal readonly Vector3 Anchor; internal readonly List ReservedPositions = new List(); internal int Spawned; internal bool Finished; internal BalancedItemJob(ControlRequest request, List items, List typeNames, Dictionary typeCounts, string placement, Vector3 anchor) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Request = request; Items = items; TypeNames = typeNames; TypeCounts = typeCounts; Placement = placement; Anchor = anchor; } } internal static class Bridge { internal const string PipeName = "CodexRepoCommandConsoleV2"; private const string HarmonyId = "com.jameskieley.repo.commandconsole.harmony"; private static readonly ConcurrentQueue Requests = new ConcurrentQueue(); private static readonly List SpawnedObjects = new List(); private static readonly FieldInfo EnemyFirstSpawnPointField = AccessTools.Field(typeof(EnemyParent), "firstSpawnPoint"); private static readonly FieldInfo EnemyFirstSpawnPointsField = AccessTools.Field(typeof(EnemyDirector), "enemyFirstSpawnPoints"); private static readonly string[] ExpensiveLootNames = new string[5] { "Diamond Display", "Griffin Statue", "Dragon Skull", "GoldTooth", "Server Rack" }; private static readonly string[] WeaponTerms = new string[28] { "weapon", "melee", "ranged", "gun", "pistol", "rifle", "shotgun", "revolver", "blaster", "cannon", "launcher", "sword", "blade", "knife", "dagger", "axe", "hatchet", "bat", "hammer", "mace", "spear", "bow", "crossbow", "grenade", "mine", "bomb", "pan", "taser" }; private static int started; private static long publishedPermissionSessionRevision = -1L; private static SpawnJob activeJob; private static DuplicateLootJob activeDuplicateLootJob; private static ItemBatchJob activeItemBatchJob; private static BalancedItemJob activeBalancedItemJob; internal static void Start() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (Interlocked.Exchange(ref started, 1) == 0) { Harmony.UnpatchID("Codex.REPO.SpawnBridge"); Harmony.UnpatchID("Codex.REPO.SpawnBridge.V2"); Harmony.UnpatchID("Codex.REPO.ControlBridge"); Harmony.UnpatchID("Codex.REPO.LiveControl"); Harmony.UnpatchID("Codex.REPO.LiveControl.V2"); Harmony.UnpatchID("Codex.REPO.LiveControl.V3"); Harmony.UnpatchID("Codex.REPO.LiveControl.V4"); Harmony.UnpatchID("Codex.REPO.LiveControl.V5"); Harmony.UnpatchID("Codex.REPO.LiveControl.V6"); Harmony.UnpatchID("Codex.REPO.LiveControl.V7"); Harmony.UnpatchID("Codex.REPO.LiveControl.V8"); Harmony.UnpatchID("Codex.REPO.LiveControl.V9"); Harmony.UnpatchID("Codex.REPO.LiveControl.V10"); Harmony.UnpatchID("Codex.REPO.LiveControl.V11"); Harmony.UnpatchID("Codex.REPO.LiveControl.V12"); Harmony.UnpatchID("Codex.REPO.LiveControl.V13"); Harmony.UnpatchID("com.jameskieley.repo.commandconsole.harmony"); new Harmony("com.jameskieley.repo.commandconsole.harmony").PatchAll(typeof(Bridge).Assembly); Thread thread = new Thread(ListenForRequests); thread.IsBackground = true; thread.Name = "Codex REPO Live Control"; thread.Start(); } } internal static void Enqueue(ControlRequest request) { if (request == null) { throw new ArgumentNullException("request"); } Requests.Enqueue(request); } internal static void PublishPermissionSessionRevision(long revision) { Interlocked.Exchange(ref publishedPermissionSessionRevision, revision); } internal static long GetPublishedPermissionSessionRevision() { return Interlocked.Read(in publishedPermissionSessionRevision); } private static void ListenForRequests() { while (true) { try { using NamedPipeServerStream namedPipeServerStream = new NamedPipeServerStream("CodexRepoCommandConsoleV2", PipeDirection.InOut, 1); namedPipeServerStream.WaitForConnection(); string text; using (StreamReader streamReader = new StreamReader(namedPipeServerStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), detectEncodingFromByteOrderMarks: false, 1024, leaveOpen: true)) { text = streamReader.ReadLine(); } string text2; if (string.IsNullOrWhiteSpace(text)) { text2 = "ERROR Empty command."; } else { ControlRequest controlRequest = new ControlRequest(text); Requests.Enqueue(controlRequest); if (controlRequest.Completed.Wait(TimeSpan.FromSeconds(30.0))) { text2 = controlRequest.Result; } else { text2 = "ERROR Command timed out waiting for the game thread; the queued request was cancelled."; controlRequest.Cancel(text2); } } using StreamWriter streamWriter = new StreamWriter(namedPipeServerStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 1024, leaveOpen: true); streamWriter.AutoFlush = true; streamWriter.WriteLine(text2); } catch (Exception ex) { try { File.AppendAllText(Path.Combine(Path.GetTempPath(), "RepoLiveControl-pipe.log"), DateTime.UtcNow.ToString("O") + " " + ex?.ToString() + Environment.NewLine); } catch { } Thread.Sleep(250); } } } internal static void ProcessFrame() { if (HasActiveJob()) { RefreshPermissionSession(); if (AbortActiveJobsIfAuthorityLost()) { return; } } if (activeBalancedItemJob != null) { ProcessBalancedItemJob(activeBalancedItemJob); if (activeBalancedItemJob.Finished) { activeBalancedItemJob = null; } } else if (activeItemBatchJob != null) { ProcessItemBatchJob(activeItemBatchJob); if (activeItemBatchJob.Finished) { activeItemBatchJob = null; } } else if (activeDuplicateLootJob != null) { ProcessDuplicateLootJob(activeDuplicateLootJob); if (activeDuplicateLootJob.Finished) { activeDuplicateLootJob = null; } } else if (activeJob != null) { ProcessSpawnJob(activeJob); if (activeJob.Finished) { activeJob = null; } } else { if (!Requests.TryDequeue(out var result) || result.IsCancelled) { return; } try { RefreshPermissionSession(); BindExecutionContext(result); string invalidExecutionReason = GetInvalidExecutionReason(result); if (invalidExecutionReason != null) { throw new InvalidOperationException(invalidExecutionReason); } Dispatch(result); } catch (Exception ex) { Complete(result, "ERROR " + ex.Message); } } } private static bool AbortActiveJobsIfAuthorityLost() { bool result = false; if (activeBalancedItemJob != null) { string invalidExecutionReason = GetInvalidExecutionReason(activeBalancedItemJob.Request); if (!activeBalancedItemJob.Finished && invalidExecutionReason != null) { activeBalancedItemJob.Finished = true; activeBalancedItemJob.ReservedPositions.Clear(); Complete(activeBalancedItemJob.Request, string.Format("ERROR {2} Balanced item spread stopped after {0}/{1}.", activeBalancedItemJob.Spawned, activeBalancedItemJob.Items.Count, invalidExecutionReason)); result = true; } if (activeBalancedItemJob.Finished) { activeBalancedItemJob = null; } } if (activeItemBatchJob != null) { string invalidExecutionReason2 = GetInvalidExecutionReason(activeItemBatchJob.Request); if (!activeItemBatchJob.Finished && invalidExecutionReason2 != null) { activeItemBatchJob.Finished = true; activeItemBatchJob.ReservedPositions.Clear(); Complete(activeItemBatchJob.Request, string.Format("ERROR {2} Item batch stopped after {0}/{1}.", activeItemBatchJob.Spawned, activeItemBatchJob.Items.Count, invalidExecutionReason2)); result = true; } if (activeItemBatchJob.Finished) { activeItemBatchJob = null; } } if (activeDuplicateLootJob != null) { string invalidExecutionReason3 = GetInvalidExecutionReason(activeDuplicateLootJob.Request); if (!activeDuplicateLootJob.Finished && invalidExecutionReason3 != null) { activeDuplicateLootJob.Finished = true; activeDuplicateLootJob.Positions.Clear(); Complete(activeDuplicateLootJob.Request, string.Format("ERROR {2} Loot duplication stopped after {0}/{1}.", activeDuplicateLootJob.Spawned, activeDuplicateLootJob.Prefabs.Count, invalidExecutionReason3)); result = true; } if (activeDuplicateLootJob.Finished) { activeDuplicateLootJob = null; } } if (activeJob != null) { string invalidExecutionReason4 = GetInvalidExecutionReason(activeJob.Request); if (!activeJob.Finished && invalidExecutionReason4 != null) { activeJob.Finished = true; activeJob.ReservedPositions.Clear(); activeJob.EnemyReservations.Clear(); Complete(activeJob.Request, string.Format("ERROR {2} Spawn stopped after {0}/{1}.", activeJob.Spawned, activeJob.Requested, invalidExecutionReason4)); result = true; } if (activeJob.Finished) { activeJob = null; } } return result; } private static void RefreshPermissionSession() { PermissionService permissionService = GetPermissionService(); if (permissionService != null) { permissionService.UpdateSession(); PublishPermissionSessionRevision(permissionService.SessionRevision); } } private static bool HasActiveJob() { if (activeBalancedItemJob == null && activeItemBatchJob == null && activeDuplicateLootJob == null) { return activeJob != null; } return true; } private static PermissionService GetPermissionService() { if (!((Object)(object)Plugin.Instance != (Object)null) || Plugin.Instance.CommandConsole == null) { return null; } return Plugin.Instance.CommandConsole.Permissions; } private static void BindExecutionContext(ControlRequest request) { if (!request.ExecutionContextBound) { PermissionService permissionService = GetPermissionService(); request.ExecutionStartedInRoom = PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null; request.ExecutionRoomIdentity = PhotonNetwork.CurrentRoom; request.ExecutionMasterActorNumber = ((PhotonNetwork.MasterClient == null) ? (-1) : PhotonNetwork.MasterClient.ActorNumber); request.ExecutionSessionRevision = permissionService?.SessionRevision ?? (-1); request.ExecutionContextBound = true; } } private static string GetInvalidExecutionReason(ControlRequest request) { if (request == null || !request.ExecutionContextBound) { return "The command has no valid execution session."; } PermissionService permissionService = GetPermissionService(); string text = CommandIngressSessionPolicy.Validate(request.IsCancelled, request.RequiredSessionRevision, permissionService?.SessionRevision); if (text != null) { return text; } if (request.AuthorizationValidator != null) { bool flag; try { flag = request.AuthorizationValidator(); } catch { flag = false; } if (!flag) { return "The requester is no longer authorized in this lobby."; } } if (request.ExecutionStartedInRoom) { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return "The original multiplayer room closed."; } if (request.ExecutionRoomIdentity != PhotonNetwork.CurrentRoom) { return "The multiplayer room changed."; } if (!PhotonNetwork.IsMasterClient) { return "Host authority was lost."; } if (((PhotonNetwork.MasterClient == null) ? (-1) : PhotonNetwork.MasterClient.ActorNumber) != request.ExecutionMasterActorNumber) { return "The lobby host changed."; } if (permissionService != null && request.ExecutionSessionRevision != permissionService.SessionRevision) { return "The multiplayer session changed."; } } else { if (request.Source == CommandRequestSource.RemoteClient) { return "Remote commands require their original multiplayer room."; } if (PhotonNetwork.InRoom) { return "The multiplayer session changed after the command began."; } } return null; } private static void Dispatch(ControlRequest request) { string translatedCommand = request.Command; if (!translatedCommand.StartsWith("/", StringComparison.Ordinal) || SlashCommandRuntime.TryTranslateOrComplete(request, translatedCommand, out translatedCommand)) { string[] parts = translatedCommand.Split('|'); string text = Part(parts, 0, string.Empty).ToLowerInvariant(); switch (text) { case "enemy": BeginSpawn(request, SpawnKind.Enemy, parts, 500, "near-player"); break; case "loot": BeginSpawn(request, SpawnKind.Loot, parts, 500, "safe"); break; case "item": BeginSpawn(request, SpawnKind.Item, parts, 500, "safe"); break; case "cart": BeginSpawn(request, SpawnKind.Cart, parts, 20, "at-player"); break; case "itemeach": BeginItemEach(request, parts); break; case "itemspread": BeginBalancedItems(request, parts); break; case "despawn": DespawnEnemies(request, Part(parts, 1, "all"), ParseInt(parts, 2, 0)); break; case "despawnitem": DespawnItems(request, Part(parts, 1, "all")); break; case "despawnspawned": DespawnSpawnedObjects(request, Part(parts, 1, "all"), Part(parts, 2, "all"), ParseInt(parts, 3, -1)); break; case "auto": SetAutomaticEnemies(request, Part(parts, 1, "on")); break; case "unstick": UnstickLoot(request); break; case "duplicate": DuplicateLoot(request, Part(parts, 1, "loot")); break; case "topup3": TopUpLootAfterOneDuplicate(request, Part(parts, 1, "loot")); break; case "inspect": InspectLoot(request, Part(parts, 1, "loot")); break; case "status": ReportStatus(request); break; default: throw new InvalidOperationException("Unknown action '" + text + "'."); } } } private static void BeginItemEach(ControlRequest request, string[] parts) { //IL_010b: Unknown result type (might be due to invalid IL or missing references) string text = Part(parts, 1, "upgrade"); int num = Mathf.Clamp(ParseInt(parts, 2, 1), 1, 50); string placement = Part(parts, 3, "safe").ToLowerInvariant(); PlayerAvatar val = RequireRequestPlayer(request); List list = new List(); List list2 = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (Item allItem in Items.AllItems) { if (!((Object)(object)allItem == (Object)null) && !string.IsNullOrWhiteSpace(allItem.itemName) && allItem.itemName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 && hashSet.Add(allItem.itemName)) { list2.Add(allItem.itemName); for (int i = 0; i < num; i++) { list.Add(allItem); } } } if (list.Count == 0) { throw new InvalidOperationException("No item types match '" + text + "'."); } activeItemBatchJob = new ItemBatchJob(request, list, list2, placement, num, ((Component)val).transform.position); ProcessItemBatchJob(activeItemBatchJob); } private static void BeginBalancedItems(ControlRequest request, string[] parts) { //IL_01b8: Unknown result type (might be due to invalid IL or missing references) string text = Part(parts, 1, "upgrade"); int num = Mathf.Clamp(ParseInt(parts, 2, 1), 1, 500); string placement = Part(parts, 3, "safe").ToLowerInvariant(); PlayerAvatar val = RequireRequestPlayer(request); List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); bool flag = text.Equals("weapon", StringComparison.OrdinalIgnoreCase) || text.Equals("weapons", StringComparison.OrdinalIgnoreCase); foreach (Item allItem in Items.AllItems) { if (!((Object)(object)allItem == (Object)null) && !string.IsNullOrWhiteSpace(allItem.itemName) && (flag ? IsWeaponItem(allItem) : (allItem.itemName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)) && hashSet.Add(allItem.itemName)) { list.Add(allItem); } } if (list.Count == 0) { throw new InvalidOperationException("No item types match '" + text + "'."); } Shuffle(list); List list2 = new List(num); List list3 = new List(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < num; i++) { Item val2 = list[i % list.Count]; list2.Add(val2); if (!dictionary.ContainsKey(val2.itemName)) { list3.Add(val2.itemName); } dictionary[val2.itemName] = ((!dictionary.ContainsKey(val2.itemName)) ? 1 : (dictionary[val2.itemName] + 1)); } activeBalancedItemJob = new BalancedItemJob(request, list2, list3, dictionary, placement, ((Component)val).transform.position); ProcessBalancedItemJob(activeBalancedItemJob); } private static void ProcessItemBatchJob(ItemBatchJob job) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0033: Unknown result type (might be due to invalid IL or missing references) try { int num = 0; while (!job.Finished && num < 10) { Item val = job.Items[job.Spawned]; Vector3 placement = GetPlacement(job.Placement, job.Anchor, job.ReservedPositions); GameObject val2 = Items.SpawnItem(val, placement, Quaternion.identity); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException("REPOLib returned no spawned item object for '" + val.itemName + "'."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val2, Name = val.itemName, Kind = SpawnKind.Item, IsWeapon = IsWeaponItem(val) }); job.Spawned++; num++; if (job.Spawned >= job.Items.Count) { job.Finished = true; Complete(job.Request, string.Format("OK Spawned {0} item object(s): {1} each of {2} matching type(s): {3}.", job.Spawned, job.CountPerType, job.TypeNames.Count, string.Join(", ", job.TypeNames.ToArray()))); } } } catch (Exception ex) { job.Finished = true; Complete(job.Request, $"ERROR Item batch stopped after {job.Spawned}/{job.Items.Count}: {ex.Message}"); } } private static void ProcessBalancedItemJob(BalancedItemJob job) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0033: Unknown result type (might be due to invalid IL or missing references) try { int num = 0; while (!job.Finished && num < 10) { Item val = job.Items[job.Spawned]; Vector3 placement = GetPlacement(job.Placement, job.Anchor, job.ReservedPositions); GameObject val2 = Items.SpawnItem(val, placement, Quaternion.identity); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException("REPOLib returned no spawned item object for '" + val.itemName + "'."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val2, Name = val.itemName, Kind = SpawnKind.Item, IsWeapon = IsWeaponItem(val) }); job.Spawned++; num++; if (job.Spawned < job.Items.Count) { continue; } job.Finished = true; List list = new List(); foreach (string typeName in job.TypeNames) { list.Add(typeName + " x" + job.TypeCounts[typeName]); } Complete(job.Request, string.Format("OK Spawned {0} balanced item object(s) across {1} type(s): {2}.", job.Spawned, job.TypeNames.Count, string.Join(", ", list.ToArray()))); } } catch (Exception ex) { job.Finished = true; Complete(job.Request, $"ERROR Balanced item spread stopped after {job.Spawned}/{job.Items.Count}: {ex.Message}"); } } private static void InspectLoot(ControlRequest request, string target) { if (!target.Equals("loot", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Inspect target must be loot."); } IList obj = (GetField(ValuableDirector.instance, "valuableList") as IList) ?? throw new InvalidOperationException("The tracked loot list is unavailable."); List list = new List(); foreach (object item in obj) { ValuableObject val = (ValuableObject)((item is ValuableObject) ? item : null); if ((Object)(object)val != (Object)null && !list.Contains(((Object)((Component)val).gameObject).name)) { list.Add(((Object)((Component)val).gameObject).name); } } List list2 = new List(); foreach (PrefabRef allValuable in Valuables.AllValuables) { if ((Object)(object)((PrefabRef)(object)allValuable).Prefab != (Object)null) { list2.Add(((Object)((PrefabRef)(object)allValuable).Prefab).name); } } Complete(request, "OK Loot inspection: tracked=[" + string.Join(", ", list.ToArray()) + "]; registered=[" + string.Join(", ", list2.ToArray()) + "]."); } private static void DuplicateLoot(ControlRequest request, string target) { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) if (!target.Equals("loot", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Duplicate target must be loot."); } IList obj = (GetField(ValuableDirector.instance, "valuableList") as IList) ?? throw new InvalidOperationException("The tracked loot list is unavailable."); List list = new List(); foreach (object item in obj) { ValuableObject val = (ValuableObject)((item is ValuableObject) ? item : null); if (!((Object)(object)val == (Object)null)) { PrefabRef val2 = FindValuablePrefab(val); if (val2 == null) { throw new InvalidOperationException("No registered valuable prefab matches existing loot '" + ((Object)((Component)val).gameObject).name + "'. No copies were spawned."); } list.Add(val2); } } if (list.Count == 0) { Complete(request, "OK Duplicated 0 loot object(s); the map had no tracked loot."); return; } PlayerAvatar val3 = RequireRequestPlayer(request); Shuffle(list); activeDuplicateLootJob = new DuplicateLootJob(request, list, ((Component)val3).transform.position); ProcessDuplicateLootJob(activeDuplicateLootJob); } private static void TopUpLootAfterOneDuplicate(ControlRequest request, string target) { //IL_0188: Unknown result type (might be due to invalid IL or missing references) if (!target.Equals("loot", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Top-up target must be loot."); } IList obj = (GetField(ValuableDirector.instance, "valuableList") as IList) ?? throw new InvalidOperationException("The tracked loot list is unavailable."); List list = new List(); List list2 = new List(); foreach (object item in obj) { ValuableObject val = (ValuableObject)((item is ValuableObject) ? item : null); if ((Object)(object)val == (Object)null) { continue; } PrefabRef val2 = FindValuablePrefab(val); if (val2 == null) { throw new InvalidOperationException("No registered valuable prefab matches existing loot '" + ((Object)((Component)val).gameObject).name + "'. No copies were spawned."); } int num = -1; for (int i = 0; i < list.Count; i++) { if (list[i] == val2) { num = i; break; } } if (num < 0) { list.Add(val2); list2.Add(1); } else { list2[num]++; } } List list3 = new List(); for (int j = 0; j < list.Count; j++) { int num2 = list2[j] / 2; for (int k = 0; k < num2; k++) { list3.Add(list[j]); } } if (list3.Count == 0) { Complete(request, "OK Added 0 loot object(s); no complete duplicated pairs were found."); return; } PlayerAvatar val3 = RequireRequestPlayer(request); Shuffle(list3); activeDuplicateLootJob = new DuplicateLootJob(request, list3, ((Component)val3).transform.position); ProcessDuplicateLootJob(activeDuplicateLootJob); } private static void ProcessDuplicateLootJob(DuplicateLootJob job) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_0045: Unknown result type (might be due to invalid IL or missing references) try { int num = 0; while (!job.Finished && num < 10) { if (job.Positions.Count < job.Prefabs.Count) { if (!TryFindClearPosition(job.Anchor, job.Positions, out var result)) { throw new InvalidOperationException("Could not reserve collision-free locations for all copies. No copies were spawned."); } job.Positions.Add(result); } else { PrefabRef val = job.Prefabs[job.Spawned]; GameObject val2 = Valuables.SpawnValuable(val, job.Positions[job.Spawned], Quaternion.identity); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException("REPOLib returned no spawned loot object for '" + ((Object)((PrefabRef)(object)val).Prefab).name + "'."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val2, Name = ((Object)((PrefabRef)(object)val).Prefab).name, Kind = SpawnKind.Loot, IsWeapon = false }); job.Spawned++; if (job.Spawned >= job.Prefabs.Count) { job.Finished = true; Complete(job.Request, $"OK Duplicated {job.Spawned} loot object(s) into distinct collision-free random locations."); } } num++; } } catch (Exception ex) { job.Finished = true; Complete(job.Request, $"ERROR Loot duplication stopped after {job.Spawned}/{job.Prefabs.Count}: {ex.Message}"); } } private static void BeginSpawn(ControlRequest request, SpawnKind kind, string[] parts, int maximum, string defaultPlacement) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar val = RequireRequestPlayer(request); string selector = Part(parts, 1, "random"); int requested = Mathf.Clamp(ParseInt(parts, 2, 1), 1, maximum); string placement = Part(parts, 3, defaultPlacement).ToLowerInvariant(); activeJob = new SpawnJob(request, kind, selector, placement, requested, ((Component)val).transform.position); ProcessSpawnJob(activeJob); } private static void ProcessSpawnJob(SpawnJob job) { try { int num = 0; while (!job.Finished && num < 10) { switch (job.Kind) { case SpawnKind.Enemy: SpawnEnemyStep(job); break; case SpawnKind.Loot: SpawnLootStep(job); break; case SpawnKind.Item: SpawnItemStep(job); break; case SpawnKind.Cart: SpawnCartStep(job); break; } num++; if (job.Spawned >= job.Requested) { job.Finished = true; string text = job.NameSummary.Format(); string result = string.Format("OK Spawned {0} {1} object(s){2}.", job.Spawned, job.Kind.ToString().ToLowerInvariant(), (text.Length == 0) ? string.Empty : (": " + text)); Complete(job.Request, result); } } } catch (Exception ex) { job.Finished = true; Complete(job.Request, $"ERROR Spawn stopped after {job.Spawned}/{job.Requested}: {ex.Message}"); } } private static void SpawnEnemyStep(SpawnJob job) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) EnemySetup val = FindEnemy(job.Selector); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("No enemy matches '" + job.Selector + "'."); } Vector3 result; if (job.Placement == "safe") { if (!TryFindClearEnemyPosition(job.Anchor, job.EnemyReservations, GetEnemyClearanceVolume(val), out result)) { throw new InvalidOperationException("No additional collision-free enemy placement was found."); } } else if (job.Placement == "at-player") { result = SemiFunc.EnemyRoamFindPoint(job.Anchor); } else { Vector3 val2 = Random.insideUnitSphere * 4f; val2.y = 0f; result = SemiFunc.EnemyRoamFindPoint(job.Anchor + val2); } List list = Enemies.SpawnEnemy(val, result, Quaternion.identity, false); if (list == null || list.Count == 0) { throw new InvalidOperationException("The enemy setup spawned no objects."); } List list2 = new List(); foreach (EnemyParent item in list) { if ((Object)(object)item != (Object)null) { list2.Add(item); } } if (list2.Count == 0) { throw new InvalidOperationException("The enemy setup returned no live objects."); } int num = CommandExecutionTranslation.AcceptedEnemyCountForSetup(job.Requested - job.Spawned, list2.Count, job.Placement == "safe"); EnemyDirector instance = EnemyDirector.instance; for (int i = num; i < list2.Count; i++) { DestroyEnemyInstance(list2[i], instance); } EnemyParent enemyParent = GetEnemyParent(val); string name = (((Object)(object)enemyParent == (Object)null) ? "unknown" : enemyParent.enemyName); for (int j = 0; j < num; j++) { EnemyParent val3 = list2[j]; SpawnedObjects.Add(new SpawnedObjectRecord { Instance = ((Component)val3).gameObject, Name = name, Kind = SpawnKind.Enemy, IsWeapon = false }); } AppendName(job, name, num); job.Spawned += num; } private static void DestroyEnemyInstance(EnemyParent enemy, EnemyDirector director) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)enemy == (Object)null) { return; } if ((Object)(object)director != (Object)null) { director.enemiesSpawned.Remove(enemy); LevelPoint val = ((EnemyFirstSpawnPointField == null) ? ((LevelPoint)null) : ((LevelPoint)EnemyFirstSpawnPointField.GetValue(enemy))); List list = ((EnemyFirstSpawnPointsField == null) ? null : ((List)EnemyFirstSpawnPointsField.GetValue(director))); if ((Object)(object)val != (Object)null) { list?.Remove(val); } } if (PhotonNetwork.InRoom) { PhotonNetwork.Destroy(((Component)enemy).gameObject); } else { Object.Destroy((Object)(object)((Component)enemy).gameObject); } } private static void SpawnLootStep(SpawnJob job) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) PrefabRef val = FindValuable(job.Selector, job.Spawned); if (val == null) { throw new InvalidOperationException("No loot matches '" + job.Selector + "'."); } Vector3 placement = GetPlacement(job); GameObject val2 = Valuables.SpawnValuable(val, placement, Quaternion.identity); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException("REPOLib returned no spawned loot object."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val2, Name = ((Object)((PrefabRef)(object)val).Prefab).name, Kind = SpawnKind.Loot, IsWeapon = false }); AppendName(job, ((Object)((PrefabRef)(object)val).Prefab).name, 1); job.Spawned++; } private static void SpawnItemStep(SpawnJob job) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Item val = FindItem(job.Selector); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("No item matches '" + job.Selector + "'."); } Vector3 placement = GetPlacement(job); GameObject val2 = Items.SpawnItem(val, placement, Quaternion.identity); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException("REPOLib returned no spawned item object."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val2, Name = val.itemName, Kind = SpawnKind.Item, IsWeapon = IsWeaponItem(val) }); AppendName(job, val.itemName, 1); job.Spawned++; } private static void SpawnCartStep(SpawnJob job) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) string text = FindCartItemName(job.Selector); if (text == null) { throw new InvalidOperationException("No cart item matches '" + job.Selector + "'."); } Vector3 placement = GetPlacement(job); GameObject val = PhotonNetwork.InstantiateRoomObject("Items/" + text, placement, Quaternion.identity, (byte)0, (object[])null); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("Photon could not spawn the cart item '" + text + "'."); } SpawnedObjects.Add(new SpawnedObjectRecord { Instance = val, Name = text, Kind = SpawnKind.Cart, IsWeapon = false }); AppendName(job, text, 1); job.Spawned++; } private static Vector3 GetPlacement(SpawnJob job) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) return GetPlacement(job.Placement, job.Anchor, job.ReservedPositions); } private static Vector3 GetPlacement(string placement, Vector3 anchor, List reservedPositions) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (placement == "at-player") { return anchor + Vector3.up * 1.5f; } if (placement == "near-player") { Vector3 val = Random.insideUnitSphere * 3f; val.y = Math.Abs(val.y) + 1f; return anchor + val; } if (!TryFindClearPosition(anchor, reservedPositions, out var result)) { throw new InvalidOperationException("No additional collision-free placement was found."); } reservedPositions.Add(result); return result; } private static bool TryFindClearPosition(Vector3 origin, List reserved, out Vector3 result) { //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_016f: 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) LevelGenerator instance = LevelGenerator.Instance; List list = (((Object)(object)instance == (Object)null) ? null : instance.LevelPathPoints); int num = EnemyClearancePolicy.BuildGameplaySolidMask((Func)LayerMask.NameToLayer); for (int i = 0; i < 500; i++) { Vector3 val; if (list != null && list.Count > 0 && i % 2 == 0) { val = ((Component)list[Random.Range(0, list.Count)]).transform.position; } else { float num2 = Random.Range(0f, MathF.PI * 2f); float num3 = Random.Range(4f, 30f); val = origin + new Vector3(Mathf.Cos(num2), 0f, Mathf.Sin(num2)) * num3; } Vector3 val2 = SemiFunc.EnemyRoamFindPoint(val) + Vector3.up * 1.75f; bool flag = false; foreach (Vector3 item in reserved) { if (Vector3.Distance(val2, item) < 4f) { flag = true; break; } } if (flag) { continue; } Collider[] array = Physics.OverlapBox(val2, new Vector3(1.35f, 1.25f, 1.35f), Quaternion.identity, num, (QueryTriggerInteraction)1); bool flag2 = false; Collider[] array2 = array; foreach (Collider val3 in array2) { if ((Object)(object)val3 != (Object)null && !val3.isTrigger) { flag2 = true; break; } } if (!flag2) { result = val2; return true; } } result = Vector3.zero; return false; } private static bool TryFindClearEnemyPosition(Vector3 origin, List reserved, EnemyClearanceVolume clearance, out Vector3 result) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: 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) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) LevelGenerator instance = LevelGenerator.Instance; List list = (((Object)(object)instance == (Object)null) ? null : instance.LevelPathPoints); int num = EnemyClearancePolicy.BuildGameplaySolidMask((Func)LayerMask.NameToLayer); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < 500; i++) { Vector3 val; if (list != null && list.Count > 0 && i % 2 == 0) { val = ((Component)list[Random.Range(0, list.Count)]).transform.position; } else { float num2 = Random.Range(0f, MathF.PI * 2f); float num3 = Random.Range(4f, 30f); val = origin + new Vector3(Mathf.Cos(num2), 0f, Mathf.Sin(num2)) * num3; } Vector3 val2 = SemiFunc.EnemyRoamFindPoint(val); bool flag = false; foreach (EnemyPlacementReservation item in reserved) { float num4 = val2.x - item.Position.x; float num5 = val2.z - item.Position.z; float num6 = clearance.HorizontalRadius + item.HorizontalRadius + 0.5f; if (num4 * num4 + num5 * num5 < num6 * num6) { flag = true; break; } } if (flag) { continue; } Collider[] array = Physics.OverlapBox(val2 + clearance.CenterOffset, clearance.HalfExtents, Quaternion.identity, num, (QueryTriggerInteraction)1); bool flag2 = false; Collider[] array2 = array; foreach (Collider val3 in array2) { if ((Object)(object)val3 != (Object)null && !val3.isTrigger) { string text = LayerMask.LayerToName(((Component)val3).gameObject.layer); string key = (string.IsNullOrEmpty(text) ? ((Component)val3).gameObject.layer.ToString() : text) + ":" + ((Object)val3).name; dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; flag2 = true; break; } } if (!flag2) { reserved.Add(new EnemyPlacementReservation(val2, clearance.HorizontalRadius)); result = val2; return true; } } string text2 = string.Empty; int num7 = 0; foreach (KeyValuePair item2 in dictionary) { if (num7 >= 8) { break; } if (text2.Length > 0) { text2 += ", "; } text2 = text2 + item2.Key + " x" + item2.Value; num7++; } Plugin.Log.LogWarning((object)$"Enemy clearance rejected all candidates. center={clearance.CenterOffset}, halfExtents={clearance.HalfExtents}, radius={clearance.HorizontalRadius:0.00}, mask={num}, blockers=[{text2}]"); result = Vector3.zero; return false; } private static EnemyClearanceVolume GetEnemyClearanceVolume(EnemySetup setup) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(-0.9f, 0.1f, -0.9f); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0.9f, 2.4f, 0.9f); if ((Object)(object)setup != (Object)null && setup.spawnObjects != null) { foreach (PrefabRef spawnObject in setup.spawnObjects) { GameObject val3 = ((PrefabRef)(object)spawnObject)?.Prefab; if (!((Object)(object)val3 == (Object)null) && TryGetAggregatePrefabBounds(val3, out var aggregate)) { Vector3 position = val3.transform.position; val = Vector3.Min(val, ((Bounds)(ref aggregate)).min - position); val2 = Vector3.Max(val2, ((Bounds)(ref aggregate)).max - position); } } } Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(0.2f, 0.2f, 0.2f); val -= val4; val2 += val4; val.y = EnemyClearancePolicy.ClampProbeBottomOffset(val.y); Vector3 centerOffset = (val + val2) * 0.5f; Vector3 halfExtents = (val2 - val) * 0.5f; float num = Mathf.Max(Mathf.Abs(val.x), Mathf.Abs(val2.x)); float num2 = Mathf.Max(Mathf.Abs(val.z), Mathf.Abs(val2.z)); float horizontalRadius = Mathf.Sqrt(num * num + num2 * num2); return new EnemyClearanceVolume(centerOffset, halfExtents, horizontalRadius); } private static bool TryGetAggregatePrefabBounds(GameObject prefab, out Bounds aggregate) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_010d: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) aggregate = default(Bounds); bool found = false; NavMeshAgent[] componentsInChildren = prefab.GetComponentsInChildren(true); Bounds candidate = default(Bounds); foreach (NavMeshAgent val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Vector3 lossyScale = ((Component)val).transform.lossyScale; float num = Mathf.Max(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.z)); float num2 = Mathf.Abs(lossyScale.y); if (EnemyClearancePolicy.IsNavigationEnvelopeUsable(val.radius, val.height, val.baseOffset, num, num2)) { float num3 = val.radius * num; float num4 = val.height * num2; float num5 = val.baseOffset * num2; ((Bounds)(ref candidate))..ctor(((Component)val).transform.position + Vector3.up * (num5 + num4 * 0.5f), new Vector3(num3 * 2f, num4, num3 * 2f)); EncapsulateBounds(ref aggregate, ref found, candidate); } } } if (found && HasUsableBounds(aggregate)) { return true; } aggregate = default(Bounds); found = false; Collider[] componentsInChildren2 = prefab.GetComponentsInChildren(true); foreach (Collider val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && EnemyClearancePolicy.IsBodyGeometryEligible(val2.enabled, val2.isTrigger, IsActiveInPrefabHierarchy(((Component)val2).transform, prefab.transform), (Object)(object)val2.attachedRigidbody != (Object)null)) { Bounds bounds = val2.bounds; if (HasUsableBounds(bounds)) { EncapsulateBounds(ref aggregate, ref found, bounds); } } } if (found && !HasUsableBounds(aggregate)) { aggregate = default(Bounds); found = false; } if (!found) { Renderer[] componentsInChildren3 = prefab.GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren3) { if (!((Object)(object)val3 == (Object)null) && EnemyClearancePolicy.IsBodyGeometryEligible(val3.enabled, isTrigger: false, IsActiveInPrefabHierarchy(((Component)val3).transform, prefab.transform), attachedToRigidbody: false)) { Bounds bounds2 = val3.bounds; if (HasUsableBounds(bounds2)) { EncapsulateBounds(ref aggregate, ref found, bounds2); } } } } if (found) { return HasUsableBounds(aggregate); } return false; } private static bool IsActiveInPrefabHierarchy(Transform componentTransform, Transform prefabRoot) { if ((Object)(object)componentTransform == (Object)null || (Object)(object)prefabRoot == (Object)null) { return false; } Transform val = componentTransform; while ((Object)(object)val != (Object)null) { if (!((Component)val).gameObject.activeSelf) { return false; } if ((Object)(object)val == (Object)(object)prefabRoot) { return true; } val = val.parent; } return false; } private static bool HasUsableBounds(Bounds candidate) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0030: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) Vector3 center = ((Bounds)(ref candidate)).center; Vector3 size = ((Bounds)(ref candidate)).size; if (float.IsNaN(center.x) || float.IsInfinity(center.x) || float.IsNaN(center.y) || float.IsInfinity(center.y) || float.IsNaN(center.z) || float.IsInfinity(center.z) || float.IsNaN(size.x) || float.IsInfinity(size.x) || float.IsNaN(size.y) || float.IsInfinity(size.y) || float.IsNaN(size.z) || float.IsInfinity(size.z)) { return false; } return ((Vector3)(ref size)).sqrMagnitude > 1E-06f; } private static void EncapsulateBounds(ref Bounds aggregate, ref bool found, Bounds candidate) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!found) { aggregate = candidate; found = true; } else { ((Bounds)(ref aggregate)).Encapsulate(candidate); } } private static void DespawnEnemies(ControlRequest request, string selector, int keep) { EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null) { throw new InvalidOperationException("The enemy director is unavailable."); } keep = Math.Max(0, keep); List list = new List(); EnemyParent[] array = instance.enemiesSpawned.ToArray(); foreach (EnemyParent val in array) { if (!((Object)(object)val == (Object)null) && (selector.Equals("all", StringComparison.OrdinalIgnoreCase) || val.enemyName.IndexOf(selector, StringComparison.OrdinalIgnoreCase) >= 0)) { list.Add(val); } } int num = 0; for (int j = keep; j < list.Count; j++) { EnemyParent val2 = list[j]; instance.enemiesSpawned.Remove(val2); PhotonNetwork.Destroy(((Component)val2).gameObject); num++; } Complete(request, $"OK Despawned {num} matching enemy object(s); kept {Math.Min(keep, list.Count)}."); } private static void DespawnItems(ControlRequest request, string selector) { if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { throw new InvalidOperationException("Only the host can despawn network items."); } bool flag = selector.Equals("weapon", StringComparison.OrdinalIgnoreCase) || selector.Equals("weapons", StringComparison.OrdinalIgnoreCase); int num = 0; for (int num2 = SpawnedObjects.Count - 1; num2 >= 0; num2--) { SpawnedObjectRecord spawnedObjectRecord = SpawnedObjects[num2]; if ((Object)(object)spawnedObjectRecord.Instance == (Object)null) { SpawnedObjects.RemoveAt(num2); continue; } if (spawnedObjectRecord.Kind != SpawnKind.Item && spawnedObjectRecord.Kind != SpawnKind.Cart) { continue; } bool num3; if (!flag) { if (selector.Equals("all", StringComparison.OrdinalIgnoreCase)) { goto IL_00bd; } num3 = spawnedObjectRecord.Name.IndexOf(selector, StringComparison.OrdinalIgnoreCase) >= 0; } else { num3 = spawnedObjectRecord.IsWeapon; } if (!num3) { continue; } goto IL_00bd; IL_00bd: DestroySpawnedObject(spawnedObjectRecord); SpawnedObjects.RemoveAt(num2); num++; } Complete(request, $"OK Despawned {num} matching bridge-spawned item object(s) for '{selector}'."); } private static void DespawnSpawnedObjects(ControlRequest request, string kindText, string selector, int requested) { SpawnKind? spawnKind = ParseSpawnKind(kindText); if (!spawnKind.HasValue && !kindText.Equals("all", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Unknown spawned-object kind '" + kindText + "'."); } int num = ((requested < 0) ? int.MaxValue : Mathf.Clamp(requested, 1, 500)); int num2 = 0; int num3 = SpawnedObjects.Count - 1; while (num3 >= 0 && num2 < num) { SpawnedObjectRecord spawnedObjectRecord = SpawnedObjects[num3]; if ((Object)(object)spawnedObjectRecord.Instance == (Object)null) { SpawnedObjects.RemoveAt(num3); } else { bool num4 = !spawnKind.HasValue || spawnedObjectRecord.Kind == spawnKind.Value || (spawnKind.Value == SpawnKind.Item && spawnedObjectRecord.Kind == SpawnKind.Cart); bool flag = selector.Equals("all", StringComparison.OrdinalIgnoreCase) || spawnedObjectRecord.Name.Equals(selector, StringComparison.OrdinalIgnoreCase); if (num4 && flag) { DestroySpawnedObject(spawnedObjectRecord); SpawnedObjects.RemoveAt(num3); num2++; } } num3--; } Complete(request, $"OK Despawned {num2} matching mod-spawned {kindText} object(s) for '{selector}'."); } private static SpawnKind? ParseSpawnKind(string value) { if (value.Equals("enemy", StringComparison.OrdinalIgnoreCase)) { return SpawnKind.Enemy; } if (value.Equals("valuable", StringComparison.OrdinalIgnoreCase) || value.Equals("loot", StringComparison.OrdinalIgnoreCase)) { return SpawnKind.Loot; } if (value.Equals("item", StringComparison.OrdinalIgnoreCase)) { return SpawnKind.Item; } if (value.Equals("cart", StringComparison.OrdinalIgnoreCase)) { return SpawnKind.Cart; } return null; } private static void DestroySpawnedObject(SpawnedObjectRecord record) { if (record == null || (Object)(object)record.Instance == (Object)null) { return; } if (record.Kind == SpawnKind.Enemy) { EnemyParent val = record.Instance.GetComponent() ?? record.Instance.GetComponentInChildren(); if ((Object)(object)val != (Object)null) { DestroyEnemyInstance(val, EnemyDirector.instance); return; } } else if (record.Kind == SpawnKind.Loot && (Object)(object)ValuableDirector.instance != (Object)null) { IList list = GetField(ValuableDirector.instance, "valuableList") as IList; ValuableObject val2 = record.Instance.GetComponent() ?? record.Instance.GetComponentInChildren(); if (list != null && (Object)(object)val2 != (Object)null) { list.Remove(val2); } } else if ((record.Kind == SpawnKind.Item || record.Kind == SpawnKind.Cart) && (Object)(object)ItemManager.instance != (Object)null) { ItemAttributes val3 = record.Instance.GetComponent() ?? record.Instance.GetComponentInChildren(); if ((Object)(object)val3 != (Object)null) { ItemManager.instance.spawnedItems.Remove(val3); } } if (PhotonNetwork.InRoom) { PhotonNetwork.Destroy(record.Instance); } else { Object.Destroy((Object)(object)record.Instance); } } private static void SetAutomaticEnemies(ControlRequest request, string setting) { EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null) { throw new InvalidOperationException("The enemy director is unavailable."); } bool flag; if (setting.Equals("on", StringComparison.OrdinalIgnoreCase) || setting == "1" || setting.Equals("true", StringComparison.OrdinalIgnoreCase)) { flag = true; } else { if (!setting.Equals("off", StringComparison.OrdinalIgnoreCase) && !(setting == "0") && !setting.Equals("false", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Auto setting must be on or off."); } flag = false; } ((Behaviour)instance).enabled = flag; Complete(request, "OK Automatic enemy spawning is " + (flag ? "enabled." : "disabled.")); } private static void UnstickLoot(ControlRequest request) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) ValuableDirector instance = ValuableDirector.instance; PlayerAvatar val = RequireRequestPlayer(request); IList obj = (GetField(instance, "valuableList") as IList) ?? throw new InvalidOperationException("The tracked loot list is unavailable."); List list = new List(); foreach (object item in obj) { ValuableObject val2 = (ValuableObject)((item is ValuableObject) ? item : null); if (!((Object)(object)val2 == (Object)null)) { PhysGrabObject val3 = ((Component)val2).GetComponent() ?? ((Component)val2).GetComponentInParent(); if ((Object)(object)val3 != (Object)null && IsStuck(val3)) { list.Add(val3); } } } List list2 = new List(); int num = 0; foreach (PhysGrabObject item2 in list) { if (!TryFindClearPosition(((Component)val).transform.position, list2, out var result)) { break; } list2.Add(result); item2.Teleport(result, Quaternion.identity); if ((Object)(object)item2.rb != (Object)null) { item2.rb.velocity = Vector3.zero; item2.rb.angularVelocity = Vector3.zero; } num++; } Complete(request, "OK Moved " + num + " stuck loot object(s) to clear positions."); } private static bool IsStuck(PhysGrabObject phys) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) Collider[] componentsInChildren = ((Component)phys).GetComponentsInChildren(); Vector3 val3 = default(Vector3); float num = default(float); foreach (Collider val in componentsInChildren) { if ((Object)(object)val == (Object)null || !val.enabled || val.isTrigger) { continue; } Bounds bounds = val.bounds; Collider[] array = Physics.OverlapBox(((Bounds)(ref bounds)).center, ((Bounds)(ref bounds)).extents * 0.95f, ((Component)val).transform.rotation, -1, (QueryTriggerInteraction)1); foreach (Collider val2 in array) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val) && !((Object)(object)val2.attachedRigidbody != (Object)null) && !((Component)val2).transform.IsChildOf(((Component)phys).transform) && Physics.ComputePenetration(val, ((Component)val).transform.position, ((Component)val).transform.rotation, val2, ((Component)val2).transform.position, ((Component)val2).transform.rotation, ref val3, ref num) && num > 0.05f && (Mathf.Abs(val3.y) < 0.75f || num > 0.5f)) { return true; } } } return false; } private static void ReportStatus(ControlRequest request) { EnemyDirector instance = EnemyDirector.instance; ValuableDirector instance2 = ValuableDirector.instance; int num = ((!((Object)(object)instance == (Object)null)) ? instance.enemiesSpawned.Count : 0); int num2 = ((!((Object)(object)instance2 == (Object)null)) ? GetListCount(instance2, "valuableList") : 0); bool flag = (Object)(object)instance != (Object)null && ((Behaviour)instance).enabled; Complete(request, $"OK Status: enemies={num}, loot={num2}, automaticEnemySpawning={flag}."); } private static EnemySetup FindEnemy(string selector) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Invalid comparison between Unknown and I4 bool flag = selector.Equals("random", StringComparison.OrdinalIgnoreCase) || selector.Equals("randomhigh", StringComparison.OrdinalIgnoreCase); bool flag2 = flag || selector.Equals("high", StringComparison.OrdinalIgnoreCase); EnemySetup result = null; EnemySetup val = null; int num = 0; foreach (EnemySetup allEnemy in Enemies.AllEnemies) { EnemyParent enemyParent = GetEnemyParent(allEnemy); if ((Object)(object)enemyParent == (Object)null) { continue; } if (!flag2) { if (enemyParent.enemyName.Equals(selector, StringComparison.OrdinalIgnoreCase)) { return allEnemy; } if ((Object)(object)val == (Object)null && enemyParent.enemyName.IndexOf(selector, StringComparison.OrdinalIgnoreCase) >= 0) { val = allEnemy; } } if (flag2 && (int)enemyParent.difficulty == 2) { if (!flag && enemyParent.enemyName.IndexOf("Reaper", StringComparison.OrdinalIgnoreCase) >= 0) { return allEnemy; } num++; if (Random.Range(0, num) == 0) { result = allEnemy; } } } if (!flag2) { return val; } return result; } private static PrefabRef FindValuable(string selector, int index) { if (selector.Equals("expensive", StringComparison.OrdinalIgnoreCase)) { selector = ExpensiveLootNames[index % ExpensiveLootNames.Length]; } if (selector.Equals("medium", StringComparison.OrdinalIgnoreCase)) { if (GetField(ValuableDirector.instance, "mediumValuables") is IList list) { List list2 = new List(); foreach (object item in list) { PrefabRef val = (PrefabRef)((item is PrefabRef) ? item : null); if (val != null && (Object)(object)((PrefabRef)(object)val).Prefab != (Object)null) { list2.Add(val); } } if (list2.Count > 0) { return list2[Random.Range(0, list2.Count)]; } } return null; } IReadOnlyList allValuables = Valuables.AllValuables; if (selector.Equals("random", StringComparison.OrdinalIgnoreCase)) { if (allValuables.Count != 0) { return allValuables[Random.Range(0, allValuables.Count)]; } return null; } PrefabRef val2 = null; foreach (PrefabRef item2 in allValuables) { GameObject prefab = ((PrefabRef)(object)item2).Prefab; if (!((Object)(object)prefab == (Object)null)) { string text = NormalizeObjectName(((Object)prefab).name); if (text.Equals(selector, StringComparison.OrdinalIgnoreCase)) { return item2; } if (val2 == null && text.IndexOf(selector, StringComparison.OrdinalIgnoreCase) >= 0) { val2 = item2; } } } return val2; } private static PrefabRef FindValuablePrefab(ValuableObject valuable) { List list = new List(); AddObjectName(list, ((Object)((Component)valuable).gameObject).name); AddObjectName(list, ((Object)((Component)((Component)valuable).transform.root).gameObject).name); PhysGrabObject val = ((Component)valuable).GetComponent() ?? ((Component)valuable).GetComponentInParent(); if ((Object)(object)val != (Object)null) { AddObjectName(list, ((Object)((Component)val).gameObject).name); } foreach (PrefabRef allValuable in Valuables.AllValuables) { if ((Object)(object)((PrefabRef)(object)allValuable).Prefab == (Object)null) { continue; } string text = NormalizeObjectName(((Object)((PrefabRef)(object)allValuable).Prefab).name); foreach (string item in list) { if (text.Equals(item, StringComparison.OrdinalIgnoreCase)) { return allValuable; } } } PrefabRef result = null; int num = 0; foreach (PrefabRef allValuable2 in Valuables.AllValuables) { if ((Object)(object)((PrefabRef)(object)allValuable2).Prefab == (Object)null) { continue; } string text2 = NormalizeObjectName(((Object)((PrefabRef)(object)allValuable2).Prefab).name); foreach (string item2 in list) { int num2 = Math.Min(item2.Length, text2.Length); if ((item2.StartsWith(text2, StringComparison.OrdinalIgnoreCase) || text2.StartsWith(item2, StringComparison.OrdinalIgnoreCase)) && num2 > num) { result = allValuable2; num = num2; } } } return result; } private static void AddObjectName(List names, string name) { string text = NormalizeObjectName(name); if (text.Length > 0 && !names.Contains(text)) { names.Add(text); } } private static string NormalizeObjectName(string name) { string text = (name ?? string.Empty).Trim(); while (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } return text; } private static void Shuffle(List values) { for (int num = values.Count - 1; num > 0; num--) { int index = Random.Range(0, num + 1); T value = values[num]; values[num] = values[index]; values[index] = value; } } private static Item FindItem(string selector) { IReadOnlyList allItems = Items.AllItems; if (selector.Equals("random", StringComparison.OrdinalIgnoreCase)) { if (allItems.Count != 0) { return allItems[Random.Range(0, allItems.Count)]; } return null; } if (selector.Equals("weapon", StringComparison.OrdinalIgnoreCase) || selector.Equals("weapons", StringComparison.OrdinalIgnoreCase)) { List list = new List(); foreach (Item item in allItems) { if (IsWeaponItem(item)) { list.Add(item); } } if (list.Count != 0) { return list[Random.Range(0, list.Count)]; } return null; } Item val = null; foreach (Item item2 in allItems) { if (!((Object)(object)item2 == (Object)null) && !string.IsNullOrWhiteSpace(item2.itemName)) { if (item2.itemName.Equals(selector, StringComparison.OrdinalIgnoreCase)) { return item2; } if ((Object)(object)val == (Object)null && item2.itemName.IndexOf(selector, StringComparison.OrdinalIgnoreCase) >= 0) { val = item2; } } } return val; } private static string FindCartItemName(string selector) { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null || instance.itemDictionary == null) { throw new InvalidOperationException("The game item dictionary is unavailable."); } bool flag = selector.Equals("small", StringComparison.OrdinalIgnoreCase) || selector.Equals("pocket", StringComparison.OrdinalIgnoreCase) || selector.IndexOf("pocket", StringComparison.OrdinalIgnoreCase) >= 0; string text = (flag ? "Item Cart Small" : "Item Cart Medium"); if (instance.itemDictionary.ContainsKey(text)) { return text; } foreach (string key in instance.itemDictionary.Keys) { if (key.IndexOf("cart", StringComparison.OrdinalIgnoreCase) >= 0 && (key.IndexOf("small", StringComparison.OrdinalIgnoreCase) >= 0 || key.IndexOf("pocket", StringComparison.OrdinalIgnoreCase) >= 0) == flag) { return key; } } return null; } private static bool IsWeaponItem(Item item) { if ((Object)(object)item == (Object)null) { return false; } if (IsWeaponDescriptor(item.itemName)) { return true; } FieldInfo[] fields = ((object)item).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!IsCategoryMember(fieldInfo.Name)) { continue; } try { object value = fieldInfo.GetValue(item); if (value != null && IsWeaponDescriptor(value.ToString())) { return true; } } catch { } } PropertyInfo[] properties = ((object)item).GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (!IsCategoryMember(propertyInfo.Name) || propertyInfo.GetIndexParameters().Length != 0) { continue; } try { object value2 = propertyInfo.GetValue(item, null); if (value2 != null && IsWeaponDescriptor(value2.ToString())) { return true; } } catch { } } return false; } private static bool IsCategoryMember(string name) { string text = (name ?? string.Empty).ToLowerInvariant(); if (!text.Contains("type") && !text.Contains("category") && !text.Contains("class") && !text.Contains("kind") && !text.Contains("tag")) { return text.Contains("weapon"); } return true; } private static bool IsWeaponDescriptor(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } char[] array = value.ToLowerInvariant().ToCharArray(); for (int i = 0; i < array.Length; i++) { if (!char.IsLetterOrDigit(array[i])) { array[i] = ' '; } } string text = " " + new string(array) + " "; string[] weaponTerms = WeaponTerms; foreach (string text2 in weaponTerms) { if (text.IndexOf(" " + text2 + " ", StringComparison.Ordinal) >= 0) { return true; } } return false; } internal static EnemyParent GetEnemyParent(EnemySetup setup) { foreach (PrefabRef spawnObject in setup.spawnObjects) { GameObject prefab = ((PrefabRef)(object)spawnObject).Prefab; if (!((Object)(object)prefab == (Object)null)) { EnemyParent component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { return component; } } } return null; } private static PlayerAvatar RequireLocalPlayer() { PlayerAvatar obj = SemiFunc.PlayerAvatarLocal(); if ((Object)(object)obj == (Object)null) { throw new InvalidOperationException("The local player is unavailable."); } return obj; } private static PlayerAvatar RequireRequestPlayer(ControlRequest request) { if (request != null && request.RequesterActorNumber > 0 && PhotonNetwork.InRoom) { List list = SemiFunc.PlayerGetList(); if (list != null) { foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { PhotonView val = (((Object)(object)item.photonView != (Object)null) ? item.photonView : ((Component)item).GetComponent()); if ((Object)(object)val != (Object)null && val.Owner != null && val.Owner.ActorNumber == request.RequesterActorNumber) { return item; } } } } throw new InvalidOperationException("The requesting player (actor " + request.RequesterActorNumber + ") is unavailable."); } return RequireLocalPlayer(); } private static object GetField(object instance, string name) { if (instance == null) { return null; } FieldInfo fieldInfo = AccessTools.Field(instance.GetType(), name); if (!(fieldInfo == null)) { return fieldInfo.GetValue(instance); } return null; } private static int GetListCount(object instance, string name) { if (GetField(instance, name) is ICollection collection) { return collection.Count; } return 0; } private static string Part(string[] parts, int index, string fallback) { if (parts.Length <= index || string.IsNullOrWhiteSpace(parts[index])) { return fallback; } return parts[index].Trim(); } private static int ParseInt(string[] parts, int index, int fallback) { if (!int.TryParse(Part(parts, index, fallback.ToString()), out var result)) { return fallback; } return result; } private static void AppendName(SpawnJob job, string name, int count) { job.NameSummary.Add(name, count); } internal static void Complete(ControlRequest request, string result) { Debug.Log((object)("[Codex Live Control] " + result)); request.Complete(result); } } [HarmonyPatch(typeof(RunManager), "Update")] internal static class MainThreadPatch { private static void Prefix() { Bridge.ProcessFrame(); } } } namespace RepoLiveControl.Runtime { internal enum ConsoleInputAction { Toggle, Close, AcceptCompletion, SelectPrevious, SelectNext, Submit } internal sealed class ConsoleInputGate { private readonly int[] lastAcceptedFrames; internal ConsoleInputGate() { lastAcceptedFrames = new int[Enum.GetValues(typeof(ConsoleInputAction)).Length]; for (int i = 0; i < lastAcceptedFrames.Length; i++) { lastAcceptedFrames[i] = -1; } } internal bool TryAccept(ConsoleInputAction action, int frame, bool legacyPressedThisFrame, bool inputSystemPressedThisFrame, bool guiPressedThisFrame) { if (!legacyPressedThisFrame && !inputSystemPressedThisFrame && !guiPressedThisFrame) { return false; } if (action < ConsoleInputAction.Toggle || (int)action >= lastAcceptedFrames.Length) { throw new ArgumentOutOfRangeException("action"); } if (frame == lastAcceptedFrames[(int)action]) { return false; } lastAcceptedFrames[(int)action] = frame; return true; } } internal static class ConsoleToggleKeyMapping { internal static string ToInputSystemKeyName(string legacyKeyName) { string text = legacyKeyName ?? string.Empty; if (text.StartsWith("Alpha", StringComparison.Ordinal) && text.Length == "Alpha0".Length && char.IsDigit(text[text.Length - 1])) { return "Digit" + text[text.Length - 1]; } if (text.StartsWith("Keypad", StringComparison.Ordinal)) { return "Numpad" + text.Substring("Keypad".Length); } switch (text) { case "Return": return "Enter"; case "LeftControl": return "LeftCtrl"; case "RightControl": return "RightCtrl"; case "LeftWindows": case "LeftCommand": case "LeftApple": return "LeftMeta"; case "RightWindows": case "RightCommand": case "RightApple": return "RightMeta"; case "SysReq": case "Print": return "PrintScreen"; case "Break": return "Pause"; default: return text; } } } internal static class NetworkSessionSceneActivationPolicy { internal static bool ShouldActivate(bool managerAvailable, bool currentLevelAvailable, bool isLobby, bool isGameplay, bool isShop, bool isArena) { if (managerAvailable && currentLevelAvailable) { return isLobby || isGameplay || isShop || isArena; } return false; } } internal enum CommandEntityKind { Item, Valuable, Enemy } internal sealed class RuntimeCommandTarget { internal readonly CommandEntityKind Kind; internal readonly string Name; internal string KindName { get { if (Kind == CommandEntityKind.Enemy) { return "enemy"; } if (Kind == CommandEntityKind.Valuable) { return "valuable"; } return "item"; } } internal string Selector => KindName + ":" + Name; internal RuntimeCommandTarget(CommandEntityKind kind, string name) { Kind = kind; Name = name; } } internal static class RuntimeTargetCatalog { internal static List GetTargets() { List list = new List(); HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); try { foreach (Item allItem in Items.AllItems) { if (!((Object)(object)allItem == (Object)null) && !string.IsNullOrWhiteSpace(allItem.itemName)) { Add(list, seen, CommandEntityKind.Item, allItem.itemName.Trim()); } } } catch { } try { foreach (PrefabRef allValuable in Valuables.AllValuables) { if (allValuable != null && !((Object)(object)((PrefabRef)(object)allValuable).Prefab == (Object)null)) { Add(list, seen, CommandEntityKind.Valuable, NormalizeObjectName(((Object)((PrefabRef)(object)allValuable).Prefab).name)); } } } catch { } try { foreach (EnemySetup allEnemy in Enemies.AllEnemies) { EnemyParent val = (((Object)(object)allEnemy == (Object)null) ? null : Bridge.GetEnemyParent(allEnemy)); if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(val.enemyName)) { Add(list, seen, CommandEntityKind.Enemy, val.enemyName.Trim()); } } } catch { } list.Sort((RuntimeCommandTarget left, RuntimeCommandTarget right) => StringComparer.OrdinalIgnoreCase.Compare(left.Selector, right.Selector)); return list; } internal static List GetSelectors(bool includeAll) { List list = new List(); if (includeAll) { list.Add("enemy:all"); list.Add("item:all"); list.Add("valuable:all"); } foreach (RuntimeCommandTarget target in GetTargets()) { list.Add(target.Selector); } return list; } internal static bool TryResolve(string selector, bool allowAll, out RuntimeCommandTarget selected, out string error) { selected = null; error = string.Empty; string text = (selector ?? string.Empty).Trim(); if (text.Length == 0) { error = "ERROR A target is required."; return false; } CommandEntityKind? commandEntityKind = null; int num = text.IndexOf(':'); if (num > 0) { string text2 = text.Substring(0, num).Trim(); text = text.Substring(num + 1).Trim(); if (text2.Equals("enemy", StringComparison.OrdinalIgnoreCase)) { commandEntityKind = CommandEntityKind.Enemy; } else if (text2.Equals("valuable", StringComparison.OrdinalIgnoreCase) || text2.Equals("loot", StringComparison.OrdinalIgnoreCase)) { commandEntityKind = CommandEntityKind.Valuable; } else { if (!text2.Equals("item", StringComparison.OrdinalIgnoreCase)) { error = "ERROR Unknown target kind '" + text2 + "'. Use item:, valuable:, or enemy:."; return false; } commandEntityKind = CommandEntityKind.Item; } } if (allowAll && text.Equals("all", StringComparison.OrdinalIgnoreCase)) { if (!commandEntityKind.HasValue) { error = "ERROR Qualify all as item:all, valuable:all, or enemy:all."; return false; } selected = new RuntimeCommandTarget(commandEntityKind.Value, "all"); return true; } List list = new List(); foreach (RuntimeCommandTarget target in GetTargets()) { if ((!commandEntityKind.HasValue || target.Kind == commandEntityKind.Value) && target.Name.Equals(text, StringComparison.OrdinalIgnoreCase)) { list.Add(target); } } if (list.Count == 1) { selected = list[0]; return true; } if (list.Count > 1) { List list2 = new List(); foreach (RuntimeCommandTarget item in list) { list2.Add(item.Selector); } error = "ERROR Target is ambiguous. Use one of: " + string.Join(", ", list2.ToArray()) + "."; return false; } error = "ERROR No canonical target matches '" + selector + "'. Choose a fuzzy autocomplete suggestion with Tab before executing."; return false; } private static void Add(List targets, HashSet seen, CommandEntityKind kind, string name) { if (!string.IsNullOrWhiteSpace(name)) { string item = kind.ToString() + "\0" + name; if (seen.Add(item)) { targets.Add(new RuntimeCommandTarget(kind, name)); } } } private static string NormalizeObjectName(string value) { string text = (value ?? string.Empty).Trim(); while (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - 7).Trim(); } return text; } } internal static class SlashCommandRuntime { internal static bool TryTranslateOrComplete(ControlRequest request, string rawCommand, out string translatedCommand) { translatedCommand = string.Empty; CommandParseResult commandParseResult = SlashCommandParser.Parse(rawCommand); if (!commandParseResult.Success) { Bridge.Complete(request, "ERROR " + commandParseResult.ErrorMessage); return false; } ParsedSlashCommand command = commandParseResult.Command; switch (command.Kind) { case SlashCommandKind.Help: Bridge.Complete(request, HelpText()); return false; case SlashCommandKind.Permissions: Bridge.Complete(request, RequireConsoleRuntime().Permissions.Describe()); return false; case SlashCommandKind.Grant: Grant(request, command.Player); return false; case SlashCommandKind.Revoke: Revoke(request, command.Player); return false; case SlashCommandKind.Spawn: return TryTranslateSpawn(request, command, out translatedCommand); case SlashCommandKind.Despawn: return TryTranslateDespawn(request, command, out translatedCommand); default: Bridge.Complete(request, "ERROR Unsupported slash command."); return false; } } private static bool TryTranslateSpawn(ControlRequest request, ParsedSlashCommand command, out string translated) { translated = string.Empty; if (!RuntimeTargetCatalog.TryResolve(command.Target, allowAll: false, out var selected, out var error)) { Bridge.Complete(request, error); return false; } translated = CommandExecutionTranslation.TranslateSpawn(ToCommandTargetKind(selected.Kind), selected.Name, command.Count.Value, command.Location); return true; } private static bool TryTranslateDespawn(ControlRequest request, ParsedSlashCommand command, out string translated) { translated = string.Empty; if (!RuntimeTargetCatalog.TryResolve(command.Target, allowAll: true, out var selected, out var error)) { Bridge.Complete(request, error); return false; } translated = CommandExecutionTranslation.TranslateDespawn(ToCommandTargetKind(selected.Kind), selected.Name, command.Count); return true; } private static CommandTargetKind ToCommandTargetKind(CommandEntityKind kind) { return kind switch { CommandEntityKind.Item => CommandTargetKind.Item, CommandEntityKind.Valuable => CommandTargetKind.Valuable, CommandEntityKind.Enemy => CommandTargetKind.Enemy, _ => CommandTargetKind.Unspecified, }; } private static void Grant(ControlRequest request, string player) { if (request.Source == CommandRequestSource.RemoteClient) { Bridge.Complete(request, "ERROR /grant can only be run locally by the host."); return; } if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { Bridge.Complete(request, "ERROR Only the lobby host can grant command permission."); return; } CommandConsoleRuntime commandConsoleRuntime = RequireConsoleRuntime(); commandConsoleRuntime.Permissions.TryGrant(player, out var actorNumber, out var message); Bridge.Complete(request, message); if (message.StartsWith("OK", StringComparison.Ordinal) && actorNumber > 0) { commandConsoleRuntime.Network.SendNotice(actorNumber, "OK The host granted you REPO Command Console permission."); } } private static void Revoke(ControlRequest request, string player) { if (request.Source == CommandRequestSource.RemoteClient) { Bridge.Complete(request, "ERROR /revoke can only be run locally by the host."); return; } if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { Bridge.Complete(request, "ERROR Only the lobby host can revoke command permission."); return; } CommandConsoleRuntime commandConsoleRuntime = RequireConsoleRuntime(); commandConsoleRuntime.Permissions.TryRevoke(player, out var actorNumber, out var message); Bridge.Complete(request, message); if (message.StartsWith("OK", StringComparison.Ordinal) && actorNumber > 0) { commandConsoleRuntime.Network.SendNotice(actorNumber, "OK The host revoked your REPO Command Console permission."); } } private static CommandConsoleRuntime RequireConsoleRuntime() { if ((Object)(object)Plugin.Instance == (Object)null || Plugin.Instance.CommandConsole == null) { throw new InvalidOperationException("The in-game command console runtime is unavailable."); } return Plugin.Instance.CommandConsole; } private static string HelpText() { return "OK Commands: /spawn [count=1] [player-location|random-non-collision-location]; /despawn [count=all]; /grant ; /revoke ; /permissions; /help. Use Up/Down and Tab for fuzzy autocomplete."; } } } namespace RepoLiveControl.Networking { public static class CommandNetworkPolicy { public const string Magic = "com.jameskieley.repo.commandconsole"; public const int ProtocolVersion = 2; public const string RequestKind = "request"; public const string ResponseKind = "response"; public const string NoticeKind = "notice"; public const int MaximumCommandLength = 512; public const int MaximumResponseLength = 2048; public static CommandRequestValidation ValidateRemoteCommand(string command, bool isAllowed) { if (string.IsNullOrWhiteSpace(command) || command.Length > 512) { return CommandRequestValidation.Deny("ERROR Malformed command request."); } if (!IsSlashCommandPayload(command)) { return CommandRequestValidation.Deny("ERROR Network commands must use the slash-command interface."); } CommandParseResult commandParseResult = SlashCommandParser.Parse(command); if (!commandParseResult.Success) { return CommandRequestValidation.Deny("ERROR " + commandParseResult.ErrorMessage); } if (IsHostOnlyVerb(command)) { return CommandRequestValidation.Deny("ERROR /grant and /revoke can only be run locally by the host."); } if (!isAllowed && !IsPublicVerb(command)) { return CommandRequestValidation.Deny("ERROR The host has not granted you command permission."); } return CommandRequestValidation.Allow(); } public static bool IsValidRequestId(string requestId) { if (string.IsNullOrEmpty(requestId) || requestId.Length != 32) { return false; } foreach (char c in requestId) { bool num = c >= '0' && c <= '9'; bool flag = c >= 'a' && c <= 'f'; bool flag2 = c >= 'A' && c <= 'F'; if (!num && !flag && !flag2) { return false; } } return true; } public static object[] Envelope(string kind, string requestId, string payload) { return new object[5] { "com.jameskieley.repo.commandconsole", 2, kind, requestId ?? string.Empty, payload ?? string.Empty }; } public static bool TryReadEnvelope(object[] values, out string kind, out string requestId, out string payload) { kind = string.Empty; requestId = string.Empty; payload = string.Empty; if (values == null || values.Length != 5 || !(values[0] is string) || !string.Equals((string)values[0], "com.jameskieley.repo.commandconsole", StringComparison.Ordinal)) { return false; } if (!(values[1] is int) || (int)values[1] != 2 || !(values[2] is string) || !(values[3] is string) || !(values[4] is string)) { return false; } kind = (string)values[2]; requestId = (string)values[3]; payload = (string)values[4]; if (!(kind == "request") && !(kind == "response")) { return kind == "notice"; } return true; } public static bool IsHostOnlyVerb(string command) { string verb = GetVerb(command); if (!(verb == "grant")) { return verb == "revoke"; } return true; } public static bool IsPublicVerb(string command) { string verb = GetVerb(command); if (!(verb == "help")) { return verb == "permissions"; } return true; } public static bool IsSlashCommandPayload(string command) { return (command ?? string.Empty).TrimStart().StartsWith("/", StringComparison.Ordinal); } public static string GetVerb(string command) { string text = (command ?? string.Empty).TrimStart(); if (text.StartsWith("/", StringComparison.Ordinal)) { text = text.Substring(1); } int num = text.IndexOfAny(new char[4] { ' ', '\t', '\r', '\n' }); return ((num < 0) ? text : text.Substring(0, num)).ToLowerInvariant(); } } public sealed class CommandRequestValidation { public bool Allowed { get; private set; } public string Error { get; private set; } private CommandRequestValidation(bool allowed, string error) { Allowed = allowed; Error = error; } internal static CommandRequestValidation Allow() { return new CommandRequestValidation(allowed: true, null); } internal static CommandRequestValidation Deny(string error) { return new CommandRequestValidation(allowed: false, error); } } public static class CommandIngressSessionPolicy { public static string Validate(bool cancelled, long? requiredSessionRevision, long? currentSessionRevision) { if (cancelled) { return "The command request was cancelled by its caller."; } if (requiredSessionRevision.HasValue && (!currentSessionRevision.HasValue || requiredSessionRevision.Value != currentSessionRevision.Value)) { return "The original lobby authorization expired."; } return null; } } public sealed class SlidingWindowRateLimiter { private readonly int maximumEvents; private readonly float windowSeconds; private readonly Dictionary> eventTimes = new Dictionary>(); public SlidingWindowRateLimiter(int maximumEvents, float windowSeconds) { if (maximumEvents <= 0) { throw new ArgumentOutOfRangeException("maximumEvents"); } if (windowSeconds <= 0f) { throw new ArgumentOutOfRangeException("windowSeconds"); } this.maximumEvents = maximumEvents; this.windowSeconds = windowSeconds; } public bool TryConsume(int actorNumber, float now) { if (actorNumber <= 0) { return false; } if (!eventTimes.TryGetValue(actorNumber, out var value)) { value = new Queue(); eventTimes[actorNumber] = value; } while (value.Count > 0 && now - value.Peek() > windowSeconds) { value.Dequeue(); } if (value.Count >= maximumEvents) { return false; } value.Enqueue(now); return true; } public void Clear() { eventTimes.Clear(); } } public sealed class RateLimitNoticeGate { private readonly float silenceSeconds; private readonly Dictionary nextNoticeAt = new Dictionary(); public RateLimitNoticeGate(float silenceSeconds) { if (silenceSeconds <= 0f) { throw new ArgumentOutOfRangeException("silenceSeconds"); } this.silenceSeconds = silenceSeconds; } public bool ShouldNotify(int actorNumber, float now) { if (actorNumber <= 0) { return false; } if (nextNoticeAt.TryGetValue(actorNumber, out var value) && now < value) { return false; } nextNoticeAt[actorNumber] = now + silenceSeconds; return true; } public void Clear() { nextNoticeAt.Clear(); } } public sealed class PendingCommandRegistry { private sealed class PendingCommand { internal int MasterActorNumber { get; private set; } internal long SessionRevision { get; private set; } internal float SentAt { get; private set; } internal PendingCommand(int masterActorNumber, long sessionRevision, float sentAt) { MasterActorNumber = masterActorNumber; SessionRevision = sessionRevision; SentAt = sentAt; } } private readonly float timeoutSeconds; private readonly Dictionary pending = new Dictionary(StringComparer.Ordinal); public int Count => pending.Count; public PendingCommandRegistry(float timeoutSeconds) { if (timeoutSeconds <= 0f) { throw new ArgumentOutOfRangeException("timeoutSeconds"); } this.timeoutSeconds = timeoutSeconds; } public bool TryAdd(string requestId, int masterActorNumber, long sessionRevision, float sentAt) { if (!CommandNetworkPolicy.IsValidRequestId(requestId) || masterActorNumber <= 0) { return false; } if (pending.ContainsKey(requestId)) { return false; } pending.Add(requestId, new PendingCommand(masterActorNumber, sessionRevision, sentAt)); return true; } public bool TryComplete(string requestId) { if (!string.IsNullOrEmpty(requestId)) { return pending.Remove(requestId); } return false; } public bool Remove(string requestId) { if (!string.IsNullOrEmpty(requestId)) { return pending.Remove(requestId); } return false; } public IReadOnlyList CollectFailures(float now, bool inRoom, int currentMasterActorNumber, long currentSessionRevision) { List list = new List(); List list2 = new List(); foreach (KeyValuePair item in pending) { string text = null; if (!inRoom || currentMasterActorNumber <= 0) { text = "ERROR The multiplayer room closed before the host responded."; } else if (item.Value.SessionRevision != currentSessionRevision) { text = "ERROR The multiplayer room changed before the host responded."; } else if (item.Value.MasterActorNumber != currentMasterActorNumber) { text = "ERROR The lobby host changed before the command completed."; } else if (now - item.Value.SentAt >= timeoutSeconds) { text = "ERROR Timed out waiting for the lobby host to respond."; } if (text != null) { list2.Add(item.Key); list.Add(new PendingCommandFailure(item.Key, text)); } } foreach (string item2 in list2) { pending.Remove(item2); } return list.AsReadOnly(); } public void Clear() { pending.Clear(); } } public sealed class PendingCommandFailure { public string RequestId { get; private set; } public string Error { get; private set; } internal PendingCommandFailure(string requestId, string error) { RequestId = requestId; Error = error; } } public sealed class SessionGrantLedger { private readonly HashSet grantedActors = new HashSet(); private bool inRoom; private string roomName = string.Empty; private int masterActorNumber = -1; public long Revision { get; private set; } public bool Synchronize(bool currentlyInRoom, string currentRoomName, int currentMasterActorNumber, IEnumerable currentActors) { currentRoomName = currentRoomName ?? string.Empty; bool flag = currentlyInRoom != inRoom || (currentlyInRoom && (!string.Equals(roomName, currentRoomName, StringComparison.Ordinal) || masterActorNumber != currentMasterActorNumber)); if (flag) { Revision++; grantedActors.Clear(); } inRoom = currentlyInRoom; roomName = (currentlyInRoom ? currentRoomName : string.Empty); masterActorNumber = (currentlyInRoom ? currentMasterActorNumber : (-1)); if (!currentlyInRoom) { grantedActors.Clear(); return flag; } HashSet hashSet = new HashSet(); if (currentActors != null) { foreach (int currentActor in currentActors) { if (currentActor > 0) { hashSet.Add(currentActor); } } } List list = new List(); foreach (int grantedActor in grantedActors) { if (!hashSet.Contains(grantedActor)) { list.Add(grantedActor); } } foreach (int item in list) { grantedActors.Remove(item); } return flag; } public bool Grant(int actorNumber) { if (inRoom && actorNumber > 0) { return grantedActors.Add(actorNumber); } return false; } public bool Revoke(int actorNumber) { if (actorNumber > 0) { return grantedActors.Remove(actorNumber); } return false; } public bool IsGranted(int actorNumber) { if (inRoom && actorNumber > 0) { return grantedActors.Contains(actorNumber); } return false; } public IReadOnlyList GetGrantedActors() { List list = new List(grantedActors); list.Sort(); return list.AsReadOnly(); } } internal sealed class CommandNetworkRouter : IOnEventCallback, IDisposable { private const int MaximumOutstandingPerActor = 2; private const int MaximumOutstandingGlobal = 32; private const int MaximumRememberedRequestIds = 2048; private readonly byte eventCode; private readonly PermissionService permissions; private readonly Action resultSink; private readonly PendingCommandRegistry pendingRequests = new PendingCommandRegistry(30f); private readonly SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(5, 3f); private readonly RateLimitNoticeGate rateLimitNoticeGate = new RateLimitNoticeGate(3f); private readonly PhotonCallbackRegistrationLifecycle callbackRegistration = new PhotonCallbackRegistrationLifecycle(); private readonly HashSet acceptedRemoteRequests = new HashSet(StringComparer.Ordinal); private readonly HashSet seenRemoteRequests = new HashSet(StringComparer.Ordinal); private readonly Queue seenRemoteRequestOrder = new Queue(); private readonly Dictionary outstandingByActor = new Dictionary(); private long observedSessionRevision = -1L; private bool disposed; internal CommandNetworkRouter(byte eventCode, PermissionService permissions, Action resultSink) { this.eventCode = eventCode; this.permissions = permissions; this.resultSink = resultSink; } internal string SendRequest(string command) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown Update(networkSessionSceneActive: true); if (!PhotonNetwork.InRoom || PhotonNetwork.MasterClient == null) { throw new InvalidOperationException("No multiplayer host is available."); } if (command == null || command.Length == 0 || command.Length > 512) { throw new InvalidOperationException("Command length must be between 1 and 512 characters."); } if (!CommandNetworkPolicy.IsSlashCommandPayload(command)) { throw new InvalidOperationException("Network commands must use the slash-command interface."); } CommandParseResult commandParseResult = SlashCommandParser.Parse(command); if (!commandParseResult.Success) { throw new InvalidOperationException(commandParseResult.ErrorMessage); } if (pendingRequests.Count > 0) { throw new InvalidOperationException("Wait for the previous host response before sending another command."); } string text = Guid.NewGuid().ToString("N"); int actorNumber = PhotonNetwork.MasterClient.ActorNumber; if (!pendingRequests.TryAdd(text, actorNumber, permissions.SessionRevision, Time.realtimeSinceStartup)) { throw new InvalidOperationException("Could not track the command request."); } if (!PhotonNetwork.RaiseEvent(eventCode, (object)CommandNetworkPolicy.Envelope("request", text, command), new RaiseEventOptions { Receivers = (ReceiverGroup)2 }, SendOptions.SendReliable)) { pendingRequests.Remove(text); throw new InvalidOperationException("Photon did not accept the command request."); } return text; } internal void Update(bool networkSessionSceneActive) { if (disposed) { return; } if (!networkSessionSceneActive) { callbackRegistration.Synchronize(roomActive: false, delegate { PhotonNetwork.AddCallbackTarget((object)this); }, delegate { PhotonNetwork.RemoveCallbackTarget((object)this); }); permissions.Reset(); return; } bool flag = PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null; callbackRegistration.Synchronize(flag, delegate { PhotonNetwork.AddCallbackTarget((object)this); }, delegate { PhotonNetwork.RemoveCallbackTarget((object)this); }); permissions.UpdateSession(); long sessionRevision = permissions.SessionRevision; if (observedSessionRevision < 0) { observedSessionRevision = sessionRevision; } else if (observedSessionRevision != sessionRevision) { observedSessionRevision = sessionRevision; rateLimiter.Clear(); rateLimitNoticeGate.Clear(); seenRemoteRequests.Clear(); seenRemoteRequestOrder.Clear(); } bool flag2 = flag; int currentMasterActorNumber = ((flag2 && PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); IReadOnlyList readOnlyList = pendingRequests.CollectFailures(Time.realtimeSinceStartup, flag2, currentMasterActorNumber, sessionRevision); if (resultSink == null) { return; } foreach (PendingCommandFailure item in readOnlyList) { resultSink(item.Error); } } internal void SendNotice(int targetActorNumber, string message) { if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && targetActorNumber > 0) { SendToActor("notice", string.Empty, message, targetActorNumber); } } public void OnEvent(EventData photonEvent) { if (disposed || photonEvent == null || photonEvent.Code != eventCode || !CommandNetworkPolicy.TryReadEnvelope(photonEvent.CustomData as object[], out var kind, out var requestId, out var payload)) { return; } if (kind == "request") { ReceiveRequest(photonEvent.Sender, requestId, payload); } else if (IsFromCurrentMaster(photonEvent.Sender)) { if (kind == "response") { ReceiveResponse(requestId, payload); } else if (kind == "notice" && resultSink != null) { resultSink(payload); } } } private void ReceiveRequest(int senderActorNumber, string requestId, string command) { if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || senderActorNumber <= 0) { return; } permissions.UpdateSession(); bool flag = CommandNetworkPolicy.IsValidRequestId(requestId); float realtimeSinceStartup = Time.realtimeSinceStartup; if (!rateLimiter.TryConsume(senderActorNumber, realtimeSinceStartup)) { if (rateLimitNoticeGate.ShouldNotify(senderActorNumber, realtimeSinceStartup)) { SendResponse(senderActorNumber, flag ? requestId : string.Empty, "ERROR Command rate limit exceeded; wait a moment and try again."); } return; } if (!flag) { SendResponse(senderActorNumber, string.Empty, "ERROR Malformed command request ID."); return; } CommandRequestValidation commandRequestValidation = CommandNetworkPolicy.ValidateRemoteCommand(command, permissions.IsAllowed(senderActorNumber)); if (!commandRequestValidation.Allowed) { SendResponse(senderActorNumber, requestId, commandRequestValidation.Error); return; } string requestKey = senderActorNumber + ":" + requestId; if (seenRemoteRequests.Contains(requestKey) || acceptedRemoteRequests.Contains(requestKey)) { SendResponse(senderActorNumber, requestId, "ERROR Duplicate command request ID."); return; } outstandingByActor.TryGetValue(senderActorNumber, out var value); if (value >= 2 || acceptedRemoteRequests.Count >= 32) { SendResponse(senderActorNumber, requestId, "ERROR Too many commands are already waiting for the host executor."); return; } long requiredSessionRevision = permissions.SessionRevision; bool requiresGrant = !CommandNetworkPolicy.IsPublicVerb(command); RememberRemoteRequest(requestKey); acceptedRemoteRequests.Add(requestKey); outstandingByActor[senderActorNumber] = value + 1; Bridge.Enqueue(new ControlRequest(command, CommandRequestSource.RemoteClient, senderActorNumber, delegate(string result) { ReleaseRemoteRequest(senderActorNumber, requestKey); if (permissions.SessionRevision == requiredSessionRevision) { SendResponse(senderActorNumber, requestId, result); } }, requiredSessionRevision, () => permissions.SessionRevision == requiredSessionRevision && (!requiresGrant || permissions.IsAllowed(senderActorNumber)))); } private void ReceiveResponse(string requestId, string response) { if (pendingRequests.TryComplete(requestId) && resultSink != null) { resultSink(response); } } private void SendResponse(int targetActorNumber, string requestId, string response) { SendToActor("response", requestId, response, targetActorNumber); } private void SendToActor(string kind, string requestId, string payload, int targetActorNumber) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom && targetActorNumber > 0) { string text = payload ?? string.Empty; if (text.Length > 2048) { text = text.Substring(0, 2048); } byte num = eventCode; object[] array = CommandNetworkPolicy.Envelope(kind, requestId, text); RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActorNumber }; if (!PhotonNetwork.RaiseEvent(num, (object)array, val, SendOptions.SendReliable) && Plugin.Log != null) { Plugin.Log.LogWarning((object)("Photon did not accept a " + kind + " event for actor " + targetActorNumber + ".")); } } } private void ReleaseRemoteRequest(int actorNumber, string requestKey) { acceptedRemoteRequests.Remove(requestKey); if (outstandingByActor.TryGetValue(actorNumber, out var value)) { if (value <= 1) { outstandingByActor.Remove(actorNumber); } else { outstandingByActor[actorNumber] = value - 1; } } } private void RememberRemoteRequest(string requestKey) { seenRemoteRequests.Add(requestKey); seenRemoteRequestOrder.Enqueue(requestKey); while (seenRemoteRequestOrder.Count > 2048) { string item = seenRemoteRequestOrder.Dequeue(); if (acceptedRemoteRequests.Contains(item)) { seenRemoteRequestOrder.Enqueue(item); } else { seenRemoteRequests.Remove(item); } } } private static bool IsFromCurrentMaster(int senderActorNumber) { if (PhotonNetwork.InRoom && PhotonNetwork.MasterClient != null) { return PhotonNetwork.MasterClient.ActorNumber == senderActorNumber; } return false; } public void Dispose() { if (!disposed) { disposed = true; callbackRegistration.Dispose(delegate { PhotonNetwork.RemoveCallbackTarget((object)this); }); pendingRequests.Clear(); rateLimiter.Clear(); rateLimitNoticeGate.Clear(); acceptedRemoteRequests.Clear(); seenRemoteRequests.Clear(); seenRemoteRequestOrder.Clear(); outstandingByActor.Clear(); } } } internal sealed class PermissionService { private readonly SessionGrantLedger grants = new SessionGrantLedger(); internal long SessionRevision => grants.Revision; internal void UpdateSession() { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { grants.Synchronize(currentlyInRoom: false, string.Empty, -1, new int[0]); return; } string currentRoomName = PhotonNetwork.CurrentRoom.Name ?? string.Empty; int currentMasterActorNumber = ((PhotonNetwork.MasterClient == null) ? (-1) : PhotonNetwork.MasterClient.ActorNumber); grants.Synchronize(currentlyInRoom: true, currentRoomName, currentMasterActorNumber, PhotonNetwork.CurrentRoom.Players.Keys); } internal void Reset() { grants.Synchronize(currentlyInRoom: false, string.Empty, -1, new int[0]); } internal bool IsAllowed(int actorNumber) { if (!PhotonNetwork.InRoom) { return true; } if (actorNumber <= 0 || PhotonNetwork.CurrentRoom == null) { return false; } if (!PhotonNetwork.CurrentRoom.Players.TryGetValue(actorNumber, out var value) || value == null) { return false; } if (!value.IsMasterClient) { return grants.IsGranted(actorNumber); } return true; } internal bool IsGranted(int actorNumber) { return grants.IsGranted(actorNumber); } internal bool TryGrant(string selector, out int actorNumber, out string message) { if (!TryResolveOtherPlayer(selector, grantedOnly: false, out var selected, out message)) { actorNumber = -1; return false; } actorNumber = selected.ActorNumber; if (grants.Grant(actorNumber)) { message = "OK Granted command permission to " + PlayerLabel(selected) + "."; } else { message = "OK " + PlayerLabel(selected) + " already has command permission."; } return true; } internal bool TryRevoke(string selector, out int actorNumber, out string message) { if (!TryResolveOtherPlayer(selector, grantedOnly: true, out var selected, out message)) { actorNumber = -1; return false; } actorNumber = selected.ActorNumber; if (grants.Revoke(actorNumber)) { message = "OK Revoked command permission from " + PlayerLabel(selected) + "."; } else { message = "OK " + PlayerLabel(selected) + " did not have command permission."; } return true; } internal string Describe() { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return "OK Permissions: single player/local host; no grants are required."; } List list = new List(); foreach (int grantedActor in grants.GetGrantedActors()) { if (PhotonNetwork.CurrentRoom.Players.TryGetValue(grantedActor, out var value) && value != null) { list.Add(PlayerLabel(value)); } } list.Sort(StringComparer.OrdinalIgnoreCase); if (list.Count != 0) { return "OK Granted players: " + string.Join(", ", list.ToArray()) + "."; } return "OK Permissions: host only; no non-host players are granted."; } internal List GetGrantCandidates() { return GetPlayerCandidates(grantedOnly: false); } internal List GetRevokeCandidates() { return GetPlayerCandidates(grantedOnly: true); } private List GetPlayerCandidates(bool grantedOnly) { List list = new List(); if (!PhotonNetwork.InRoom || PhotonNetwork.PlayerListOthers == null) { return list; } Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (val != null && (!grantedOnly || grants.IsGranted(val.ActorNumber))) { list.Add(PlayerSelector(val)); } } list.Sort(StringComparer.OrdinalIgnoreCase); return list; } private bool TryResolveOtherPlayer(string selector, bool grantedOnly, out Player selected, out string error) { selected = null; error = string.Empty; if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { error = "ERROR Player grants are only available in a multiplayer room."; return false; } string text = (selector ?? string.Empty).Trim(); if (TryParseActorSuffix(text, out var actorNumber)) { if (PhotonNetwork.CurrentRoom.Players.TryGetValue(actorNumber, out var value) && value != null && !value.IsMasterClient && (!grantedOnly || grants.IsGranted(actorNumber))) { selected = value; return true; } error = "ERROR No eligible non-host player has actor number " + actorNumber + "."; return false; } List list = new List(); List list2 = new List(); Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (val != null && (!grantedOnly || grants.IsGranted(val.ActorNumber))) { string text2 = val.NickName ?? string.Empty; if (text2.Equals(text, StringComparison.OrdinalIgnoreCase)) { list.Add(val); } else if (text.Length > 0 && text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { list2.Add(val); } } } List list3 = ((list.Count > 0) ? list : list2); if (list3.Count == 1) { selected = list3[0]; return true; } if (list3.Count > 1) { List list4 = new List(); foreach (Player item in list3) { list4.Add(PlayerSelector(item)); } error = "ERROR Player name is ambiguous. Use one of: " + string.Join(", ", list4.ToArray()) + "."; return false; } error = "ERROR No eligible non-host player matches '" + text + "'."; return false; } private static bool TryParseActorSuffix(string selector, out int actorNumber) { actorNumber = -1; int num = selector.LastIndexOf('#'); if (num >= 0 && num + 1 < selector.Length) { return int.TryParse(selector.Substring(num + 1), out actorNumber); } return false; } private static string PlayerSelector(Player player) { return (player.NickName ?? "Player") + "#" + player.ActorNumber; } private static string PlayerLabel(Player player) { return (player.NickName ?? "Player") + " (actor " + player.ActorNumber + ")"; } } internal sealed class PhotonCallbackRegistrationLifecycle { private bool registered; private bool disposed; internal bool IsRegistered => registered; internal bool IsDisposed => disposed; internal void Synchronize(bool roomActive, Action register, Action unregister) { if (disposed) { return; } if (roomActive) { if (!registered) { if (register == null) { throw new ArgumentNullException("register"); } register(); registered = true; } } else if (registered) { if (unregister == null) { throw new ArgumentNullException("unregister"); } unregister(); registered = false; } } internal void Dispose(Action unregister) { if (disposed) { return; } if (registered && unregister == null) { throw new ArgumentNullException("unregister"); } disposed = true; if (!registered) { return; } try { unregister(); } finally { registered = false; } } } } namespace RepoLiveControl.Commands { public sealed class CompletionCatalog { private static readonly CompletionCatalog EmptyCatalog = new CompletionCatalog(new string[0], new string[0]); public static CompletionCatalog Empty => EmptyCatalog; public IReadOnlyList Targets { get; private set; } public IReadOnlyList GrantPlayers { get; private set; } public IReadOnlyList RevokePlayers { get; private set; } public bool IncludeHostManagementCommands { get; private set; } public CompletionCatalog(IEnumerable targets, IEnumerable players) : this(targets, players, includeHostManagementCommands: true) { } public CompletionCatalog(IEnumerable targets, IEnumerable players, bool includeHostManagementCommands) : this(targets, players, players, includeHostManagementCommands) { } public CompletionCatalog(IEnumerable targets, IEnumerable grantPlayers, IEnumerable revokePlayers, bool includeHostManagementCommands) { Targets = CopyDistinct(targets); GrantPlayers = CopyDistinct(grantPlayers); RevokePlayers = CopyDistinct(revokePlayers); IncludeHostManagementCommands = includeHostManagementCommands; } private static IReadOnlyList CopyDistinct(IEnumerable values) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (values != null) { foreach (string value in values) { if (!string.IsNullOrWhiteSpace(value) && hashSet.Add(value)) { list.Add(value); } } } return list.AsReadOnly(); } } public sealed class CompletionItem { public string Value { get; private set; } public int Score { get; private set; } public int ArgumentIndex { get; private set; } public int ReplacementStart { get; private set; } public int ReplacementLength { get; private set; } internal CompletionItem(string value, int score, int argumentIndex, int replacementStart, int replacementLength) { Value = value; Score = score; ArgumentIndex = argumentIndex; ReplacementStart = replacementStart; ReplacementLength = replacementLength; } } public sealed class CompletionApplication { public string Text { get; private set; } public int CaretPosition { get; private set; } internal CompletionApplication(string text, int caretPosition) { Text = text; CaretPosition = caretPosition; } } public static class CommandCompletionEngine { private sealed class CompletionPosition { internal int ArgumentIndex { get; private set; } internal int ReplacementStart { get; private set; } internal int ReplacementLength { get; private set; } internal string Query { get; private set; } internal CompletionPosition(int argumentIndex, int replacementStart, int replacementLength, string query) { ArgumentIndex = argumentIndex; ReplacementStart = replacementStart; ReplacementLength = replacementLength; Query = query; } } private static readonly IReadOnlyList SpawnCounts = BuildSpawnCounts(); private static readonly IReadOnlyList DespawnCounts = BuildDespawnCounts(); private static readonly IReadOnlyList Locations = Array.AsReadOnly(new string[2] { "player-location", "random-non-collision-location" }); private static readonly IReadOnlyList SpawnCountOrLocations = BuildSpawnCountOrLocations(); public static IReadOnlyList GetCompletions(string input, int caretPosition, CompletionCatalog catalog, int maxResults) { input = input ?? string.Empty; catalog = catalog ?? CompletionCatalog.Empty; if (caretPosition < 0 || caretPosition > input.Length) { throw new ArgumentOutOfRangeException("caretPosition"); } if (maxResults <= 0) { return Array.AsReadOnly(new CompletionItem[0]); } CommandTokenization commandTokenization = CommandTokenizer.Tokenize(input); CompletionPosition completionPosition = FindCompletionPosition(input, caretPosition, commandTokenization.Tokens); IEnumerable candidates; SlashCommandKind kind; if (completionPosition.ArgumentIndex == 0) { candidates = GetCommandNames(catalog); } else if (!TryGetCommandKind(commandTokenization.Tokens, out kind)) { if (commandTokenization.Tokens.Count == 0) { return Array.AsReadOnly(new CompletionItem[0]); } completionPosition = new CompletionPosition(0, commandTokenization.Tokens[0].Start, commandTokenization.Tokens[0].Length, commandTokenization.Tokens[0].Value); candidates = GetCommandNames(catalog); } else { candidates = GetArgumentSource(kind, completionPosition.ArgumentIndex, commandTokenization.Tokens, catalog); } IReadOnlyList readOnlyList = FuzzyMatcher.Rank(completionPosition.Query, candidates, maxResults); List list = new List(readOnlyList.Count); foreach (FuzzyMatch item in readOnlyList) { list.Add(new CompletionItem(item.Value, item.Score, completionPosition.ArgumentIndex, completionPosition.ReplacementStart, completionPosition.ReplacementLength)); } return list.AsReadOnly(); } public static CompletionApplication ApplyCompletion(string input, CompletionItem completion) { return ApplyCompletion(input, completion, appendSpace: false); } public static CompletionApplication ApplyCompletion(string input, CompletionItem completion, bool appendSpace) { input = input ?? string.Empty; if (completion == null) { throw new ArgumentNullException("completion"); } if (completion.ReplacementStart < 0 || completion.ReplacementLength < 0 || completion.ReplacementStart + completion.ReplacementLength > input.Length) { throw new ArgumentException("The completion replacement span is outside the input."); } string text = CommandTokenizer.QuoteArgument(completion.Value); string text2 = input.Substring(0, completion.ReplacementStart) + text + input.Substring(completion.ReplacementStart + completion.ReplacementLength); int num = completion.ReplacementStart + text.Length; if (appendSpace) { if (num >= text2.Length || !char.IsWhiteSpace(text2[num])) { text2 = text2.Insert(num, " "); num++; } else { num++; } } return new CompletionApplication(text2, num); } private static CompletionPosition FindCompletionPosition(string input, int caretPosition, IReadOnlyList tokens) { for (int i = 0; i < tokens.Count; i++) { CommandToken commandToken = tokens[i]; if (caretPosition >= commandToken.Start && caretPosition <= commandToken.End) { return new CompletionPosition(i, commandToken.Start, commandToken.Length, commandToken.Value); } } int num = 0; foreach (CommandToken token in tokens) { if (token.End <= caretPosition) { num++; } } return new CompletionPosition(num, caretPosition, 0, string.Empty); } private static bool TryGetCommandKind(IReadOnlyList tokens, out SlashCommandKind kind) { kind = SlashCommandKind.Help; if (tokens.Count == 0) { return false; } switch (tokens[0].Value.ToLowerInvariant()) { case "/spawn": kind = SlashCommandKind.Spawn; return true; case "/despawn": kind = SlashCommandKind.Despawn; return true; case "/grant": kind = SlashCommandKind.Grant; return true; case "/revoke": kind = SlashCommandKind.Revoke; return true; case "/permissions": kind = SlashCommandKind.Permissions; return true; case "/help": kind = SlashCommandKind.Help; return true; default: return false; } } private static IEnumerable GetArgumentSource(SlashCommandKind kind, int argumentIndex, IReadOnlyList tokens, CompletionCatalog catalog) { switch (kind) { case SlashCommandKind.Spawn: switch (argumentIndex) { case 1: return GetSpawnTargets(catalog.Targets); case 2: return SpawnCountOrLocations; case 3: if (tokens.Count >= 3 && IsLocation(tokens[2].Value)) { return new string[0]; } return Locations; } break; case SlashCommandKind.Despawn: switch (argumentIndex) { case 1: return catalog.Targets; case 2: return DespawnCounts; } break; case SlashCommandKind.Grant: if (argumentIndex == 1 && catalog.IncludeHostManagementCommands) { return catalog.GrantPlayers; } break; case SlashCommandKind.Revoke: if (argumentIndex == 1 && catalog.IncludeHostManagementCommands) { return catalog.RevokePlayers; } break; } return new string[0]; } private static bool IsLocation(string value) { foreach (string location in Locations) { if (location.Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static IEnumerable GetSpawnTargets(IEnumerable targets) { foreach (string target in targets) { if (!target.EndsWith(":all", StringComparison.OrdinalIgnoreCase)) { yield return target; } } } private static IReadOnlyList BuildSpawnCounts() { List list = new List(); int[] array = new int[8] { 1, 5, 10, 25, 50, 100, 250, 500 }; foreach (int num in array) { list.Add(num.ToString(CultureInfo.InvariantCulture)); } for (int j = 1; j <= 500; j++) { string item = j.ToString(CultureInfo.InvariantCulture); if (!list.Contains(item)) { list.Add(item); } } return list.AsReadOnly(); } private static IReadOnlyList BuildDespawnCounts() { List list = new List { "all" }; foreach (string spawnCount in SpawnCounts) { list.Add(spawnCount); } return list.AsReadOnly(); } private static IEnumerable GetCommandNames(CompletionCatalog catalog) { foreach (string commandName in SlashCommandParser.CommandNames) { if (catalog.IncludeHostManagementCommands || (!commandName.Equals("/grant", StringComparison.OrdinalIgnoreCase) && !commandName.Equals("/revoke", StringComparison.OrdinalIgnoreCase))) { yield return commandName; } } } private static IReadOnlyList BuildSpawnCountOrLocations() { List list = new List(SpawnCounts); foreach (string location in Locations) { list.Add(location); } return list.AsReadOnly(); } } public static class CommandExecutionTranslation { public static string TranslateSpawn(CommandTargetKind targetKind, string targetName, int count, string location) { string text = ActionFor(targetKind); ValidateTargetName(targetName); ValidateCount(count); string text2; if (string.Equals(location, "player-location", StringComparison.OrdinalIgnoreCase)) { text2 = "at-player"; } else { if (!string.Equals(location, "random-non-collision-location", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Unknown spawn location.", "location"); } text2 = "safe"; } return text + "|" + targetName + "|" + count.ToString(CultureInfo.InvariantCulture) + "|" + text2; } public static string TranslateDespawn(CommandTargetKind targetKind, string targetName, int? count) { string text = KindNameFor(targetKind); ValidateTargetName(targetName); if (count.HasValue) { ValidateCount(count.Value); } return "despawnspawned|" + text + "|" + targetName + "|" + (count.HasValue ? count.Value.ToString(CultureInfo.InvariantCulture) : "-1"); } public static int AcceptedEnemyCountForSetup(int needed, int liveSpawned, bool collisionFreePlacement) { if (needed <= 0) { throw new ArgumentOutOfRangeException("needed"); } if (liveSpawned <= 0) { throw new ArgumentOutOfRangeException("liveSpawned"); } if (!collisionFreePlacement) { return Math.Min(needed, liveSpawned); } return 1; } private static string ActionFor(CommandTargetKind targetKind) { return targetKind switch { CommandTargetKind.Item => "item", CommandTargetKind.Valuable => "loot", CommandTargetKind.Enemy => "enemy", _ => throw new ArgumentOutOfRangeException("targetKind", "A canonical item, valuable, or enemy kind is required."), }; } private static string KindNameFor(CommandTargetKind targetKind) { return targetKind switch { CommandTargetKind.Item => "item", CommandTargetKind.Valuable => "valuable", CommandTargetKind.Enemy => "enemy", _ => throw new ArgumentOutOfRangeException("targetKind", "A canonical item, valuable, or enemy kind is required."), }; } private static void ValidateTargetName(string targetName) { if (string.IsNullOrWhiteSpace(targetName)) { throw new ArgumentException("A canonical target name is required.", "targetName"); } if (targetName.IndexOf('|') >= 0 || targetName.IndexOf('\r') >= 0 || targetName.IndexOf('\n') >= 0) { throw new ArgumentException("Target names cannot contain protocol delimiters.", "targetName"); } } private static void ValidateCount(int count) { if (count < 1 || count > 500) { throw new ArgumentOutOfRangeException("count", "Count must be from 1 through 500."); } } } public enum SlashCommandKind { Spawn, Despawn, Grant, Revoke, Permissions, Help } public enum CommandTargetKind { Unspecified, Item, Valuable, Enemy } public enum CommandParseErrorCode { None, EmptyInput, UnterminatedQuote, MissingSlash, UnknownCommand, MissingArgument, TooManyArguments, InvalidCount, CountOutOfRange, InvalidLocation } public static class CommandLocations { public const string PlayerLocation = "player-location"; public const string RandomNonCollisionLocation = "random-non-collision-location"; } public sealed class ParsedSlashCommand { public SlashCommandKind Kind { get; private set; } public string Target { get; private set; } public CommandTargetKind TargetKind { get; private set; } public string TargetName { get; private set; } public int? Count { get; private set; } public bool IsAllCount { get { if (Kind == SlashCommandKind.Despawn) { return !Count.HasValue; } return false; } } public string Location { get; private set; } public string Player { get; private set; } internal ParsedSlashCommand(SlashCommandKind kind, string target, int? count, string location, string player) { Kind = kind; Target = target; Count = count; Location = location; Player = player; SplitTarget(target, out var targetKind, out var targetName); TargetKind = targetKind; TargetName = targetName; } private static void SplitTarget(string target, out CommandTargetKind targetKind, out string targetName) { targetKind = CommandTargetKind.Unspecified; targetName = target; if (string.IsNullOrEmpty(target)) { return; } int num = target.IndexOf(':'); if (num <= 0) { return; } string text = target.Substring(0, num); if (text.Equals("item", StringComparison.OrdinalIgnoreCase)) { targetKind = CommandTargetKind.Item; } else if (text.Equals("valuable", StringComparison.OrdinalIgnoreCase)) { targetKind = CommandTargetKind.Valuable; } else { if (!text.Equals("enemy", StringComparison.OrdinalIgnoreCase)) { return; } targetKind = CommandTargetKind.Enemy; } targetName = target.Substring(num + 1); } } public sealed class CommandParseResult { public bool Success => Command != null; public ParsedSlashCommand Command { get; private set; } public CommandParseErrorCode ErrorCode { get; private set; } public string ErrorMessage { get; private set; } internal CommandParseResult(ParsedSlashCommand command, CommandParseErrorCode errorCode, string errorMessage) { Command = command; ErrorCode = errorCode; ErrorMessage = errorMessage; } } public sealed class CommandToken { public string Value { get; private set; } public int Start { get; private set; } public int Length { get; private set; } public int End => Start + Length; public bool IsQuoted { get; private set; } internal CommandToken(string value, int start, int length, bool isQuoted) { Value = value; Start = start; Length = length; IsQuoted = isQuoted; } } public sealed class CommandTokenization { public IReadOnlyList Tokens { get; private set; } public bool HasUnterminatedQuote { get; private set; } internal CommandTokenization(List tokens, bool hasUnterminatedQuote) { Tokens = tokens.AsReadOnly(); HasUnterminatedQuote = hasUnterminatedQuote; } } public static class CommandTokenizer { public static CommandTokenization Tokenize(string input) { input = input ?? string.Empty; List list = new List(); bool hasUnterminatedQuote = false; int i = 0; while (i < input.Length) { for (; i < input.Length && char.IsWhiteSpace(input[i]); i++) { } if (i >= input.Length) { break; } int num = i; char c = '\0'; bool isQuoted = false; StringBuilder stringBuilder = new StringBuilder(); while (i < input.Length) { char c2 = input[i]; if (c != 0) { if (c2 == c) { c = '\0'; i++; continue; } if (c2 == '\\' && i + 1 < input.Length) { char c3 = input[i + 1]; if (c3 == c || c3 == '\\') { stringBuilder.Append(c3); i += 2; continue; } } stringBuilder.Append(c2); i++; } else { if (char.IsWhiteSpace(c2)) { break; } if (c2 == '"' || c2 == '\'') { c = c2; isQuoted = true; i++; } else { stringBuilder.Append(c2); i++; } } } if (c != 0) { hasUnterminatedQuote = true; } list.Add(new CommandToken(stringBuilder.ToString(), num, i - num, isQuoted)); } return new CommandTokenization(list, hasUnterminatedQuote); } public static string QuoteArgument(string value) { value = value ?? string.Empty; bool flag = value.Length == 0; for (int i = 0; i < value.Length; i++) { if (flag) { break; } char c = value[i]; flag = char.IsWhiteSpace(c) || c == '"' || c == '\''; } if (!flag) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length + 2); stringBuilder.Append('"'); string text = value; foreach (char c2 in text) { if (c2 == '"' || c2 == '\\') { stringBuilder.Append('\\'); } stringBuilder.Append(c2); } stringBuilder.Append('"'); return stringBuilder.ToString(); } } public static class EnemyClearancePolicy { public const float MinimumProbeBottomOffset = 0.15f; public static readonly IReadOnlyList GameplaySolidLayerNames = Array.AsReadOnly(new string[7] { "Default", "StaticGrabObject", "Enemy", "Player", "PhysGrabObject", "PhysGrabObjectCart", "PhysGrabObjectHinge" }); public static float ClampProbeBottomOffset(float bottomOffset) { if (float.IsNaN(bottomOffset) || float.IsInfinity(bottomOffset)) { throw new ArgumentOutOfRangeException("bottomOffset"); } return Math.Max(bottomOffset, 0.15f); } public static int BuildGameplaySolidMask(Func layerLookup) { if (layerLookup == null) { throw new ArgumentNullException("layerLookup"); } int num = 0; foreach (string gameplaySolidLayerName in GameplaySolidLayerNames) { int num2 = layerLookup(gameplaySolidLayerName); if (num2 >= 0 && num2 < 32) { num |= 1 << num2; } } return num; } public static bool IsBodyGeometryEligible(bool componentEnabled, bool isTrigger, bool activeInPrefabHierarchy, bool attachedToRigidbody) { if (componentEnabled && !isTrigger) { return activeInPrefabHierarchy || attachedToRigidbody; } return false; } public static bool IsNavigationEnvelopeUsable(float radius, float height, float baseOffset, float horizontalScale, float verticalScale) { if (IsFinite(radius) && radius > 0f && IsFinite(height) && height > 0f && IsFinite(baseOffset) && IsFinite(horizontalScale) && horizontalScale > 0f && IsFinite(verticalScale)) { return verticalScale > 0f; } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public sealed class FuzzyMatch { public string Value { get; private set; } public int Score { get; private set; } internal int OriginalIndex { get; private set; } internal FuzzyMatch(string value, int score, int originalIndex) { Value = value; Score = score; OriginalIndex = originalIndex; } } public static class FuzzyMatcher { private sealed class SearchVariant { internal string Text { get; private set; } internal int Penalty { get; private set; } internal SearchVariant(string text, int penalty) { Text = text; Penalty = penalty; } } public const int NoMatch = int.MinValue; public static int Score(string query, string candidate) { query = Normalize(query); candidate = Normalize(candidate); if (candidate.Length == 0) { return int.MinValue; } if (query.Length == 0) { return 1; } List list = BuildVariants(query, splitSegments: false); List list2 = BuildVariants(candidate, splitSegments: true); int num = int.MinValue; foreach (SearchVariant item in list) { foreach (SearchVariant item2 in list2) { int num2 = ScoreVariant(item.Text, item2.Text); if (num2 != int.MinValue) { num2 -= item.Penalty + item2.Penalty; if (num2 > num) { num = num2; } } } } return num; } public static IReadOnlyList Rank(string query, IEnumerable candidates, int maxResults) { if (candidates == null) { throw new ArgumentNullException("candidates"); } if (maxResults <= 0) { return Array.AsReadOnly(new FuzzyMatch[0]); } List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (string candidate in candidates) { if (!string.IsNullOrWhiteSpace(candidate) && hashSet.Add(candidate)) { int num2 = Score(query, candidate); if (num2 != int.MinValue) { list.Add(new FuzzyMatch(candidate, num2, num)); } } num++; } list.Sort(CompareMatches); if (list.Count > maxResults) { list.RemoveRange(maxResults, list.Count - maxResults); } return list.AsReadOnly(); } private static int CompareMatches(FuzzyMatch left, FuzzyMatch right) { int num = right.Score.CompareTo(left.Score); if (num != 0) { return num; } return left.OriginalIndex.CompareTo(right.OriginalIndex); } private static int ScoreVariant(string query, string candidate) { if (query.Equals(candidate, StringComparison.Ordinal)) { return 100000; } if (candidate.StartsWith(query, StringComparison.Ordinal)) { return 90000 - (candidate.Length - query.Length) * 5; } int num = candidate.IndexOf(query, StringComparison.Ordinal); if (num >= 0) { return 80000 - num * 50 - (candidate.Length - query.Length) * 3; } int num2 = ScoreSubsequence(query, candidate); if (num2 != int.MinValue) { return num2; } if (query.Length < 3) { return int.MinValue; } int num3 = ((query.Length <= 4) ? 1 : ((query.Length <= 8) ? 2 : 3)); if (Math.Abs(query.Length - candidate.Length) > num3) { return int.MinValue; } int num4 = DamerauLevenshteinDistance(query, candidate); if (num4 > num3) { return int.MinValue; } return 60000 - num4 * 1000 - Math.Abs(candidate.Length - query.Length) * 20; } private static int ScoreSubsequence(string query, string candidate) { if (query.Length > candidate.Length) { return int.MinValue; } int num = 0; int num2 = -1; int num3 = -1; int num4 = 0; for (int i = 0; i < candidate.Length; i++) { if (num >= query.Length) { break; } if (candidate[i] == query[num]) { if (num2 < 0) { num2 = i; } if (num3 >= 0) { num4 += i - num3 - 1; } num3 = i; num++; } } if (num != query.Length) { return int.MinValue; } return 70000 - num2 * 30 - num4 * 40 - (candidate.Length - query.Length) * 2; } private static int DamerauLevenshteinDistance(string left, string right) { int[,] array = new int[left.Length + 1, right.Length + 1]; for (int i = 0; i <= left.Length; i++) { array[i, 0] = i; } for (int j = 0; j <= right.Length; j++) { array[0, j] = j; } for (int k = 1; k <= left.Length; k++) { for (int l = 1; l <= right.Length; l++) { int num = ((left[k - 1] != right[l - 1]) ? 1 : 0); int val = array[k - 1, l] + 1; int val2 = array[k, l - 1] + 1; int num2 = Math.Min(val2: array[k - 1, l - 1] + num, val1: Math.Min(val, val2)); if (k > 1 && l > 1 && left[k - 1] == right[l - 2] && left[k - 2] == right[l - 1]) { num2 = Math.Min(num2, array[k - 2, l - 2] + 1); } array[k, l] = num2; } } return array[left.Length, right.Length]; } private static List BuildVariants(string value, bool splitSegments) { List list = new List(); AddVariant(list, value, 0); if (value.Length > 0 && value[0] == '/') { AddVariant(list, value.Substring(1), 10); } if (splitSegments) { int num = value.IndexOf(':'); if (num >= 0 && num + 1 < value.Length) { AddVariant(list, value.Substring(num + 1), 20); } string[] array = value.Split(new char[7] { ':', '/', '-', '_', '.', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); foreach (string value2 in array) { AddVariant(list, value2, 40); } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(c); } } AddVariant(list, stringBuilder.ToString(), 60); } return list; } private static void AddVariant(List variants, string value, int penalty) { if (string.IsNullOrEmpty(value)) { return; } foreach (SearchVariant variant in variants) { if (variant.Text.Equals(value, StringComparison.Ordinal)) { return; } } variants.Add(new SearchVariant(value, penalty)); } private static string Normalize(string value) { return (value ?? string.Empty).Trim().ToLowerInvariant(); } } public static class SlashCommandParser { public const int MinimumCount = 1; public const int MaximumCount = 500; private static readonly IReadOnlyList KnownCommandNames = Array.AsReadOnly(new string[6] { "/spawn", "/despawn", "/grant", "/revoke", "/permissions", "/help" }); public static IReadOnlyList CommandNames => KnownCommandNames; public static CommandParseResult Parse(string input) { CommandTokenization commandTokenization = CommandTokenizer.Tokenize(input); if (commandTokenization.Tokens.Count == 0) { return Failure(CommandParseErrorCode.EmptyInput, "Enter a slash command."); } if (commandTokenization.HasUnterminatedQuote) { return Failure(CommandParseErrorCode.UnterminatedQuote, "Close the quoted argument before running the command."); } string value = commandTokenization.Tokens[0].Value; if (!value.StartsWith("/", StringComparison.Ordinal)) { return Failure(CommandParseErrorCode.MissingSlash, "Commands must begin with '/'."); } return value.ToLowerInvariant() switch { "/spawn" => ParseSpawn(commandTokenization.Tokens), "/despawn" => ParseDespawn(commandTokenization.Tokens), "/grant" => ParsePlayerCommand(commandTokenization.Tokens, SlashCommandKind.Grant), "/revoke" => ParsePlayerCommand(commandTokenization.Tokens, SlashCommandKind.Revoke), "/permissions" => ParseNoArgumentCommand(commandTokenization.Tokens, SlashCommandKind.Permissions), "/help" => ParseNoArgumentCommand(commandTokenization.Tokens, SlashCommandKind.Help), _ => Failure(CommandParseErrorCode.UnknownCommand, "Unknown command '" + value + "'."), }; } private static CommandParseResult ParseSpawn(IReadOnlyList tokens) { if (tokens.Count < 2 || string.IsNullOrWhiteSpace(tokens[1].Value)) { return Missing("Spawn requires a target."); } if (tokens.Count > 4) { return TooMany("Spawn accepts a target, optional count, and optional location."); } int count = 1; string location = "player-location"; if (tokens.Count >= 3) { string value = tokens[2].Value; if (TryNormalizeLocation(value, out var location2)) { location = location2; if (tokens.Count >= 4) { return TooMany("When count is omitted, spawn location must be the final argument."); } } else { CommandParseResult commandParseResult = TryParseCount(value, allowAll: false, out count); if (commandParseResult != null) { return commandParseResult; } } } if (tokens.Count >= 4 && !TryNormalizeLocation(tokens[3].Value, out location)) { return Failure(CommandParseErrorCode.InvalidLocation, "Spawn location must be 'player-location' or 'random-non-collision-location'."); } return Success(new ParsedSlashCommand(SlashCommandKind.Spawn, tokens[1].Value, count, location, null)); } private static bool TryNormalizeLocation(string value, out string location) { if (value.Equals("player-location", StringComparison.OrdinalIgnoreCase)) { location = "player-location"; return true; } if (value.Equals("random-non-collision-location", StringComparison.OrdinalIgnoreCase)) { location = "random-non-collision-location"; return true; } location = null; return false; } private static CommandParseResult ParseDespawn(IReadOnlyList tokens) { if (tokens.Count < 2 || string.IsNullOrWhiteSpace(tokens[1].Value)) { return Missing("Despawn requires a target."); } if (tokens.Count > 3) { return TooMany("Despawn accepts a target and optional count."); } int? count = null; if (tokens.Count >= 3) { int count2; CommandParseResult commandParseResult = TryParseCount(tokens[2].Value, allowAll: true, out count2); if (commandParseResult != null) { return commandParseResult; } if (!tokens[2].Value.Equals("all", StringComparison.OrdinalIgnoreCase)) { count = count2; } } return Success(new ParsedSlashCommand(SlashCommandKind.Despawn, tokens[1].Value, count, null, null)); } private static CommandParseResult ParsePlayerCommand(IReadOnlyList tokens, SlashCommandKind kind) { if (tokens.Count < 2 || string.IsNullOrWhiteSpace(tokens[1].Value)) { return Missing(kind.ToString() + " requires a player."); } if (tokens.Count > 2) { return TooMany(kind.ToString() + " accepts exactly one player."); } return Success(new ParsedSlashCommand(kind, null, null, null, tokens[1].Value)); } private static CommandParseResult ParseNoArgumentCommand(IReadOnlyList tokens, SlashCommandKind kind) { if (tokens.Count > 1) { return TooMany(kind.ToString() + " does not accept arguments."); } return Success(new ParsedSlashCommand(kind, null, null, null, null)); } private static CommandParseResult TryParseCount(string value, bool allowAll, out int count) { count = 0; if (allowAll && value.Equals("all", StringComparison.OrdinalIgnoreCase)) { return null; } if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out count)) { return Failure(CommandParseErrorCode.InvalidCount, allowAll ? "Count must be 'all' or a whole number from 1 through 500." : "Count must be a whole number from 1 through 500."); } if (count < 1 || count > 500) { return Failure(CommandParseErrorCode.CountOutOfRange, "Count must be from 1 through 500."); } return null; } private static CommandParseResult Success(ParsedSlashCommand command) { return new CommandParseResult(command, CommandParseErrorCode.None, null); } private static CommandParseResult Failure(CommandParseErrorCode errorCode, string errorMessage) { return new CommandParseResult(null, errorCode, errorMessage); } private static CommandParseResult Missing(string message) { return Failure(CommandParseErrorCode.MissingArgument, message); } private static CommandParseResult TooMany(string message) { return Failure(CommandParseErrorCode.TooManyArguments, message); } } public sealed class SpawnNameSummary { public const int MaximumDisplayedNames = 8; private readonly Dictionary counts = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly List orderedNames = new List(); public void Add(string name, int count) { if (!string.IsNullOrWhiteSpace(name) && count > 0) { if (counts.TryGetValue(name, out var value)) { counts[name] = value + count; return; } counts.Add(name, count); orderedNames.Add(name); } } public string Format() { List list = new List(); int num = Math.Min(orderedNames.Count, 8); for (int i = 0; i < num; i++) { string text = orderedNames[i]; int num2 = counts[text]; list.Add((num2 == 1) ? text : (text + " x" + num2)); } int num3 = orderedNames.Count - num; if (num3 > 0) { list.Add("+" + num3 + " more name(s)"); } return string.Join(", ", list.ToArray()); } } }