diff --git a/build/common.targets b/build/common.targets
index 1ead1508..961da53a 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -7,13 +7,16 @@ repo. It imports the other MSBuild files as needed.
- 3.18.3
+ 3.18.4
SMAPI
latest
$(AssemblySearchPaths);{GAC}
$(DefineConstants);SMAPI_DEPRECATED
true
+
+ embedded
+
enable
$(NoWarn);CS8632
diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets
index 6ea5f0a2..e6c01800 100644
--- a/build/deploy-local-smapi.targets
+++ b/build/deploy-local-smapi.targets
@@ -18,7 +18,6 @@ This assumes `find-game-folder.targets` has already been imported and validated.
-
@@ -62,7 +61,6 @@ This assumes `find-game-folder.targets` has already been imported and validated.
-
@@ -70,12 +68,10 @@ This assumes `find-game-folder.targets` has already been imported and validated.
-
-
diff --git a/build/unix/prepare-install-package.sh b/build/unix/prepare-install-package.sh
index 304579b9..3e33c277 100755
--- a/build/unix/prepare-install-package.sh
+++ b/build/unix/prepare-install-package.sh
@@ -122,7 +122,7 @@ for folder in ${folders[@]}; do
fi
# bundle root files
- for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.pdb" "StardewModdingAPI.xml" "steam_appid.txt"; do
+ for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.xml" "steam_appid.txt"; do
if [ $name == "StardewModdingAPI" ] && [ $folder == "windows" ]; then
name="$name.exe"
fi
@@ -134,7 +134,7 @@ for folder in ${folders[@]}; do
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
- for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
+ for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
done
@@ -164,7 +164,6 @@ for folder in ${folders[@]}; do
mkdir "$targetPath" --parents
cp "$fromPath/$modName.dll" "$targetPath"
- cp "$fromPath/$modName.pdb" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if [ -d "$fromPath/i18n" ]; then
cp -r "$fromPath/i18n" "$targetPath"
diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1
index 71de1154..434d2466 100644
--- a/build/windows/prepare-install-package.ps1
+++ b/build/windows/prepare-install-package.ps1
@@ -142,7 +142,7 @@ foreach ($folder in $folders) {
}
# bundle root files
- foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.pdb", "StardewModdingAPI.xml", "steam_appid.txt")) {
+ foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.xml", "steam_appid.txt")) {
if ($name -eq "StardewModdingAPI" -and $folder -eq "windows") {
$name = "$name.exe"
}
@@ -154,7 +154,7 @@ foreach ($folder in $folders) {
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
- foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
+ foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
}
@@ -189,7 +189,6 @@ foreach ($folder in $folders) {
mkdir "$targetPath" > $null
cp "$fromPath/$modName.dll" "$targetPath"
- cp "$fromPath/$modName.pdb" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if (Test-Path "$fromPath/i18n" -PathType Container) {
cp -Recurse "$fromPath/i18n" "$targetPath"
diff --git a/docs/release-notes.md b/docs/release-notes.md
index e99218bd..40392ce5 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -7,6 +7,29 @@
_If needed, you can update to SMAPI 3.16.0 first and then install the latest version._
-->
+## 3.18.4
+Released 24 June 2023 for Stardew Valley 1.5.6 or later.
+
+* For players:
+ * In multiplayer, the game/SMAPI window titles now show whether you're the main player or a farmhand.
+ * The `test_input` console command now logs input until the command is run again (instead of for 30 seconds).
+ * Fixed logged SMAPI errors not having line numbers on Linux/macOS.
+ * Fixed wezterm terminal support on Linux/macoS (thanks to romangraef!).
+ * Fixed install error if a game folder has an invalid symlink.
+
+* For mod authors:
+ * Added `--no-prompt` installer command-line argument for automated tools (thanks to NyCodeGHG!).
+ * Added clearer error message when a map tilesheet has no image source (thanks to atravita!).
+ * Fixed `Context.HasRemotePlayers` being true when there's no farmhands connected.
+ * Fixed error loading a mod if it explicitly sets `"MinimumApiVersion": null`.
+ * Updated Newtonsoft.Json 13.0.2 → 13.0.3 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.3)) and Pintail 2.2.2 → 2.3.0 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#230)).
+
+* For SMAPI toolkit users:
+ * Fixed `ModFolder` not being JSON-serializable.
+
+* For the web API:
+ * Fixed manifest schema format for the `examples` field (thanks to boneskull!).
+
## 3.18.3
Released 09 April 2023 for Stardew Valley 1.5.6 or later.
@@ -15,7 +38,7 @@ Released 09 April 2023 for Stardew Valley 1.5.6 or later.
* Fixed installer error for some Linux players due to a non-portable shebang (thanks to freyacoded!).
* Fixed error using load order overrides when there are broken mods installed (thanks to atravita!).
* Removed `LargeAddressAware` flag on SMAPI (no longer needed since it's 64-bit now).
- * Improved translations. Thganks to stylemate (updated Korean)!
+ * Improved translations. Thanks to stylemate (updated Korean)!
* For mod authors:
* Added `IsActiveForScreen()` method to `PerScreen`.
diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md
index ab8a2c95..20e8c834 100644
--- a/docs/technical/mod-package.md
+++ b/docs/technical/mod-package.md
@@ -416,28 +416,33 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi
when you compile it.
## Release notes
+## 4.1.1
+Released 24 June 2023 for SMAPI 3.13.0 or later.
+
+* Replaced `.pdb` files with embedded symbols by default. This fixes logged errors not having line numbers on Linux/macOS.
+
### 4.1.0
-Released 08 January 2023.
+Released 08 January 2023 for SMAPI 3.13.0 or later.
* Added `manifest.json` format validation on build (thanks to tylergibbs2!).
* Fixed game DLLs not excluded from the release zip when they're referenced explicitly but `BundleExtraAssemblies` isn't set.
### 4.0.2
-Released 09 October 2022.
+Released 09 October 2022 for SMAPI 3.13.0 or later.
* Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!).
* Fixed `BundleExtraAssemblies` option being partly case-sensitive.
* Fixed `BundleExtraAssemblies` not applying `All` value to game assemblies.
### 4.0.1
-Released 14 April 2022.
+Released 14 April 2022 for SMAPI 3.13.0 or later.
* Added detection for Xbox app game folders.
* Fixed "_conflicts between different versions of Microsoft.Win32.Registry_" warnings in recent SMAPI versions.
* Internal refactoring.
### 4.0.0
-Released 30 November 2021.
+Released 30 November 2021 for SMAPI 3.13.0 or later.
* Updated for Stardew Valley 1.5.5 and SMAPI 3.13.0. (Older versions are no longer supported.)
* Added `IgnoreModFilePaths` option to ignore literal paths.
@@ -459,7 +464,7 @@ Released 30 November 2021.
documentation](#configure).
### 3.3.0
-Released 30 March 2021.
+Released 30 March 2021 for SMAPI 3.0.0 or later.
* Added a build warning when the mod isn't compiled for `Any CPU`.
* Added a `GameFramework` build property set to `MonoGame` or `Xna` based on the platform. This can
@@ -468,32 +473,32 @@ Released 30 March 2021.
* The package now suppresses the misleading 'processor architecture mismatch' warnings.
### 3.2.2
-Released 23 September 2020.
+Released 23 September 2020 for SMAPI 3.0.0 or later.
* Reworked and streamlined how the package is compiled.
* Added [SMAPI-ModTranslationClassBuilder](https://github.com/Pathoschild/SMAPI-ModTranslationClassBuilder)
files to the ignore list.
### 3.2.1
-Released 11 September 2020.
+Released 11 September 2020 for SMAPI 3.0.0 or later.
* Added more detailed logging.
* Fixed _path's format is not supported_ error when using default `Mods` path in 3.2.
### 3.2.0
-Released 07 September 2020.
+Released 07 September 2020 for SMAPI 3.0.0 or later.
* Added option to change `Mods` folder path.
* Rewrote documentation to make it easier to read.
### 3.1.0
-Released 01 February 2020.
+Released 01 February 2020 for SMAPI 3.0.0 or later.
* Added support for semantic versioning 2.0.
* `0Harmony.dll` is now ignored if the mod references Harmony directly (it's bundled with SMAPI).
### 3.0.0
-Released 26 November 2019.
+Released 26 November 2019 for SMAPI 3.0.0 or later.
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
* Added automatic support for `assets` folders.
diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md
index d115aefa..d1591143 100644
--- a/docs/technical/smapi.md
+++ b/docs/technical/smapi.md
@@ -32,6 +32,7 @@ argument | purpose
`--install` | Preselects the install action, skipping the prompt asking what the user wants to do.
`--uninstall` | Preselects the uninstall action, skipping the prompt asking what the user wants to do.
`--game-path "path"` | Specifies the full path to the folder containing the Stardew Valley executable, skipping automatic detection and any prompt to choose a path. If the path is not valid, the installer displays an error.
+`--no-prompt` | Don't let the installer wait for user input (e.g. for cases where it's being run by a script). If the installer is unable to continue without user input, it'll fail instead.
SMAPI itself recognises five arguments, but these are meant for internal use or testing, and might
change without warning. **On Linux/macOS**, command-line arguments won't work; see _environment
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index d00a5df4..c256be42 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -47,8 +47,8 @@ namespace StardewModdingApi.Installer
yield return GetInstallPath("StardewModdingAPI.dll");
yield return GetInstallPath("StardewModdingAPI.exe");
yield return GetInstallPath("StardewModdingAPI.exe.config");
- yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/macOS only
- yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
+ yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // before 3.18.4 (Linux/macOS only)
+ yield return GetInstallPath("StardewModdingAPI.pdb"); // before 3.18.4 (Windows only)
yield return GetInstallPath("StardewModdingAPI.runtimeconfig.json");
yield return GetInstallPath("StardewModdingAPI.xml");
yield return GetInstallPath("smapi-internal");
@@ -135,35 +135,24 @@ namespace StardewModdingApi.Installer
Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}";
Console.WriteLine();
- /****
- ** Check if correct installer
- ****/
-#if SMAPI_FOR_WINDOWS
- if (context.IsUnix)
- {
- this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead.");
- Console.ReadLine();
- return;
- }
-#else
- if (context.IsWindows)
- {
- this.PrintError($"This is the installer for Linux/macOS. Run the 'install on Windows.exe' file instead.");
- Console.ReadLine();
- return;
- }
-#endif
-
/****
** read command-line arguments
****/
- // get action from CLI
+ // get input mode
+ bool allowUserInput = !args.Contains("--no-prompt");
+
+ // get action
bool installArg = args.Contains("--install");
bool uninstallArg = args.Contains("--uninstall");
if (installArg && uninstallArg)
{
this.PrintError("You can't specify both --install and --uninstall command-line flags.");
- Console.ReadLine();
+ this.AwaitConfirmation(allowUserInput);
+ return;
+ }
+ if (!allowUserInput && !installArg && !uninstallArg)
+ {
+ this.PrintError("You must specify --install or --uninstall when running with --no-prompt.");
return;
}
@@ -175,12 +164,31 @@ namespace StardewModdingApi.Installer
gamePathArg = args[pathIndex];
}
+ /****
+ ** Check if correct installer
+ ****/
+#if SMAPI_FOR_WINDOWS
+ if (context.IsUnix)
+ {
+ this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead.");
+ this.AwaitConfirmation(allowUserInput);
+ return;
+ }
+#else
+ if (context.IsWindows)
+ {
+ this.PrintError($"This is the installer for Linux/macOS. Run the 'install on Windows.exe' file instead.");
+ this.AwaitConfirmation(allowUserInput);
+ return;
+ }
+#endif
+
/*********
** Step 2: choose a theme (can't auto-detect on Linux/macOS)
*********/
MonitorColorScheme scheme = MonitorColorScheme.AutoDetect;
- if (context.IsUnix)
+ if (context.IsUnix && allowUserInput)
{
/****
** print header
@@ -245,7 +253,7 @@ namespace StardewModdingApi.Installer
if (installDir == null)
{
this.PrintError("Failed finding your game path.");
- Console.ReadLine();
+ this.AwaitConfirmation(allowUserInput);
return;
}
@@ -262,7 +270,7 @@ namespace StardewModdingApi.Installer
if (!File.Exists(paths.GameDllPath))
{
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
- Console.ReadLine();
+ this.AwaitConfirmation(allowUserInput);
return;
}
Console.Clear();
@@ -340,7 +348,7 @@ namespace StardewModdingApi.Installer
if (context.IsUnix && File.Exists(paths.BackupLaunchScriptPath))
{
this.PrintDebug("Removing SMAPI launcher...");
- this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
+ this.InteractivelyDelete(paths.VanillaLaunchScriptPath, allowUserInput);
File.Move(paths.BackupLaunchScriptPath, paths.VanillaLaunchScriptPath);
}
@@ -352,7 +360,7 @@ namespace StardewModdingApi.Installer
{
this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files...");
foreach (string path in removePaths)
- this.InteractivelyDelete(path);
+ this.InteractivelyDelete(path, allowUserInput);
}
// move global save data folder (changed in 3.2)
@@ -364,7 +372,7 @@ namespace StardewModdingApi.Installer
if (oldDir.Exists)
{
if (newDir.Exists)
- this.InteractivelyDelete(oldDir.FullName);
+ this.InteractivelyDelete(oldDir.FullName, allowUserInput);
else
oldDir.MoveTo(newDir.FullName);
}
@@ -379,7 +387,7 @@ namespace StardewModdingApi.Installer
this.PrintDebug("Adding SMAPI files...");
foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
{
- this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name));
+ this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name), allowUserInput);
this.RecursiveCopy(sourceEntry, paths.GameDir);
}
@@ -394,7 +402,7 @@ namespace StardewModdingApi.Installer
if (!File.Exists(paths.BackupLaunchScriptPath))
File.Move(paths.VanillaLaunchScriptPath, paths.BackupLaunchScriptPath);
else
- this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
+ this.InteractivelyDelete(paths.VanillaLaunchScriptPath, allowUserInput);
}
// add new launcher
@@ -464,7 +472,7 @@ namespace StardewModdingApi.Installer
// remove existing folder
if (targetFolder.Exists)
- this.InteractivelyDelete(targetFolder.FullName);
+ this.InteractivelyDelete(targetFolder.FullName, allowUserInput);
// copy files
this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy);
@@ -482,7 +490,7 @@ namespace StardewModdingApi.Installer
#if SMAPI_DEPRECATED
// remove obsolete appdata mods
- this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir);
+ this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir, allowUserInput);
#endif
}
}
@@ -513,7 +521,7 @@ namespace StardewModdingApi.Installer
);
}
- Console.ReadKey();
+ this.AwaitConfirmation(allowUserInput);
}
@@ -581,7 +589,8 @@ namespace StardewModdingApi.Installer
/// Interactively delete a file or folder path, and block until deletion completes.
/// The file or folder path.
- private void InteractivelyDelete(string path)
+ /// Whether the installer can ask for user input from the terminal.
+ private void InteractivelyDelete(string path, bool allowUserInput)
{
while (true)
{
@@ -594,7 +603,7 @@ namespace StardewModdingApi.Installer
{
this.PrintError($"Oops! The installer couldn't delete {path}: [{ex.GetType().Name}] {ex.Message}.");
this.PrintError("Try rebooting your computer and then run the installer again. If that doesn't work, try deleting it yourself then press any key to retry.");
- Console.ReadKey();
+ this.AwaitConfirmation(allowUserInput);
}
}
}
@@ -814,7 +823,8 @@ namespace StardewModdingApi.Installer
/// Interactively move mods out of the app data directory.
/// The directory which should contain all mods.
/// The installer directory containing packaged mods.
- private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir)
+ /// Whether the installer can ask for user input from the terminal.
+ private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir, bool allowUserInput)
{
// get packaged mods to delete
string[] packagedModNames = packagedModsDir.GetDirectories().Select(p => p.Name).ToArray();
@@ -841,7 +851,7 @@ namespace StardewModdingApi.Installer
if (isDir && packagedModNames.Contains(entry.Name, StringComparer.OrdinalIgnoreCase))
{
this.PrintDebug($" Deleting {entry.Name} because it's bundled into SMAPI...");
- this.InteractivelyDelete(entry.FullName);
+ this.InteractivelyDelete(entry.FullName, allowUserInput);
continue;
}
@@ -906,5 +916,13 @@ namespace StardewModdingApi.Installer
_ => true
};
}
+
+ /// Wait until the user presses enter to confirm, if user input is allowed.
+ /// Whether the installer can ask for user input from the terminal.
+ private void AwaitConfirmation(bool allowUserInput)
+ {
+ if (allowUserInput)
+ Console.ReadLine();
+ }
}
}
diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh
index 778663d7..751e219e 100644
--- a/src/SMAPI.Installer/assets/unix-launcher.sh
+++ b/src/SMAPI.Installer/assets/unix-launcher.sh
@@ -93,7 +93,7 @@ else
# run in terminal
if [ "$USE_CURRENT_SHELL" == "false" ]; then
# select terminal (prefer xterm for best compatibility, then known supported terminals)
- for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do
+ for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator wezterm; do
if command -v "$terminal" 2>/dev/null; then
export TERMINAL_NAME=$terminal
break;
@@ -132,6 +132,11 @@ else
exec "$TERMINAL_NAME" -- env TERM=xterm $LAUNCH_FILE "$@"
;;
+ wezterm)
+ # consumes all arguments after start --. does not copy working directory automatically, needs --cwd for that.
+ exec "$TERMINAL_NAME" start --cwd "$(pwd)" -- env TERM=xterm $LAUNCH_FILE "$@"
+ ;;
+
kitty)
# consumes all trailing arguments
exec "$TERMINAL_NAME" env TERM=xterm $LAUNCH_FILE "$@"
diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
index 1719d39b..454f2ecf 100644
--- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
+++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
@@ -6,9 +6,9 @@
-
+
-
+
diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
index badabfc7..2e97d53f 100644
--- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
+++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
@@ -10,7 +10,7 @@
Pathoschild.Stardew.ModBuildConfig
Build package for SMAPI mods
- 4.1.0
+ 4.1.1
Pathoschild
Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.13.0 or later.
MIT
@@ -24,7 +24,7 @@
-
+
-
+
true
+ embedded
false
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs
index 8bf9f5db..67325c7c 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs
@@ -1,4 +1,3 @@
-using System;
using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
@@ -10,11 +9,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/*********
** Fields
*********/
- /// The number of seconds for which to log input.
- private readonly int LogSeconds = 30;
-
- /// When the command should stop printing input, or null if currently disabled.
- private long? ExpiryTicks;
+ /// Whether the command should print input.
+ private bool Enabled;
/*********
@@ -30,21 +26,12 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/// The command arguments.
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
{
- this.ExpiryTicks = DateTime.UtcNow.Add(TimeSpan.FromSeconds(this.LogSeconds)).Ticks;
- monitor.Log($"OK, logging all player input for {this.LogSeconds} seconds.", LogLevel.Info);
- }
+ this.Enabled = !this.Enabled;
- /// Perform any logic needed on update tick.
- /// Writes messages to the console and log file.
- public override void OnUpdated(IMonitor monitor)
- {
- // handle expiry
- if (this.ExpiryTicks != null && this.ExpiryTicks <= DateTime.UtcNow.Ticks)
- {
- monitor.Log("No longer logging input.", LogLevel.Info);
- this.ExpiryTicks = null;
- return;
- }
+ monitor.Log(
+ this.Enabled ? "OK, logging all player input until you run this command again." : "OK, no longer logging player input.",
+ LogLevel.Info
+ );
}
/// Perform any logic when input is received.
@@ -52,7 +39,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/// The button that was pressed.
public override void OnButtonPressed(IMonitor monitor, SButton button)
{
- if (this.ExpiryTicks != null)
+ if (this.Enabled)
monitor.Log($"Pressed {button}", LogLevel.Info);
}
}
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 2447c5c3..9e70936f 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.18.3",
+ "Version": "3.18.4",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.18.3"
+ "MinimumApiVersion": "3.18.4"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index 306c92fc..b007b672 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.18.3",
+ "Version": "3.18.4",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.18.3"
+ "MinimumApiVersion": "3.18.4"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index c5075c57..7b819ec6 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.18.3",
+ "Version": "3.18.4",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.18.3"
+ "MinimumApiVersion": "3.18.4"
}
diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj
index 0b1fb638..4c320696 100644
--- a/src/SMAPI.Tests/SMAPI.Tests.csproj
+++ b/src/SMAPI.Tests/SMAPI.Tests.csproj
@@ -14,12 +14,12 @@
-
-
+
+
-
+
-
+
diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
index 88142805..45f3e203 100644
--- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
@@ -73,7 +73,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
return GameFolderType.NoGameFound;
// apparently valid
- if (dir.EnumerateFiles("Stardew Valley.dll").Any())
+ if (File.Exists(Path.Combine(dir.FullName, "Stardew Valley.dll")))
return GameFolderType.Valid;
// doesn't contain any version of Stardew Valley
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
index da2a3c85..106e3622 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Models;
using StardewModdingAPI.Toolkit.Utilities;
@@ -9,14 +10,25 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// The info about a mod read from its folder.
public class ModFolder
{
+ /*********
+ ** Fields
+ *********/
+ /// The backing field for .
+ private DirectoryInfo? DirectoryImpl;
+
+
/*********
** Accessors
*********/
/// A suggested display name for the mod folder.
public string DisplayName { get; }
+ /// The folder path containing the mod's manifest.json.
+ public string DirectoryPath { get; }
+
/// The folder containing the mod's manifest.json.
- public DirectoryInfo Directory { get; }
+ [JsonIgnore]
+ public DirectoryInfo Directory => this.DirectoryImpl ??= new DirectoryInfo(this.DirectoryPath);
/// The mod type.
public ModType Type { get; }
@@ -52,7 +64,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText)
{
// save info
- this.Directory = directory;
+ this.DirectoryImpl = directory;
+ this.DirectoryPath = directory.FullName;
this.Type = type;
this.Manifest = manifest;
this.ManifestParseError = manifestParseError;
@@ -64,6 +77,24 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
: PathUtilities.GetRelativePath(root.FullName, directory.FullName);
}
+ /// Construct an instance.
+ /// A suggested display name for the mod folder.
+ /// The folder path containing the mod's manifest.json.
+ /// The mod type.
+ /// The mod manifest.
+ /// The error which occurred parsing the manifest, if any.
+ /// A human-readable message for the , if any.
+ [JsonConstructor]
+ public ModFolder(string displayName, string directoryPath, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText)
+ {
+ this.DisplayName = displayName;
+ this.DirectoryPath = directoryPath;
+ this.Type = type;
+ this.Manifest = manifest;
+ this.ManifestParseError = manifestParseError;
+ this.ManifestParseErrorText = manifestParseErrorText;
+ }
+
/// Get the update keys for a mod.
/// The mod manifest.
public IEnumerable GetUpdateKeys(Manifest manifest)
diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
index 2a9a8294..dce5fe1b 100644
--- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
+++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
index 913d54e0..650815b5 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
@@ -44,6 +44,9 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
string path = reader.Path;
switch (reader.TokenType)
{
+ case JsonToken.Null:
+ return null;
+
case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader));
diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj
index 1e568572..1cb2704d 100644
--- a/src/SMAPI.Web/SMAPI.Web.csproj
+++ b/src/SMAPI.Web/SMAPI.Web.csproj
@@ -15,15 +15,15 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+
diff --git a/src/SMAPI.Web/wwwroot/schemas/manifest.json b/src/SMAPI.Web/wwwroot/schemas/manifest.json
index 7457b993..727e5cbd 100644
--- a/src/SMAPI.Web/wwwroot/schemas/manifest.json
+++ b/src/SMAPI.Web/wwwroot/schemas/manifest.json
@@ -43,7 +43,7 @@
"description": "The DLL filename SMAPI should load for this mod. Mutually exclusive with ContentPackFor.",
"type": "string",
"pattern": "^[a-zA-Z0-9_.-]+\\.dll$",
- "examples": "LookupAnything.dll",
+ "examples": ["LookupAnything.dll"],
"@errorMessages": {
"pattern": "Invalid value; must be a filename ending with .dll."
}
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 77a19964..416e1ec7 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -52,7 +52,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// SMAPI's current raw semantic version.
- internal static string RawApiVersion = "3.18.3";
+ internal static string RawApiVersion = "3.18.4";
}
/// Contains SMAPI's constants and assumptions.
diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs
index 978459e8..3e3ee784 100644
--- a/src/SMAPI/Context.cs
+++ b/src/SMAPI/Context.cs
@@ -88,7 +88,7 @@ namespace StardewModdingAPI
public static bool IsSplitScreen => LocalMultiplayer.IsLocalMultiplayer();
/// Whether there are players connected over the network.
- public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly;
+ public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly && Game1.getOnlineFarmers().Count > 1;
/// Whether the current player is the main player. This is always true in single-player, and true when hosting in multiplayer.
public static bool IsMainPlayer => Game1.IsMasterGame && Context.ScreenId == 0 && TitleMenu.subMenu is not FarmhandMenu;
@@ -97,8 +97,7 @@ namespace StardewModdingAPI
/*********
** Public methods
*********/
- /// Get whether a screen ID is still active.
- /// The screen ID.
+ /// Get whether a screen ID is still active. The screen ID.
public static bool HasScreenId(int id)
{
return Context.ActiveScreenIds.Contains(id);
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 2c068784..8cdb65eb 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -436,6 +436,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
tilesheet.ImageSource = this.NormalizePathSeparators(tilesheet.ImageSource);
string imageSource = tilesheet.ImageSource;
+ // validate image source
+ if (string.IsNullOrWhiteSpace(imageSource))
+ throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet '{tilesheet.Id}'. This tilesheet has no image source.");
+
// reverse incorrect eager tilesheet path prefixing
if (fixEagerPathPrefixes && relativeMapFolder.Length > 0 && imageSource.StartsWith(relativeMapFolder))
imageSource = imageSource[(relativeMapFolder.Length + 1)..];
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index abba7f3b..d16f7248 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -852,6 +852,9 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log(context);
+ // add context to window titles
+ this.UpdateWindowTitles();
+
// raise events
this.OnLoadStageChanged(LoadStage.Ready);
events.SaveLoaded.RaiseEmpty();
@@ -1203,6 +1206,7 @@ namespace StardewModdingAPI.Framework
case LoadStage.None:
this.JustReturnedToTitle = true;
+ this.UpdateWindowTitles();
break;
case LoadStage.Loaded:
@@ -1450,15 +1454,14 @@ namespace StardewModdingAPI.Framework
string consoleTitle = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}";
string gameTitle = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion}";
+ string suffix = "";
if (this.ModRegistry.AreAllModsLoaded)
- {
- int modsLoaded = this.ModRegistry.GetAll().Count();
- consoleTitle += $" with {modsLoaded} mods";
- gameTitle += $" with {modsLoaded} mods";
- }
+ suffix += $" with {this.ModRegistry.GetAll().Count()} mods";
+ if (Context.IsMultiplayer)
+ suffix += $" [{(Context.IsMainPlayer ? "main player" : "farmhand")}]";
- this.Game.Window.Title = gameTitle;
- this.LogManager.SetConsoleTitle(consoleTitle);
+ this.Game.Window.Title = gameTitle + suffix;
+ this.LogManager.SetConsoleTitle(consoleTitle + suffix);
}
/// Log a warning if software known to cause issues is installed.
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index 530b75aa..1be286b4 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -23,9 +23,9 @@
-
+
-
+