diff --git a/src/TrainerMod/Framework/Commands/ArgumentParser.cs b/src/TrainerMod/Framework/Commands/ArgumentParser.cs new file mode 100644 index 00000000..bce068f1 --- /dev/null +++ b/src/TrainerMod/Framework/Commands/ArgumentParser.cs @@ -0,0 +1,158 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI; + +namespace TrainerMod.Framework.Commands +{ + /// Provides methods for parsing command-line arguments. + internal class ArgumentParser : IReadOnlyList + { + /********* + ** Properties + *********/ + /// The command name for errors. + private readonly string CommandName; + + /// The arguments to parse. + private readonly string[] Args; + + /// Writes messages to the console and log file. + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// Get the number of arguments. + public int Count => this.Args.Length; + + /// Get the argument at the specified index in the list. + /// The zero-based index of the element to get. + public string this[int index] => this.Args[index]; + + /// A method which parses a string argument into the given value. + /// The expected argument type. + /// The argument to parse. + /// The parsed value. + /// Returns whether the argument was successfully parsed. + public delegate bool ParseDelegate(string input, out T output); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The command name for errors. + /// The arguments to parse. + /// Writes messages to the console and log file. + public ArgumentParser(string commandName, string[] args, IMonitor monitor) + { + this.CommandName = commandName; + this.Args = args; + this.Monitor = monitor; + } + + /// Try to read a string argument. + /// The argument index. + /// The argument name for error messages. + /// The parsed value. + /// Whether to show an error if the argument is missing. + /// Require that the argument match one of the given values. + public bool TryGet(int index, string name, out string value, bool required = true, string[] oneOf = null) + { + value = null; + + // validate + if (this.Args.Length < index + 1) + { + if (required) + this.LogError($"Argument {index} ({name}) is required."); + return false; + } + if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index])) + { + this.LogError($"Argument {index} ({name}) must be one of {string.Join(", ", oneOf)}."); + return false; + } + + // get value + value = this.Args[index]; + return true; + } + + /// Try to read an integer argument. + /// The argument index. + /// The argument name for error messages. + /// The parsed value. + /// Whether to show an error if the argument is missing. + /// The minimum value allowed. + /// The maximum value allowed. + public bool TryGetInt(int index, string name, out int value, bool required = true, int? min = null, int? max = null) + { + value = 0; + + // get argument + if (!this.TryGet(index, name, out string raw, required)) + return false; + + // parse + if (!int.TryParse(raw, out value)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + // validate + if ((min.HasValue && value < min) || (max.HasValue && value > max)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + return true; + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return ((IEnumerable)this.Args).GetEnumerator(); + } + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + + /********* + ** Private methods + *********/ + /// Log a usage error. + /// The message describing the error. + private void LogError(string message) + { + this.Monitor.Log($"{message} Type 'help {this.CommandName}' for usage.", LogLevel.Error); + } + + /// Print an error for an invalid int argument. + /// The argument index. + /// The argument name for error messages. + /// The minimum value allowed. + /// The maximum value allowed. + private void LogIntFormatError(int index, string name, int? min, int? max) + { + if (min.HasValue && max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer between {min} and {max}."); + else if (min.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at least {min}."); + else if (max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at most {max}."); + else + this.LogError($"Argument {index} ({name}) must be an integer."); + } + } +} diff --git a/src/TrainerMod/Framework/Commands/ITrainerCommand.cs b/src/TrainerMod/Framework/Commands/ITrainerCommand.cs index 55f36ceb..3d97e799 100644 --- a/src/TrainerMod/Framework/Commands/ITrainerCommand.cs +++ b/src/TrainerMod/Framework/Commands/ITrainerCommand.cs @@ -25,7 +25,7 @@ namespace TrainerMod.Framework.Commands /// Writes messages to the console and log file. /// The command name. /// The command arguments. - void Handle(IMonitor monitor, string command, string[] args); + void Handle(IMonitor monitor, string command, ArgumentParser args); /// Perform any logic needed on update tick. /// Writes messages to the console and log file. diff --git a/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs b/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs index ad38d1ba..8c6e9f3b 100644 --- a/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs +++ b/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs @@ -17,7 +17,7 @@ namespace TrainerMod.Framework.Commands.Other /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // submit command string debugCommand = string.Join(" ", args); diff --git a/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs b/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs index b2985bb1..367a70c6 100644 --- a/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs +++ b/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs @@ -17,7 +17,7 @@ namespace TrainerMod.Framework.Commands.Other /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { Process.Start(Constants.DataPath); monitor.Log($"OK, opening {Constants.DataPath}.", LogLevel.Info); diff --git a/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs b/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs index 5695ce9a..67fa83a3 100644 --- a/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs +++ b/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs @@ -17,7 +17,7 @@ namespace TrainerMod.Framework.Commands.Other /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { Process.Start(Constants.ExecutionPath); monitor.Log($"OK, opening {Constants.ExecutionPath}.", LogLevel.Info); diff --git a/src/TrainerMod/Framework/Commands/Player/AddFlooringCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddFlooringCommand.cs index 57bd39e3..1bc96466 100644 --- a/src/TrainerMod/Framework/Commands/Player/AddFlooringCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/AddFlooringCommand.cs @@ -1,5 +1,4 @@ -using System.Linq; -using StardewModdingAPI; +using StardewModdingAPI; using StardewValley; using StardewValley.Objects; @@ -19,24 +18,11 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // read arguments + if (!args.TryGetInt(0, "floor ID", out int floorID, min: 0, max: 39)) return; - } - if (!int.TryParse(args[0], out int floorID)) - { - this.LogArgumentNotInt(monitor, command); - return; - } - if (floorID < 0 || floorID > 39) - { - monitor.Log("There is no such flooring ID (must be between 0 and 39).", LogLevel.Error); - return; - } // handle Wallpaper wallpaper = new Wallpaper(floorID, isFloor: true); diff --git a/src/TrainerMod/Framework/Commands/Player/AddItemCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddItemCommand.cs index 6d3cf968..190d040a 100644 --- a/src/TrainerMod/Framework/Commands/Player/AddItemCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/AddItemCommand.cs @@ -1,5 +1,4 @@ -using System.Linq; -using StardewModdingAPI; +using StardewModdingAPI; using StardewValley; namespace TrainerMod.Framework.Commands.Player @@ -18,39 +17,15 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // read arguments + if (!args.TryGetInt(0, "item ID", out int itemID, min: 0)) return; - } - if (!int.TryParse(args[0], out int itemID)) - { - this.LogUsageError(monitor, "The item ID must be an integer.", command); - return; - } - - // parse arguments - int count = 1; - int quality = 0; - if (args.Length > 1) - { - if (!int.TryParse(args[1], out count)) - { - this.LogUsageError(monitor, "The optional count is invalid.", command); - return; - } - } - if (args.Length > 2) - { - if (!int.TryParse(args[2], out quality)) - { - this.LogUsageError(monitor, "The optional quality is invalid.", command); - return; - } - } + if (!args.TryGetInt(1, "count", out int count, min: 1, required: false)) + count = 1; + if (!args.TryGetInt(2, "quality", out int quality, min: Object.lowQuality, max: Object.bestQuality, required: false)) + quality = Object.lowQuality; // spawn item var item = new Object(itemID, count) { quality = quality }; diff --git a/src/TrainerMod/Framework/Commands/Player/AddRingCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddRingCommand.cs index d62d8b5b..93c5b2a5 100644 --- a/src/TrainerMod/Framework/Commands/Player/AddRingCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/AddRingCommand.cs @@ -1,5 +1,4 @@ -using System.Linq; -using StardewModdingAPI; +using StardewModdingAPI; using StardewValley; using StardewValley.Objects; @@ -19,24 +18,11 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGetInt(0, "ring ID", out int ringID, min: Ring.ringLowerIndexRange, max: Ring.ringUpperIndexRange)) return; - } - if (!int.TryParse(args[0], out int ringID)) - { - monitor.Log(" is invalid", LogLevel.Error); - return; - } - if (ringID < Ring.ringLowerIndexRange || ringID > Ring.ringUpperIndexRange) - { - monitor.Log($"There is no such ring ID (must be between {Ring.ringLowerIndexRange} and {Ring.ringUpperIndexRange}).", LogLevel.Error); - return; - } // handle Ring ring = new Ring(ringID); diff --git a/src/TrainerMod/Framework/Commands/Player/AddWallpaperCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddWallpaperCommand.cs index e02b05a4..dddb9ffd 100644 --- a/src/TrainerMod/Framework/Commands/Player/AddWallpaperCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/AddWallpaperCommand.cs @@ -1,5 +1,4 @@ -using System.Linq; -using StardewModdingAPI; +using StardewModdingAPI; using StardewValley; using StardewValley.Objects; @@ -19,24 +18,11 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGetInt(0, "wallpaper ID", out int wallpaperID, min: 0, max: 111)) return; - } - if (!int.TryParse(args[0], out int wallpaperID)) - { - this.LogArgumentNotInt(monitor, command); - return; - } - if (wallpaperID < 0 || wallpaperID > 111) - { - monitor.Log("There is no such wallpaper ID (must be between 0 and 111).", LogLevel.Error); - return; - } // handle Wallpaper wallpaper = new Wallpaper(wallpaperID); diff --git a/src/TrainerMod/Framework/Commands/Player/AddWeaponCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddWeaponCommand.cs index ee94093f..c4ea3d6f 100644 --- a/src/TrainerMod/Framework/Commands/Player/AddWeaponCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/AddWeaponCommand.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using StardewModdingAPI; using StardewValley; using StardewValley.Tools; @@ -20,19 +19,11 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGetInt(0, "weapon ID", out int weaponID, min: 0)) return; - } - if (!int.TryParse(args[0], out int weaponID)) - { - this.LogUsageError(monitor, "The weapon ID must be an integer.", command); - return; - } // get raw weapon data if (!Game1.content.Load>("Data\\weapons").TryGetValue(weaponID, out string data)) diff --git a/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs b/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs index a1b9aceb..68adf8c2 100644 --- a/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs @@ -22,9 +22,9 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - var matches = this.GetItems(args).ToArray(); + var matches = this.GetItems(args.ToArray()).ToArray(); // show matches string summary = "Searching...\n"; diff --git a/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs index 00907fba..28ace0df 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs @@ -18,22 +18,23 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (args.Length <= 2) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "eyes", "pants" })) return; - } - if (!this.TryParseColor(args[1], out Color color)) + if (!args.TryGet(1, "color", out string rawColor)) + return; + + // parse color + if (!this.TryParseColor(rawColor, out Color color)) { - this.LogUsageError(monitor, "The color should be an RBG value like '255,150,0'.", command); + this.LogUsageError(monitor, "Argument 1 (color) must be an RBG value like '255,150,0'."); return; } // handle - switch (args[0]) + switch (target) { case "hair": Game1.player.hairstyleColor = color; @@ -49,10 +50,6 @@ namespace TrainerMod.Framework.Commands.Player Game1.player.pantsColor = color; monitor.Log("OK, your pants color is updated.", LogLevel.Info); break; - - default: - this.LogArgumentsInvalid(monitor, command); - break; } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs index d3f06459..f64e9035 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs @@ -32,9 +32,9 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate + // no-argument mode if (!args.Any()) { monitor.Log($"You currently have {(this.InfiniteHealth ? "infinite" : Game1.player.health.ToString())} health. Specify a value to change it.", LogLevel.Info); @@ -57,7 +57,7 @@ namespace TrainerMod.Framework.Commands.Player monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); } else - this.LogArgumentNotInt(monitor, command); + this.LogArgumentNotInt(monitor); } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs index ff74f981..59b28a3c 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate if (!args.Any()) @@ -28,13 +28,11 @@ namespace TrainerMod.Framework.Commands.Player } // handle - if (int.TryParse(args[0], out int amount)) + if (args.TryGetInt(0, "amount", out int amount, min: 0)) { Game1.player.immunity = amount; monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); } - else - this.LogArgumentNotInt(monitor, command); } } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs index 4982a0b8..b223aa9f 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs @@ -17,22 +17,16 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate - if (args.Length <= 2) - { - this.LogArgumentsInvalid(monitor, command); + if (!args.TryGet(0, "skill", out string skill, oneOf: new[] { "luck", "mining", "combat", "farming", "fishing", "foraging" })) return; - } - if (!int.TryParse(args[1], out int level)) - { - this.LogArgumentNotInt(monitor, command); + if (!args.TryGetInt(1, "level", out int level, min: 0, max: 10)) return; - } // handle - switch (args[0]) + switch (skill) { case "luck": Game1.player.LuckLevel = level; @@ -63,10 +57,6 @@ namespace TrainerMod.Framework.Commands.Player Game1.player.ForagingLevel = level; monitor.Log($"OK, your foraging skill is now {Game1.player.ForagingLevel}.", LogLevel.Info); break; - - default: - this.LogUsageError(monitor, "That isn't a valid skill.", command); - break; } } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs index 73ba252a..4b9d87dc 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate if (!args.Any()) @@ -28,13 +28,11 @@ namespace TrainerMod.Framework.Commands.Player } // handle - if (int.TryParse(args[0], out int maxHealth)) + if (args.TryGetInt(0, "amount", out int amount, min: 1)) { - Game1.player.maxHealth = maxHealth; + Game1.player.maxHealth = amount; monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); } - else - this.LogArgumentNotInt(monitor, command); } } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs index c21f6592..3997bb1b 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate if (!args.Any()) @@ -28,13 +28,11 @@ namespace TrainerMod.Framework.Commands.Player } // handle - if (int.TryParse(args[0], out int amount)) + if (args.TryGetInt(0, "amount", out int amount, min: 1)) { Game1.player.MaxStamina = amount; monitor.Log($"OK, you now have {Game1.player.MaxStamina} max stamina.", LogLevel.Info); } - else - this.LogArgumentNotInt(monitor, command); } } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs index ad74499d..55e069a4 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs @@ -32,7 +32,7 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate if (!args.Any()) @@ -57,7 +57,7 @@ namespace TrainerMod.Framework.Commands.Player monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info); } else - this.LogArgumentNotInt(monitor, command); + this.LogArgumentNotInt(monitor); } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs index 8284d882..3fd4475c 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs @@ -17,29 +17,34 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (args.Length <= 1) - { - monitor.Log($"Your name is currently '{Game1.player.Name}'. Type 'help player_setname' for usage.", LogLevel.Info); + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "player", "farm" })) return; - } + args.TryGet(1, "name", out string name, required: false); // handle - string target = args[0]; switch (target) { case "player": - Game1.player.Name = args[1]; - monitor.Log($"OK, your player's name is now {Game1.player.Name}.", LogLevel.Info); + if (!string.IsNullOrWhiteSpace(name)) + { + Game1.player.Name = args[1]; + monitor.Log($"OK, your name is now {Game1.player.Name}.", LogLevel.Info); + } + else + monitor.Log($"Your name is currently '{Game1.player.Name}'. Type 'help player_setname' for usage.", LogLevel.Info); break; + case "farm": - Game1.player.farmName = args[1]; - monitor.Log($"OK, your farm's name is now {Game1.player.Name}.", LogLevel.Info); - break; - default: - this.LogArgumentsInvalid(monitor, command); + if (!string.IsNullOrWhiteSpace(name)) + { + Game1.player.farmName = args[1]; + monitor.Log($"OK, your farm's name is now {Game1.player.farmName}.", LogLevel.Info); + } + else + monitor.Log($"Your farm's name is currently '{Game1.player.farmName}'. Type 'help player_setname' for usage.", LogLevel.Info); break; } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs index a8c05d0c..40b87b62 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs @@ -1,5 +1,4 @@ -using System.Linq; -using StardewModdingAPI; +using StardewModdingAPI; using StardewValley; namespace TrainerMod.Framework.Commands.Player @@ -18,22 +17,14 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {Game1.player.addedSpeed} added speed. Specify a value to change it.", LogLevel.Info); + // parse arguments + if (!args.TryGetInt(0, "added speed", out int amount, min: 0)) return; - } - if (!int.TryParse(args[0], out int addedSpeed)) - { - this.LogArgumentNotInt(monitor, command); - return; - } // handle - Game1.player.addedSpeed = addedSpeed; + Game1.player.addedSpeed = amount; monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs index 55a55eab..d44d1370 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs @@ -32,7 +32,7 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { // validate if (!args.Any()) @@ -57,7 +57,7 @@ namespace TrainerMod.Framework.Commands.Player monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info); } else - this.LogArgumentNotInt(monitor, command); + this.LogArgumentNotInt(monitor); } } diff --git a/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs index 9ef5f88b..96e34af2 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs @@ -17,22 +17,16 @@ namespace TrainerMod.Framework.Commands.Player /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (args.Length <= 1) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "shirt", "acc", "skin", "shoe", "swim", "gender" })) return; - } - if (!int.TryParse(args[1], out int styleID)) - { - this.LogArgumentsInvalid(monitor, command); + if (!args.TryGetInt(1, "style ID", out int styleID)) return; - } // handle - switch (args[0]) + switch (target) { case "hair": Game1.player.changeHairStyle(styleID); @@ -71,7 +65,7 @@ namespace TrainerMod.Framework.Commands.Player monitor.Log("OK, you're now in your swimming suit.", LogLevel.Info); break; default: - this.LogUsageError(monitor, "The swim value should be 0 (no swimming suit) or 1 (swimming suit).", command); + this.LogUsageError(monitor, "The swim value should be 0 (no swimming suit) or 1 (swimming suit)."); break; } break; @@ -88,14 +82,10 @@ namespace TrainerMod.Framework.Commands.Player monitor.Log("OK, you're now female.", LogLevel.Info); break; default: - this.LogUsageError(monitor, "The gender value should be 0 (male) or 1 (female).", command); + this.LogUsageError(monitor, "The gender value should be 0 (male) or 1 (female)."); break; } break; - - default: - this.LogArgumentsInvalid(monitor, command); - break; } } } diff --git a/src/TrainerMod/Framework/Commands/Saves/LoadCommand.cs b/src/TrainerMod/Framework/Commands/Saves/LoadCommand.cs index 1a70b54c..121ad9a6 100644 --- a/src/TrainerMod/Framework/Commands/Saves/LoadCommand.cs +++ b/src/TrainerMod/Framework/Commands/Saves/LoadCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.Saves /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { monitor.Log("Triggering load menu...", LogLevel.Info); Game1.hasLoadedGame = false; diff --git a/src/TrainerMod/Framework/Commands/Saves/SaveCommand.cs b/src/TrainerMod/Framework/Commands/Saves/SaveCommand.cs index 8ce9738d..5f6941e9 100644 --- a/src/TrainerMod/Framework/Commands/Saves/SaveCommand.cs +++ b/src/TrainerMod/Framework/Commands/Saves/SaveCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.Saves /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { monitor.Log("Saving the game...", LogLevel.Info); SaveGame.Save(); diff --git a/src/TrainerMod/Framework/Commands/TrainerCommand.cs b/src/TrainerMod/Framework/Commands/TrainerCommand.cs index 1b18b44b..4715aa04 100644 --- a/src/TrainerMod/Framework/Commands/TrainerCommand.cs +++ b/src/TrainerMod/Framework/Commands/TrainerCommand.cs @@ -25,7 +25,7 @@ namespace TrainerMod.Framework.Commands /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public abstract void Handle(IMonitor monitor, string command, string[] args); + public abstract void Handle(IMonitor monitor, string command, ArgumentParser args); /// Perform any logic needed on update tick. /// Writes messages to the console and log file. @@ -47,26 +47,16 @@ namespace TrainerMod.Framework.Commands /// Log an error indicating incorrect usage. /// Writes messages to the console and log file. /// A sentence explaining the problem. - /// The name of the command. - protected void LogUsageError(IMonitor monitor, string error, string command) + protected void LogUsageError(IMonitor monitor, string error) { - monitor.Log($"{error} Type 'help {command}' for usage.", LogLevel.Error); + monitor.Log($"{error} Type 'help {this.Name}' for usage.", LogLevel.Error); } /// Log an error indicating a value must be an integer. /// Writes messages to the console and log file. - /// The name of the command. - protected void LogArgumentNotInt(IMonitor monitor, string command) + protected void LogArgumentNotInt(IMonitor monitor) { - this.LogUsageError(monitor, "The value must be a whole number.", command); - } - - /// Log an error indicating a value is invalid. - /// Writes messages to the console and log file. - /// The name of the command. - protected void LogArgumentsInvalid(IMonitor monitor, string command) - { - this.LogUsageError(monitor, "The arguments are invalid.", command); + this.LogUsageError(monitor, "The value must be a whole number."); } } } diff --git a/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs b/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs index 2700a0dc..4e62cf77 100644 --- a/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs @@ -18,7 +18,7 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { int level = (Game1.currentLocation as MineShaft)?.mineLevel ?? 0; monitor.Log($"OK, warping you to mine level {level + 1}.", LogLevel.Info); diff --git a/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs b/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs index 89cd68cb..13d08398 100644 --- a/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs @@ -35,23 +35,18 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { if (args.Any()) { - if (int.TryParse(args[0], out int value)) - { - if (value == 0 || value == 1) - { - this.FreezeTime = value == 1; - FreezeTimeCommand.FrozenTime = Game1.timeOfDay; - monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); - } - else - this.LogUsageError(monitor, "The value should be 0 (not frozen), 1 (frozen), or empty (toggle).", command); - } - else - this.LogArgumentNotInt(monitor, command); + // parse arguments + if (!args.TryGetInt(0, "value", out int value, min: 0, max: 1)) + return; + + // handle + this.FreezeTime = value == 1; + FreezeTimeCommand.FrozenTime = Game1.timeOfDay; + monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); } else { diff --git a/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs b/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs index e47b76a7..54267384 100644 --- a/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs @@ -18,24 +18,18 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate + // no-argument mode if (!args.Any()) { monitor.Log($"The current date is {Game1.currentSeason} {Game1.dayOfMonth}. Specify a value to change the day.", LogLevel.Info); return; } - if (!int.TryParse(args[0], out int day)) - { - this.LogArgumentNotInt(monitor, command); + + // parse arguments + if (!args.TryGetInt(0, "day", out int day, min: 1, max: 28)) return; - } - if (day > 28 || day <= 0) - { - this.LogUsageError(monitor, "That isn't a valid day.", command); - return; - } // handle Game1.dayOfMonth = day; diff --git a/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs b/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs index bfcc566f..225ec091 100644 --- a/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using StardewModdingAPI; using StardewValley; @@ -19,19 +18,11 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate - if (!args.Any()) - { - this.LogArgumentsInvalid(monitor, command); + // parse arguments + if (!args.TryGetInt(0, "mine level", out int level, min: 1)) return; - } - if (!int.TryParse(args[0], out int level)) - { - this.LogArgumentNotInt(monitor, command); - return; - } // handle level = Math.Max(1, level); diff --git a/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs b/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs index d60f8601..96c3d920 100644 --- a/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs @@ -25,22 +25,21 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate + // no-argument mode if (!args.Any()) { monitor.Log($"The current season is {Game1.currentSeason}. Specify a value to change it.", LogLevel.Info); return; } - if (!this.ValidSeasons.Contains(args[0])) - { - this.LogUsageError(monitor, "That isn't a valid season name.", command); + + // parse arguments + if (!args.TryGet(0, "season", out string season, oneOf: this.ValidSeasons)) return; - } // handle - Game1.currentSeason = args[0]; + Game1.currentSeason = season; monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); } } diff --git a/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs b/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs index 4ecff485..c827ea5e 100644 --- a/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs @@ -18,24 +18,18 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate + // no-argument mode if (!args.Any()) { monitor.Log($"The current time is {Game1.timeOfDay}. Specify a value to change it.", LogLevel.Info); return; } - if (!int.TryParse(args[0], out int time)) - { - this.LogArgumentNotInt(monitor, command); + + // parse arguments + if (!args.TryGetInt(0, "time", out int time, min: 600, max: 2600)) return; - } - if (time > 2600 || time < 600) - { - this.LogUsageError(monitor, "That isn't a valid time.", command); - return; - } // handle Game1.timeOfDay = time; diff --git a/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs b/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs index 6b2b0d93..760fc170 100644 --- a/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs +++ b/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs @@ -18,24 +18,18 @@ namespace TrainerMod.Framework.Commands.World /// Writes messages to the console and log file. /// The command name. /// The command arguments. - public override void Handle(IMonitor monitor, string command, string[] args) + public override void Handle(IMonitor monitor, string command, ArgumentParser args) { - // validate + // no-argument mode if (!args.Any()) { monitor.Log($"The current year is {Game1.year}. Specify a value to change the year.", LogLevel.Info); return; } - if (!int.TryParse(args[0], out int year)) - { - this.LogArgumentNotInt(monitor, command); + + // parse arguments + if (!args.TryGetInt(0, "year", out int year, min: 1)) return; - } - if (year < 1) - { - this.LogUsageError(monitor, "That isn't a valid year.", command); - return; - } // handle Game1.year = year; diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 047bbbfe..5db02cd6 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -58,7 +58,8 @@ namespace TrainerMod /// The command arguments. private void HandleCommand(ITrainerCommand command, string commandName, string[] args) { - command.Handle(this.Monitor, commandName, args); + ArgumentParser argParser = new ArgumentParser(commandName, args, this.Monitor); + command.Handle(this.Monitor, commandName, argParser); } /// Find all commands in the assembly. diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj index 1702c577..ee17f970 100644 --- a/src/TrainerMod/TrainerMod.csproj +++ b/src/TrainerMod/TrainerMod.csproj @@ -51,6 +51,7 @@ Properties\GlobalAssemblyInfo.cs +