From f89fdd1d2daa090cada1f746ed93746d409589b1 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 11 Apr 2026 15:31:47 -0500 Subject: [PATCH] Clean up and allow player ID and Friend code in player argument --- src/Commands/Arguments/BaseArgument.cs | 12 ++-- src/Commands/Arguments/BoolArgument.cs | 30 ++++------ src/Commands/Arguments/PlayerArgument.cs | 75 +++++++++++++++++------- src/Commands/Arguments/StringArgument.cs | 20 ++++++- src/Commands/KickCommand.cs | 9 ++- src/Commands/PlayerInfoCommand.cs | 3 +- src/Commands/RemovePlayerCommand.cs | 2 +- 7 files changed, 95 insertions(+), 56 deletions(-) diff --git a/src/Commands/Arguments/BaseArgument.cs b/src/Commands/Arguments/BaseArgument.cs index 67ae8ad..94f5b46 100644 --- a/src/Commands/Arguments/BaseArgument.cs +++ b/src/Commands/Arguments/BaseArgument.cs @@ -25,12 +25,10 @@ internal abstract class BaseArgument(BaseCommand command, string argInfo) /// /// Gets the argument suggestions for auto-completion. /// - protected virtual string[] ArgSuggestions => GetArgSuggestions.Invoke(); - - /// - /// Gets or sets the function that provides argument suggestions. - /// - internal Func GetArgSuggestions { get; set; } = () => { return []; }; + protected virtual string[] GetArgSuggestions() + { + return []; + } /// /// Resets the Argument back to its default state. @@ -45,5 +43,5 @@ internal abstract class BaseArgument(BaseCommand command, string argInfo) /// /// The closest matching suggestion, or empty string if none found. internal string GetClosestSuggestion() => - ArgSuggestions.FirstOrDefault(name => name.StartsWith(Arg, StringComparison.OrdinalIgnoreCase)) ?? string.Empty; + GetArgSuggestions().FirstOrDefault(name => name.StartsWith(Arg, StringComparison.OrdinalIgnoreCase)) ?? string.Empty; } \ No newline at end of file diff --git a/src/Commands/Arguments/BoolArgument.cs b/src/Commands/Arguments/BoolArgument.cs index 7ebc9cb..bbd7137 100644 --- a/src/Commands/Arguments/BoolArgument.cs +++ b/src/Commands/Arguments/BoolArgument.cs @@ -7,37 +7,33 @@ /// Information about the argument (default: "{bool}"). internal sealed class BoolArgument(BaseCommand command, string argInfo = "{bool}") : BaseArgument(command, argInfo) { - /// - /// Gets the boolean argument suggestions for auto-completion. - /// - protected override string[] ArgSuggestions => ["true", "false"]; + protected override string[] GetArgSuggestions() + { + return ["true", "false"]; + } /// - /// Parses the argument string to a nullable boolean value. + /// Tries to parse the argument as a boolean value. /// - /// - /// True if the argument is "true", - /// False if the argument is "false" or empty, - /// Null if the argument is invalid. - /// - /// - /// Displays an error message if the argument is invalid. - /// - internal bool? GetBool() + /// + /// + internal bool TryParseBool(out bool result) { if (Arg.ToLower() == "true") { + result = true; return true; } else if (Arg.ToLower() is "false" or "") { - return false; + result = false; + return true; } else { BaseCommand.CommandErrorText($"Invalid Syntax!"); + result = false; + return false; } - - return null; } } \ No newline at end of file diff --git a/src/Commands/Arguments/PlayerArgument.cs b/src/Commands/Arguments/PlayerArgument.cs index cc674b9..2ab8e8b 100644 --- a/src/Commands/Arguments/PlayerArgument.cs +++ b/src/Commands/Arguments/PlayerArgument.cs @@ -9,31 +9,62 @@ namespace BetterAmongUs.Commands.Arguments; /// Information about the argument (default: "{player}"). internal sealed class PlayerArgument(BaseCommand command, string argInfo = "{player}") : BaseArgument(command, argInfo) { - /// - /// Gets the player argument suggestions for auto-completion. - /// - /// - /// Suggestions are ordered with the local player first, then other players. - /// Spaces in player names are replaced with underscores for command parsing. - /// - protected override string[] ArgSuggestions => BAUPlugin.AllPlayerControls.OrderBy(pc => pc.IsLocalPlayer() ? 0 : 1).Select(pc => pc.Data.PlayerName.Replace(' ', '_')).ToArray(); - - /// - /// Attempts to get a PlayerControl instance based on the argument value. - /// - /// - /// The PlayerControl if found; otherwise, null. - /// Displays an error message if the player is not found. - /// - internal PlayerControl? TryGetTarget() + protected override string[] GetArgSuggestions() { - var player = BAUPlugin.AllPlayerControls.FirstOrDefault(pc => pc.Data.PlayerName.ToLower().Replace(' ', '_') == Arg.ToLower()); - - if (player == null) + List suggestions = []; + foreach (var player in BAUPlugin.AllPlayerControls.OrderBy(pc => pc.IsLocalPlayer() ? 0 : 1)) { - BaseCommand.CommandErrorText($"Player not found!"); + if (player.Data == null) + continue; + + if (!string.IsNullOrEmpty(player.Data.PlayerName)) + { + suggestions.Add(player.Data.PlayerName.Replace(' ', '_')); + } + + if (!string.IsNullOrEmpty(player.Data.FriendCode)) + { + suggestions.Add(player.Data.FriendCode); + } + + suggestions.Add($"ID{player.Data.PlayerId}"); } - return player; + return [.. suggestions]; + } + + /// + /// Tries to parse the player argument and find the corresponding PlayerControl. + /// + /// + /// + internal bool TryParsePlayer(out PlayerControl? result) + { + foreach (var player in BAUPlugin.AllPlayerControls) + { + if (player.Data == null) continue; + + if (IsMatchingPlayer(player.Data)) + { + result = player; + return true; + } + } + + result = null; + BaseCommand.CommandErrorText("Player not found!"); + return false; + } + + /// + /// Checks if the given player info matches the argument value. + /// + /// + /// + private bool IsMatchingPlayer(NetworkedPlayerInfo data) + { + return Arg.Equals(data.PlayerName, StringComparison.OrdinalIgnoreCase) + || Arg.Equals(data.FriendCode, StringComparison.OrdinalIgnoreCase) + || Arg.Equals($"ID{data.PlayerId}", StringComparison.OrdinalIgnoreCase); } } \ No newline at end of file diff --git a/src/Commands/Arguments/StringArgument.cs b/src/Commands/Arguments/StringArgument.cs index 2b8000f..153a564 100644 --- a/src/Commands/Arguments/StringArgument.cs +++ b/src/Commands/Arguments/StringArgument.cs @@ -7,6 +7,22 @@ /// Information about the argument (default: "{String}"). internal sealed class StringArgument(BaseCommand command, string argInfo = "{String}") : BaseArgument(command, argInfo) { - // This class inherits the basic string argument functionality from BaseArgument - // and can be extended with string-specific parsing or validation if needed. + protected override string[] GetArgSuggestions() + { + return ArgSuggestions.Invoke(); + } + + /// + /// Gets or sets the function that provides argument suggestions. + /// + internal Func ArgSuggestions { get; set; } = () => { return []; }; + + /// + /// Gets the string argument. + /// + /// + internal string GetString() + { + return Arg; + } } \ No newline at end of file diff --git a/src/Commands/KickCommand.cs b/src/Commands/KickCommand.cs index b0877a3..6f6674e 100644 --- a/src/Commands/KickCommand.cs +++ b/src/Commands/KickCommand.cs @@ -31,17 +31,16 @@ internal sealed class KickCommand : BaseCommand internal override void Run() { - var player = _playerArgument.TryGetTarget(); - if (player == null) + if (!_playerArgument.TryParsePlayer(out var player)) return; - var isBan = _boolArgument.GetBool(); - if (isBan == null) + var isBan = _boolArgument.TryParseBool(out var ban); + if (!isBan) return; if (!player.IsHost()) { - player.Kick((bool)isBan); + player.Kick(ban); } } } diff --git a/src/Commands/PlayerInfoCommand.cs b/src/Commands/PlayerInfoCommand.cs index 9c09d47..e0511cd 100644 --- a/src/Commands/PlayerInfoCommand.cs +++ b/src/Commands/PlayerInfoCommand.cs @@ -20,8 +20,7 @@ internal sealed class PlayerInfoCommand : BaseCommand internal override void Run() { - var player = _playerArgument.TryGetTarget(); - if (player == null) + if (!_playerArgument.TryParsePlayer(out var player)) return; if (player.Data == null) diff --git a/src/Commands/RemovePlayerCommand.cs b/src/Commands/RemovePlayerCommand.cs index 100bda1..2a53d3a 100644 --- a/src/Commands/RemovePlayerCommand.cs +++ b/src/Commands/RemovePlayerCommand.cs @@ -15,7 +15,7 @@ internal sealed class RemovePlayerCommand : BaseCommand { _identifierArgument = new StringArgument(this, "{identifier}") { - GetArgSuggestions = () => + ArgSuggestions = () => BetterDataManager.Files.BetterDataFile.AllCheatData .SelectMany(info => new[] { info.HashPuid.Replace(' ', '_'), info.FriendCode.Replace(' ', '_'), info.PlayerName.Replace(' ', '_') }) .ToArray()