diff --git a/src/Commands/BaseCommand.cs b/src/Commands/BaseCommand.cs index d56bd16..85cfbbd 100644 --- a/src/Commands/BaseCommand.cs +++ b/src/Commands/BaseCommand.cs @@ -57,10 +57,15 @@ internal abstract class BaseCommand internal virtual bool ShowCommand() => true; /// - /// Determines whether this command should be shown in suggestions. + /// Determines whether this command can be executed based on current conditions. /// - /// True if the command should be shown in suggestions; otherwise, false. - internal virtual bool ShowSuggestion() => ShowCommand(); + /// When this method returns, contains the reason why the command cannot be run if it returns false; otherwise, an empty string. + /// True if the command can be executed; otherwise, false. + internal virtual bool CanRunCommand(out string reason) + { + reason = string.Empty; + return true; + } /// /// Executes the command. diff --git a/src/Commands/DumpCommand.cs b/src/Commands/DumpCommand.cs index 0bc3175..9d3653c 100644 --- a/src/Commands/DumpCommand.cs +++ b/src/Commands/DumpCommand.cs @@ -1,5 +1,5 @@ -using BetterAmongUs.Data; -using BetterAmongUs.Attributes; +using BetterAmongUs.Attributes; +using BetterAmongUs.Data; using BetterAmongUs.Modules; namespace BetterAmongUs.Commands; @@ -9,7 +9,17 @@ internal sealed class DumpCommand : BaseCommand { internal override string Name => "dump"; internal override string Description => "Dump the entire log to the user's desktop"; - internal override bool ShowCommand() => !GameState.IsInGamePlay; + + internal override bool CanRunCommand(out string reason) + { + if (GameState.IsInGamePlay) + { + reason = "Only can run in lobby"; + return false; + } + + return base.CanRunCommand(out reason); + } internal override void Run() { diff --git a/src/Commands/ForceSkipCommand.cs b/src/Commands/ForceSkipCommand.cs index 6e4e664..0b2c13f 100644 --- a/src/Commands/ForceSkipCommand.cs +++ b/src/Commands/ForceSkipCommand.cs @@ -8,7 +8,23 @@ internal sealed class ForceSkipCommand : BaseCommand { internal override string Name => "forceskip"; internal override string Description => "Force skips a meeting in progress"; - internal override bool ShowCommand() => GameState.IsHost && GameState.IsMeeting; + internal override bool CanRunCommand(out string reason) + { + if (!GameState.IsHost) + { + reason = "Can only run as host"; + return false; + } + + if (!GameState.IsMeeting) + { + reason = "Can only run in meeting"; + return false; + } + + return base.CanRunCommand(out reason); + } + internal override void Run() { if (GameState.IsHost) diff --git a/src/Commands/KickCommand.cs b/src/Commands/KickCommand.cs index bb0eef9..f553f94 100644 --- a/src/Commands/KickCommand.cs +++ b/src/Commands/KickCommand.cs @@ -1,6 +1,6 @@ -using BetterAmongUs.Commands.Arguments; +using BetterAmongUs.Attributes; +using BetterAmongUs.Commands.Arguments; using BetterAmongUs.Helpers; -using BetterAmongUs.Attributes; using BetterAmongUs.Modules; namespace BetterAmongUs.Commands; @@ -10,6 +10,16 @@ internal sealed class KickCommand : BaseCommand { internal override string Name => "kick"; internal override string Description => "Kick a player from the game"; + internal override bool CanRunCommand(out string reason) + { + if (!GameState.IsHost) + { + reason = "Can only run as host"; + return false; + } + + return base.CanRunCommand(out reason); + } public KickCommand() { playerArgument = new PlayerArgument(this); @@ -19,7 +29,6 @@ internal sealed class KickCommand : BaseCommand private PlayerArgument playerArgument { get; } private BoolArgument boolArgument { get; } - internal override bool ShowCommand() => GameState.IsHost; internal override void Run() { var player = playerArgument.TryGetTarget(); diff --git a/src/Patches/Gameplay/UI/Chat/ChatCommandsPatch.cs b/src/Patches/Gameplay/UI/Chat/ChatCommandsPatch.cs index d6b987f..75300fc 100644 --- a/src/Patches/Gameplay/UI/Chat/ChatCommandsPatch.cs +++ b/src/Patches/Gameplay/UI/Chat/ChatCommandsPatch.cs @@ -52,6 +52,11 @@ internal static class ChatCommandsPatch return true; } + if (!closestCommand.CanRunCommand(out string _)) + { + return false; + } + HandleCommand(); if (ChatPatch.ChatHistory.Count == 0 || ChatPatch.ChatHistory[^1] != text) ChatPatch.ChatHistory.Add(text); @@ -130,8 +135,7 @@ internal static class ChatCommandsPatch closestCommand = GetClosestCommand(typedParts[0]); bool isSuggestionValid = closestCommand != null - && (typedParts[0].Equals(closestCommand.Name, StringComparison.OrdinalIgnoreCase) || typedParts.Length == 1) - && closestCommand.ShowSuggestion(); + && (typedParts[0].Equals(closestCommand.Name, StringComparison.OrdinalIgnoreCase) || typedParts.Length == 1); if (isSuggestionValid) { @@ -167,8 +171,13 @@ internal static class ChatCommandsPatch __instance.freeChatField.textArea.SetText(fullSuggestion); } + if (!closestCommand.CanRunCommand(out string _)) + { + fullSuggestion = fullSuggestion.ToColor("#FF0300".HexToColor()); + } + commandText.text = fullSuggestion; - commandInfo.text = $"{closestCommand.Description}{GenerateArgumentInfo()}"; + commandInfo.text = $"{closestCommand.Description}{GenerateArgumentInfo()}{GenerateCanRunInfo()}"; } private static string GenerateSuggestion(string[] typedParts) @@ -207,6 +216,15 @@ internal static class ChatCommandsPatch return $" - <#a6a6a6>{argumentInfo}"; } + private static string GenerateCanRunInfo() + { + if (!closestCommand.CanRunCommand(out string reason)) + { + return $" - <#FF0300>{reason}"; + } + + return string.Empty; + } internal static BaseCommand? GetClosestCommand(string typedCommand) {