summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/CommandManager.cs67
-rw-r--r--src/SMAPI/Framework/SCore.cs67
2 files changed, 110 insertions, 24 deletions
diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs
index 4a99fd4d..ff540ad8 100644
--- a/src/SMAPI/Framework/CommandManager.cs
+++ b/src/SMAPI/Framework/CommandManager.cs
@@ -15,10 +15,20 @@ namespace StardewModdingAPI.Framework
/// <summary>The commands registered with SMAPI.</summary>
private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.OrdinalIgnoreCase);
+ /// <summary>Writes messages to the console.</summary>
+ private readonly IMonitor Monitor;
+
/*********
** Public methods
*********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitor">Writes messages to the console.</param>
+ public CommandManager(IMonitor monitor)
+ {
+ this.Monitor = monitor;
+ }
+
/// <summary>Add a console command.</summary>
/// <param name="mod">The mod adding the command (or <c>null</c> for a SMAPI command).</param>
/// <param name="name">The command name, which the user must type to trigger it.</param>
@@ -81,8 +91,9 @@ namespace StardewModdingAPI.Framework
/// <param name="name">The parsed command name.</param>
/// <param name="args">The parsed command arguments.</param>
/// <param name="command">The command which can handle the input.</param>
+ /// <param name="screenId">The screen ID on which to run the command.</param>
/// <returns>Returns true if the input was successfully parsed and matched to a command; else false.</returns>
- public bool TryParse(string input, out string name, out string[] args, out Command command)
+ public bool TryParse(string input, out string name, out string[] args, out Command command, out int screenId)
{
// ignore if blank
if (string.IsNullOrWhiteSpace(input))
@@ -90,6 +101,7 @@ namespace StardewModdingAPI.Framework
name = null;
args = null;
command = null;
+ screenId = 0;
return false;
}
@@ -98,6 +110,27 @@ namespace StardewModdingAPI.Framework
name = this.GetNormalizedName(args[0]);
args = args.Skip(1).ToArray();
+ // get screen ID argument
+ screenId = 0;
+ for (int i = 0; i < args.Length; i++)
+ {
+ // consume arg & set screen ID
+ if (this.TryParseScreenId(args[i], out int rawScreenId, out string error))
+ {
+ args = args.Take(i).Concat(args.Skip(i + 1)).ToArray();
+ screenId = rawScreenId;
+ continue;
+ }
+
+ // invalid screen arg
+ if (error != null)
+ {
+ this.Monitor.Log(error, LogLevel.Error);
+ command = null;
+ return false;
+ }
+ }
+
// get command
return this.Commands.TryGetValue(name, out command);
}
@@ -152,6 +185,38 @@ namespace StardewModdingAPI.Framework
return args.Where(item => !string.IsNullOrWhiteSpace(item)).ToArray();
}
+ /// <summary>Try to parse a 'screen=X' command argument, which specifies the screen that should receive the command.</summary>
+ /// <param name="arg">The raw argument to parse.</param>
+ /// <param name="screen">The parsed screen ID, if any.</param>
+ /// <param name="error">The error which indicates an invalid screen ID, if applicable.</param>
+ /// <returns>Returns whether the screen ID was parsed successfully.</returns>
+ private bool TryParseScreenId(string arg, out int screen, out string error)
+ {
+ screen = -1;
+ error = null;
+
+ // skip non-screen arg
+ if (!arg.StartsWith("screen="))
+ return false;
+
+ // get screen ID
+ string rawScreen = arg.Substring("screen=".Length);
+ if (!int.TryParse(rawScreen, out screen))
+ {
+ error = $"invalid screen ID format: {rawScreen}";
+ return false;
+ }
+
+ // validate ID
+ if (!Context.HasScreenId(screen))
+ {
+ error = $"there's no active screen with ID {screen}. Active screen IDs: {string.Join(", ", Context.ActiveScreenIds)}.";
+ return false;
+ }
+
+ return true;
+ }
+
/// <summary>Get a normalized command name.</summary>
/// <param name="name">The command name.</param>
private string GetNormalizedName(string name)
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index f9113194..3a51b418 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -81,7 +81,7 @@ namespace StardewModdingAPI.Framework
** Higher-level components
****/
/// <summary>Manages console commands.</summary>
- private readonly CommandManager CommandManager = new CommandManager();
+ private readonly CommandManager CommandManager;
/// <summary>The underlying game instance.</summary>
private SGameRunner Game;
@@ -130,9 +130,12 @@ namespace StardewModdingAPI.Framework
/// <summary>Asset interceptors added or removed since the last tick.</summary>
private readonly List<AssetInterceptorChange> ReloadAssetInterceptorsQueue = new List<AssetInterceptorChange>();
- /// <summary>A list of queued commands to execute.</summary>
+ /// <summary>A list of queued commands to parse and execute.</summary>
/// <remarks>This property must be thread-safe, since it's accessed from a separate console input thread.</remarks>
- public ConcurrentQueue<string> CommandQueue { get; } = new ConcurrentQueue<string>();
+ private readonly ConcurrentQueue<string> RawCommandQueue = new ConcurrentQueue<string>();
+
+ /// <summary>A list of commands to execute on each screen.</summary>
+ private readonly PerScreen<List<Tuple<Command, string, string[]>>> ScreenCommandQueue = new(() => new());
/*********
@@ -169,11 +172,9 @@ namespace StardewModdingAPI.Framework
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), this.Settings);
this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, isVerbose: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog);
-
+ this.CommandManager = new CommandManager(this.Monitor);
this.EventManager = new EventManager(this.ModRegistry);
-
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
-
SDate.Translations = this.Translator;
// log SMAPI/OS info
@@ -406,7 +407,7 @@ namespace StardewModdingAPI.Framework
() => this.LogManager.RunConsoleInputLoop(
commandManager: this.CommandManager,
reloadTranslations: this.ReloadTranslations,
- handleInput: input => this.CommandQueue.Enqueue(input),
+ handleInput: input => this.RawCommandQueue.Enqueue(input),
continueWhile: () => this.IsGameRunning && !this.CancellationToken.IsCancellationRequested
)
).Start();
@@ -489,17 +490,18 @@ namespace StardewModdingAPI.Framework
}
/*********
- ** Execute commands
+ ** Parse commands
*********/
- while (this.CommandQueue.TryDequeue(out string rawInput))
+ while (this.RawCommandQueue.TryDequeue(out string rawInput))
{
// parse command
string name;
string[] args;
Command command;
+ int screenId;
try
{
- if (!this.CommandManager.TryParse(rawInput, out name, out args, out command))
+ if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId))
{
this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
continue;
@@ -511,18 +513,8 @@ namespace StardewModdingAPI.Framework
continue;
}
- // execute command
- try
- {
- command.Callback.Invoke(name, args);
- }
- catch (Exception ex)
- {
- if (command.Mod != null)
- command.Mod.LogAsMod($"Mod failed handling that command:\n{ex.GetLogSummary()}", LogLevel.Error);
- else
- this.Monitor.Log($"Failed handling that command:\n{ex.GetLogSummary()}", LogLevel.Error);
- }
+ // queue command for screen
+ this.ScreenCommandQueue.GetValueForScreen(screenId).Add(Tuple.Create(command, name, args));
}
/*********
@@ -570,7 +562,9 @@ namespace StardewModdingAPI.Framework
try
{
- // reapply overrides
+ /*********
+ ** Reapply overrides
+ *********/
if (this.JustReturnedToTitle)
{
if (!(Game1.mapDisplayDevice is SDisplayDevice))
@@ -580,6 +574,33 @@ namespace StardewModdingAPI.Framework
}
/*********
+ ** Execute commands
+ *********/
+ {
+ var commandQueue = this.ScreenCommandQueue.Value;
+ foreach (var entry in commandQueue)
+ {
+ Command command = entry.Item1;
+ string name = entry.Item2;
+ string[] args = entry.Item3;
+
+ try
+ {
+ command.Callback.Invoke(name, args);
+ }
+ catch (Exception ex)
+ {
+ if (command.Mod != null)
+ command.Mod.LogAsMod($"Mod failed handling that command:\n{ex.GetLogSummary()}", LogLevel.Error);
+ else
+ this.Monitor.Log($"Failed handling that command:\n{ex.GetLogSummary()}", LogLevel.Error);
+ }
+ }
+ commandQueue.Clear();
+ }
+
+
+ /*********
** Update input
*********/
// This should *always* run, even when suppressing mod events, since the game uses