From d9c001a39f4562038fb608468169ea22cf074111 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:06:18 -0400 Subject: fix version zero validation --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index c70820e4..7761a567 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -173,7 +173,7 @@ namespace StardewModdingAPI.Framework.ModLoading if (string.IsNullOrWhiteSpace(mod.Manifest.Name)) missingFields.Add(nameof(IManifest.Name)); - if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0") + if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0.0") missingFields.Add(nameof(IManifest.Version)); if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID)) missingFields.Add(nameof(IManifest.UniqueID)); -- cgit From 4b391d631ccde301969ae0c04f407a01ba4055b1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:12:03 -0400 Subject: normalize manifest array fields --- src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs | 2 +- src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs | 2 +- src/SMAPI.Toolkit/Serialization/Models/Manifest.cs | 10 ++++++++++ src/SMAPI/Framework/ModLoading/ModMetadata.cs | 19 +++++++++++-------- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- src/SMAPI/Framework/SCore.cs | 19 ++++++++----------- 6 files changed, 32 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs index 6658d219..b02be3e4 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs @@ -66,7 +66,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData { // update key case ModDataFieldKey.UpdateKey: - return manifest.UpdateKeys != null && manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p)); + return manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p)); // non-manifest fields case ModDataFieldKey.StatusReasonPhrase: diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs index d0df09a1..825b98e5 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -69,7 +69,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning public IEnumerable GetUpdateKeys(Manifest manifest) { return - (manifest.UpdateKeys ?? new string[0]) + manifest.UpdateKeys .Where(p => !string.IsNullOrWhiteSpace(p)) .ToArray(); } diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs index 99e85cbd..46b654a5 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Runtime.Serialization; using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Serialization.Converters; @@ -70,5 +71,14 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models this.UpdateKeys = new string[0]; this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor }; } + + /// Normalize the model after it's deserialized. + /// The deserialization context. + [OnDeserialized] + public void OnDeserialized(StreamingContext context) + { + this.Dependencies ??= new IManifestDependency[0]; + this.UpdateKeys ??= new string[0]; + } } } diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 17e6d59a..b703f74c 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -195,7 +195,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// public IEnumerable GetUpdateKeys(bool validOnly = false) { - foreach (string rawKey in this.Manifest?.UpdateKeys ?? new string[0]) + if (this.Manifest == null) + yield break; + + foreach (string rawKey in this.Manifest.UpdateKeys) { UpdateKey updateKey = UpdateKey.Parse(rawKey); if (updateKey.LooksValid || !validOnly) @@ -251,16 +254,16 @@ namespace StardewModdingAPI.Framework.ModLoading { var ids = new Dictionary(StringComparer.OrdinalIgnoreCase); - // yield dependencies - if (this.Manifest?.Dependencies != null) + if (this.Manifest != null) { - foreach (var entry in this.Manifest?.Dependencies) + // yield dependencies + foreach (IManifestDependency entry in this.Manifest.Dependencies) ids[entry.UniqueID] = entry.IsRequired; - } - // yield content pack parent - if (this.Manifest?.ContentPackFor?.UniqueID != null) - ids[this.Manifest.ContentPackFor.UniqueID] = true; + // yield content pack parent + if (this.Manifest.ContentPackFor?.UniqueID != null) + ids[this.Manifest.ContentPackFor.UniqueID] = true; + } return ids; } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 7761a567..3bf2b0a1 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -82,7 +82,7 @@ namespace StardewModdingAPI.Framework.ModLoading // get update URLs List updateUrls = new List(); - foreach (string key in mod.Manifest.UpdateKeys ?? new string[0]) + foreach (string key in mod.Manifest.UpdateKeys) { string url = getUpdateUrl(key); if (url != null) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 5862b112..8b2c7544 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1593,19 +1593,16 @@ namespace StardewModdingAPI.Framework // validate dependencies // Although dependencies are validated before mods are loaded, a dependency may have failed to load. - if (mod.Manifest.Dependencies?.Any() == true) + foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) { - foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) + if (this.ModRegistry.Get(dependency.UniqueID) == null) { - if (this.ModRegistry.Get(dependency.UniqueID) == null) - { - string dependencyName = mods - .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) - ?.DisplayName ?? dependency.UniqueID; - errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; - failReason = ModFailReason.MissingDependencies; - return false; - } + string dependencyName = mods + .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) + ?.DisplayName ?? dependency.UniqueID; + errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; + failReason = ModFailReason.MissingDependencies; + return false; } } -- cgit From 7c76c5cad2c6edac75d6387dbaa5999295be46d8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:13:39 -0400 Subject: add validation for the manifest 'Dependencies' field --- docs/release-notes.md | 1 + src/SMAPI/Framework/ModLoading/ModResolver.cs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 859bf155..f3440d43 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For mod authors: + * Added validation for the manifest `Dependencies` field. * Fixed validation for mods with version `0.0.0`. ## 3.10.1 diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 3bf2b0a1..2f506571 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -188,6 +188,28 @@ namespace StardewModdingAPI.Framework.ModLoading // validate ID format if (!PathUtilities.IsSlug(mod.Manifest.UniqueID)) mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); + + // validate dependencies + foreach (var dependency in mod.Manifest.Dependencies) + { + // null dependency + if (dependency == null) + { + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a null entry under {nameof(IManifest.Dependencies)}."); + continue; + } + + // missing ID + if (string.IsNullOrWhiteSpace(dependency.UniqueID)) + { + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field."); + continue; + } + + // invalid ID + if (!PathUtilities.IsSlug(dependency.UniqueID)) + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); + } } // validate IDs are unique -- cgit From 4ac04ee3aca00f7c1e105ddabd1ed16995b17086 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:19:30 -0400 Subject: fix error if a mod has a 'Dependencies' entry with no ID --- src/SMAPI/Framework/ModLoading/ModMetadata.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index b703f74c..0ace084f 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -195,7 +195,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// public IEnumerable GetUpdateKeys(bool validOnly = false) { - if (this.Manifest == null) + if (!this.HasManifest()) yield break; foreach (string rawKey in this.Manifest.UpdateKeys) @@ -254,14 +254,17 @@ namespace StardewModdingAPI.Framework.ModLoading { var ids = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (this.Manifest != null) + if (this.HasManifest()) { // yield dependencies foreach (IManifestDependency entry in this.Manifest.Dependencies) - ids[entry.UniqueID] = entry.IsRequired; + { + if (!string.IsNullOrWhiteSpace(entry.UniqueID)) + ids[entry.UniqueID] = entry.IsRequired; + } // yield content pack parent - if (this.Manifest.ContentPackFor?.UniqueID != null) + if (!string.IsNullOrWhiteSpace(this.Manifest.ContentPackFor?.UniqueID)) ids[this.Manifest.ContentPackFor.UniqueID] = true; } -- cgit From 9b09941bd53dff2bf47c9fa8b5afffe267b01aaf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 10 May 2021 22:40:13 -0400 Subject: set script path in Windows launch script (#777) --- docs/release-notes.md | 3 +++ src/SMAPI.Installer/assets/windows-install.bat | 1 + 2 files changed, 4 insertions(+) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index f3440d43..304423fc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,9 @@ --> ## Upcoming release +* For players: + * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). + * For mod authors: * Added validation for the manifest `Dependencies` field. * Fixed validation for mods with version `0.0.0`. diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat index 2cc54e80..18c40a64 100644 --- a/src/SMAPI.Installer/assets/windows-install.bat +++ b/src/SMAPI.Installer/assets/windows-install.bat @@ -4,5 +4,6 @@ if not errorlevel 1 ( echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first. pause ) else ( + cd "%~dp0" start /WAIT /B ./internal/windows-install.exe ) -- cgit From fe25a122f44fcd1454df3d5185efe9e54714a684 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 May 2021 20:45:46 -0400 Subject: fix some vanilla shirts not returned by item repo --- docs/release-notes.md | 1 + .../Framework/ItemRepository.cs | 59 +++++++++++++++------- 2 files changed, 41 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 304423fc..666dc8e4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). + * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. * For mod authors: * Added validation for the manifest `Dependencies` field. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 34149209..69194f75 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -62,25 +62,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand()); // clothing - { - // items - HashSet clothingIds = new HashSet(); - foreach (int id in Game1.clothingInformation.Keys) - { - if (id < 0) - continue; // placeholder data for character customization clothing below - - clothingIds.Add(id); - yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); - } - - // character customization shirts (some shirts in this range have no data, but game has special logic to handle them) - for (int id = 1000; id <= 1111; id++) - { - if (!clothingIds.Contains(id)) - yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); - } - } + foreach (int id in this.GetShirtIds()) + yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); // wallpapers for (int id = 0; id < 112; id++) @@ -333,5 +316,43 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework ? new Color(61, 55, 42) : (TailoringMenu.GetDyeColor(fish) ?? Color.Orange); } + + /// Get valid shirt IDs. + /// + /// Shirts have a possible range of 1000–1999, but not all of those IDs are valid. There are two sets of IDs: + /// + /// + /// + /// Shirts which exist in . + /// + /// + /// Shirts with a dynamic ID and no entry in . These automatically + /// use the generic shirt entry with ID -1 and are mapped to a calculated position in the + /// Characters/Farmer/shirts spritesheet. There's no constant we can use, but some known valid + /// ranges are 1000–1111 (used in for the customization screen and + /// 1000–1127 (used in and ). + /// Based on the spritesheet, the max valid ID is 1299. + /// + /// + /// + private IEnumerable GetShirtIds() + { + // defined shirt items + foreach (int id in Game1.clothingInformation.Keys) + { + if (id < 0) + continue; // placeholder data for character customization clothing below + + yield return id; + } + + // dynamic shirts + HashSet clothingIds = new HashSet(Game1.clothingInformation.Keys); + for (int id = 1000; id <= 1299; id++) + { + if (!clothingIds.Contains(id)) + yield return id; + } + } } } -- cgit From 75d7c101441f034c85ee91afbe4ae83599a17995 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 May 2021 20:52:01 -0400 Subject: add support for filtering the item repo This isn't used by SMAPI itself, but is used by some mods like Lookup Anything that copy this code. --- .../Framework/ItemRepository.cs | 359 ++++++++++++--------- 1 file changed, 201 insertions(+), 158 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 69194f75..0357fe6b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -28,8 +28,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework ** Public methods *********/ /// Get all spawnable items. + /// The item types to fetch (or null for any type). + /// Whether to include flavored variants like "Sunflower Honey". [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] - public IEnumerable GetAll() + public IEnumerable GetAll(ItemType[] itemTypes = null, bool includeVariants = true) { // // @@ -41,205 +43,246 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // // - IEnumerable GetAllRaw() { + HashSet types = itemTypes?.Any() == true ? new HashSet(itemTypes) : null; + bool ShouldGet(ItemType type) => types == null || types.Contains(type); + // get tools - for (int q = Tool.stone; q <= Tool.iridium; q++) + if (ShouldGet(ItemType.Tool)) { - int quality = q; - - yield return this.TryCreate(ItemType.Tool, ToolFactory.axe, _ => ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.hoe, _ => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, _ => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, _ => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); - if (quality != Tool.iridium) - yield return this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, _ => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); + for (int q = Tool.stone; q <= Tool.iridium; q++) + { + int quality = q; + + yield return this.TryCreate(ItemType.Tool, ToolFactory.axe, _ => ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.hoe, _ => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, _ => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, _ => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); + if (quality != Tool.iridium) + yield return this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, _ => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); + } + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset, _ => new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, _ => new Shears()); + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, _ => new Pan()); + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand()); } - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset, _ => new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, _ => new Shears()); - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, _ => new Pan()); - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand()); // clothing - foreach (int id in this.GetShirtIds()) - yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); + if (ShouldGet(ItemType.Clothing)) + { + foreach (int id in this.GetShirtIds()) + yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); + } // wallpapers - for (int id = 0; id < 112; id++) - yield return this.TryCreate(ItemType.Wallpaper, id, p => new Wallpaper(p.ID) { Category = SObject.furnitureCategory }); + if (ShouldGet(ItemType.Wallpaper)) + { + for (int id = 0; id < 112; id++) + yield return this.TryCreate(ItemType.Wallpaper, id, p => new Wallpaper(p.ID) { Category = SObject.furnitureCategory }); + } // flooring - for (int id = 0; id < 56; id++) - yield return this.TryCreate(ItemType.Flooring, id, p => new Wallpaper(p.ID, isFloor: true) { Category = SObject.furnitureCategory }); + if (ShouldGet(ItemType.Flooring)) + { + for (int id = 0; id < 56; id++) + yield return this.TryCreate(ItemType.Flooring, id, p => new Wallpaper(p.ID, isFloor: true) { Category = SObject.furnitureCategory }); + } // equipment - foreach (int id in this.TryLoad("Data\\Boots").Keys) - yield return this.TryCreate(ItemType.Boots, id, p => new Boots(p.ID)); - foreach (int id in this.TryLoad("Data\\hats").Keys) - yield return this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID)); + if (ShouldGet(ItemType.Boots)) + { + foreach (int id in this.TryLoad("Data\\Boots").Keys) + yield return this.TryCreate(ItemType.Boots, id, p => new Boots(p.ID)); + } + if (ShouldGet(ItemType.Hat)) + { + foreach (int id in this.TryLoad("Data\\hats").Keys) + yield return this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID)); + } // weapons - foreach (int id in this.TryLoad("Data\\weapons").Keys) + if (ShouldGet(ItemType.Weapon)) { - yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) - ? (Item)new Slingshot(p.ID) - : new MeleeWeapon(p.ID) - ); + foreach (int id in this.TryLoad("Data\\weapons").Keys) + { + yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) + ? (Item)new Slingshot(p.ID) + : new MeleeWeapon(p.ID) + ); + } } // furniture - foreach (int id in this.TryLoad("Data\\Furniture").Keys) - yield return this.TryCreate(ItemType.Furniture, id, p => Furniture.GetFurnitureInstance(p.ID)); + if (ShouldGet(ItemType.Furniture)) + { + foreach (int id in this.TryLoad("Data\\Furniture").Keys) + yield return this.TryCreate(ItemType.Furniture, id, p => Furniture.GetFurnitureInstance(p.ID)); + } // craftables - foreach (int id in Game1.bigCraftablesInformation.Keys) - yield return this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID)); + if (ShouldGet(ItemType.BigCraftable)) + { + foreach (int id in Game1.bigCraftablesInformation.Keys) + yield return this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID)); + } // objects - foreach (int id in Game1.objectInformation.Keys) + if (ShouldGet(ItemType.Object) || ShouldGet(ItemType.Ring)) { - string[] fields = Game1.objectInformation[id]?.Split('/'); - - // secret notes - if (id == 79) + foreach (int id in Game1.objectInformation.Keys) { - foreach (int secretNoteId in this.TryLoad("Data\\SecretNotes").Keys) + string[] fields = Game1.objectInformation[id]?.Split('/'); + + // secret notes + if (id == 79) { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => + if (ShouldGet(ItemType.Object)) { - SObject note = new SObject(79, 1); - note.name = $"{note.name} #{secretNoteId}"; - return note; - }); + foreach (int secretNoteId in this.TryLoad("Data\\SecretNotes").Keys) + { + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => + { + SObject note = new SObject(79, 1); + note.name = $"{note.name} #{secretNoteId}"; + return note; + }); + } + } } - } - // ring - else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring - yield return this.TryCreate(ItemType.Ring, id, p => new Ring(p.ID)); - - // item - else - { - // spawn main item - SObject item = null; - yield return this.TryCreate(ItemType.Object, id, p => + // ring + else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring { - return item = (p.ID == 812 // roe - ? new ColoredObject(p.ID, 1, Color.White) - : new SObject(p.ID, 1) - ); - }); - if (item == null) - continue; - - // flavored items - switch (item.Category) + if (ShouldGet(ItemType.Ring)) + yield return this.TryCreate(ItemType.Ring, id, p => new Ring(p.ID)); + } + + // item + else if (ShouldGet(ItemType.Object)) { - // fruit products - case SObject.FruitsCategory: - // wine - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) - { - Name = $"{item.Name} Wine", - Price = item.Price * 3, - preserve = { SObject.PreserveType.Wine }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - - // jelly - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) - { - Name = $"{item.Name} Jelly", - Price = 50 + item.Price * 2, - preserve = { SObject.PreserveType.Jelly }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - break; - - // vegetable products - case SObject.VegetableCategory: - // juice - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + item.ParentSheetIndex, _ => new SObject(350, 1) - { - Name = $"{item.Name} Juice", - Price = (int)(item.Price * 2.25d), - preserve = { SObject.PreserveType.Juice }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - - // pickled - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) - { - Name = $"Pickled {item.Name}", - Price = 50 + item.Price * 2, - preserve = { SObject.PreserveType.Pickle }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - break; - - // flower honey - case SObject.flowersCategory: - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => - { - SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) - { - Name = $"{item.Name} Honey", - preservedParentSheetIndex = { item.ParentSheetIndex } - }; - honey.Price += item.Price * 2; - return honey; - }); - break; - - // roe and aged roe (derived from FishPond.GetFishProduce) - case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: + // spawn main item + SObject item = null; + yield return this.TryCreate(ItemType.Object, id, p => + { + return item = (p.ID == 812 // roe + ? new ColoredObject(p.ID, 1, Color.White) + : new SObject(p.ID, 1) + ); + }); + if (item == null) + continue; + + // flavored items + if (includeVariants) + { + switch (item.Category) { - this.GetRoeContextTagLookups(out HashSet simpleTags, out List> complexTags); + // fruit products + case SObject.FruitsCategory: + // wine + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) + { + Name = $"{item.Name} Wine", + Price = item.Price * 3, + preserve = { SObject.PreserveType.Wine }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); - foreach (var pair in Game1.objectInformation) - { - // get input - SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; - var inputTags = input?.GetContextTags(); - if (inputTags?.Any() != true) - continue; - - // check if roe-producing fish - if (!inputTags.Any(tag => simpleTags.Contains(tag)) && !complexTags.Any(set => set.All(tag => input.HasContextTag(tag)))) - continue; - - // yield roe - SObject roe = null; - Color color = this.GetRoeColor(input); - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => + // jelly + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) { - roe = new ColoredObject(812, 1, color) + Name = $"{item.Name} Jelly", + Price = 50 + item.Price * 2, + preserve = { SObject.PreserveType.Jelly }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + break; + + // vegetable products + case SObject.VegetableCategory: + // juice + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + item.ParentSheetIndex, _ => new SObject(350, 1) + { + Name = $"{item.Name} Juice", + Price = (int)(item.Price * 2.25d), + preserve = { SObject.PreserveType.Juice }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + + // pickled + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) + { + Name = $"Pickled {item.Name}", + Price = 50 + item.Price * 2, + preserve = { SObject.PreserveType.Pickle }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + break; + + // flower honey + case SObject.flowersCategory: + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => + { + SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { - name = $"{input.Name} Roe", - preserve = { Value = SObject.PreserveType.Roe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex } + Name = $"{item.Name} Honey", + preservedParentSheetIndex = { item.ParentSheetIndex } }; - roe.Price += input.Price / 2; - return roe; + honey.Price += item.Price * 2; + return honey; }); + break; - // aged roe - if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + // roe and aged roe (derived from FishPond.GetFishProduce) + case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) + this.GetRoeContextTagLookups(out HashSet simpleTags, out List> complexTags); + + foreach (var pair in Game1.objectInformation) { - name = $"Aged {input.Name} Roe", - Category = -27, - preserve = { Value = SObject.PreserveType.AgedRoe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex }, - Price = roe.Price * 2 - }); + // get input + SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; + var inputTags = input?.GetContextTags(); + if (inputTags?.Any() != true) + continue; + + // check if roe-producing fish + if (!inputTags.Any(tag => simpleTags.Contains(tag)) && !complexTags.Any(set => set.All(tag => input.HasContextTag(tag)))) + continue; + + // yield roe + SObject roe = null; + Color color = this.GetRoeColor(input); + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => + { + roe = new ColoredObject(812, 1, color) + { + name = $"{input.Name} Roe", + preserve = { Value = SObject.PreserveType.Roe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex } + }; + roe.Price += input.Price / 2; + return roe; + }); + + // aged roe + if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + { + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) + { + name = $"Aged {input.Name} Roe", + Category = -27, + preserve = { Value = SObject.PreserveType.AgedRoe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex }, + Price = roe.Price * 2 + }); + } + } } - } + break; } - break; + } } } } -- cgit From c310875f902f82ef3b9486e2ff3705106b2ad40c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 19 May 2021 23:38:10 -0400 Subject: fix 'loaded with custom settings' message shown with default settings --- docs/release-notes.md | 1 + src/SMAPI/Framework/Models/SConfig.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 666dc8e4..a0b64bdc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,6 +15,7 @@ * For mod authors: * Added validation for the manifest `Dependencies` field. * Fixed validation for mods with version `0.0.0`. + * Fixed _loaded with custom settings_ trace log when using default settings. ## 3.10.1 Released 03 May 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index a71bafd9..10bf9f94 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(AggressiveMemoryOptimizations)] = true + [nameof(AggressiveMemoryOptimizations)] = false }; /// The default values for , to log changes if different. -- cgit From 38a76b5108f679fedb0032a1d70bd8ca89f47283 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 May 2021 11:24:34 -0400 Subject: allow comments and trailing commas in schemas for VSCode The `allowComments` and `allowTrailingCommas` fields are VSCode-specific extensions to JSON Schema. --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 5 ++++- src/SMAPI.Web/wwwroot/schemas/i18n.json | 5 ++++- src/SMAPI.Web/wwwroot/schemas/manifest.json | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index a0b64bdc..1497d6c9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * For mod authors: * Added validation for the manifest `Dependencies` field. + * When using Visual Studio Code to edit JSON files [with a SMAPI JSON schema](technical/web.md#using-a-schema-file-directly), it will no longer warn about comments or trailing commas. * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index 49900da6..6e6278de 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -4,8 +4,11 @@ "title": "Content Patcher content pack", "description": "Content Patcher content file for mods", "@documentationUrl": "https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme", - "type": "object", + "allowComments": true, + "allowTrailingCommas": true, + + "type": "object", "properties": { "Format": { "title": "Format version", diff --git a/src/SMAPI.Web/wwwroot/schemas/i18n.json b/src/SMAPI.Web/wwwroot/schemas/i18n.json index 493ad213..313daa59 100644 --- a/src/SMAPI.Web/wwwroot/schemas/i18n.json +++ b/src/SMAPI.Web/wwwroot/schemas/i18n.json @@ -4,8 +4,11 @@ "title": "SMAPI i18n file", "description": "A translation file for a SMAPI mod or content pack.", "@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation", - "type": "object", + "allowComments": true, + "allowTrailingCommas": true, + + "type": "object", "properties": { "$schema": { "title": "Schema", diff --git a/src/SMAPI.Web/wwwroot/schemas/manifest.json b/src/SMAPI.Web/wwwroot/schemas/manifest.json index 0b265201..05698ba2 100644 --- a/src/SMAPI.Web/wwwroot/schemas/manifest.json +++ b/src/SMAPI.Web/wwwroot/schemas/manifest.json @@ -4,6 +4,10 @@ "title": "SMAPI manifest", "description": "Manifest file for a SMAPI mod or content pack", "@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest", + + "allowComments": true, + "allowTrailingCommas": true, + "type": "object", "properties": { "Name": { -- cgit From 112b505118e65d2741a5cfebce44abd02742dd2f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 May 2021 17:42:24 -0400 Subject: update schema for Content Patcher 1.23 --- docs/release-notes.md | 3 +++ src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 1497d6c9..b12e8931 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,6 +18,9 @@ * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. +* For the web UI: + * Updated the JSON validator/schema for Content Patcher 1.23. + ## 3.10.1 Released 03 May 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index 6e6278de..aac4ff0f 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -14,9 +14,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.", "type": "string", - "const": "1.22.0", + "const": "1.23.0", "@errorMessages": { - "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.22.0'." + "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.23.0'." } }, "ConfigSchema": { -- cgit From b149e11338ba2dbaf030a70783d28b6be21ccf1e Mon Sep 17 00:00:00 2001 From: DiscipleOfEris Date: Wed, 26 May 2021 11:50:49 -0700 Subject: Add `World.FurnitureListChanged` event Create a new event available to SMAPI mods to track furniture changes. To facilitate the event, a `FurnitureListChangedEventArgs` class is added as well. Fixes #778 --- src/SMAPI/Events/FurnitureListChangedEventArgs.cs | 44 ++++++++++++++++++++++ src/SMAPI/Events/IWorldEvents.cs | 3 ++ src/SMAPI/Framework/Events/EventManager.cs | 4 ++ src/SMAPI/Framework/Events/ModWorldEvents.cs | 7 ++++ src/SMAPI/Framework/SCore.cs | 4 ++ .../Framework/StateTracking/LocationTracker.cs | 7 +++- .../StateTracking/Snapshots/LocationSnapshot.cs | 4 ++ 7 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/SMAPI/Events/FurnitureListChangedEventArgs.cs (limited to 'src') diff --git a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs new file mode 100644 index 00000000..04fcf9ff --- /dev/null +++ b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StardewValley; +using StardewValley.Objects; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class FurnitureListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The location which changed. + public GameLocation Location { get; } + + /// The furniture added to the location. + public IEnumerable Added { get; } + + /// The furniture removed from the location. + public IEnumerable Removed { get; } + + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The location which changed. + /// The furniture added to the location. + /// The furniture removed from the location. + internal FurnitureListChangedEventArgs(GameLocation location, IEnumerable added, IEnumerable removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/IWorldEvents.cs index 9569a57b..c023e1f0 100644 --- a/src/SMAPI/Events/IWorldEvents.cs +++ b/src/SMAPI/Events/IWorldEvents.cs @@ -28,5 +28,8 @@ namespace StardewModdingAPI.Events /// Raised after terrain features (like floors and trees) are added or removed in a location. event EventHandler TerrainFeatureListChanged; + + /// Raised after furniture are added or removed in a location. + event EventHandler FurnitureListChanged; } } diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index f4abfffe..dfc289ed 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -162,6 +162,9 @@ namespace StardewModdingAPI.Framework.Events /// Raised after terrain features (like floors and trees) are added or removed in a location. public readonly ManagedEvent TerrainFeatureListChanged; + /// Raised after furniture are added or removed in a location. + public readonly ManagedEvent FurnitureListChanged; + /**** ** Specialized ****/ @@ -238,6 +241,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.ChestInventoryChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ChestInventoryChanged)); this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + this.FurnitureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.FurnitureListChanged)); this.LoadStageChanged = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true); diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs index 21b1b664..f4c40abc 100644 --- a/src/SMAPI/Framework/Events/ModWorldEvents.cs +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -65,6 +65,13 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.TerrainFeatureListChanged.Remove(value); } + /// Raised after furniture are added or removed in a location. + public event EventHandler FurnitureListChanged + { + add => this.EventManager.FurnitureListChanged.Add(value, this.Mod); + remove => this.EventManager.FurnitureListChanged.Remove(value); + } + /********* ** Public methods diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8b2c7544..bf88798b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -914,6 +914,10 @@ namespace StardewModdingAPI.Framework // terrain features changed if (locState.TerrainFeatures.IsChanged) events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, locState.TerrainFeatures.Added, locState.TerrainFeatures.Removed)); + + // furniture changed + if (locState.Furniture.IsChanged) + events.FurnitureListChanged.Raise(new FurnitureListChangedEventArgs(location, locState.Furniture.Added, locState.Furniture.Removed)); } } diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 519fe8f4..6d3a62bb 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -48,6 +48,9 @@ namespace StardewModdingAPI.Framework.StateTracking /// Tracks added or removed terrain features. public IDictionaryWatcher TerrainFeaturesWatcher { get; } + /// Tracks added or removed furniture. + public ICollectionWatcher FurnitureWatcher { get; } + /// Tracks items added or removed to chests. public IDictionary ChestWatchers { get; } = new Dictionary(); @@ -68,6 +71,7 @@ namespace StardewModdingAPI.Framework.StateTracking this.NpcsWatcher = WatcherFactory.ForNetCollection(location.characters); this.ObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects); this.TerrainFeaturesWatcher = WatcherFactory.ForNetDictionary(location.terrainFeatures); + this.FurnitureWatcher = WatcherFactory.ForNetCollection(location.furniture); this.Watchers.AddRange(new IWatcher[] { @@ -76,7 +80,8 @@ namespace StardewModdingAPI.Framework.StateTracking this.LargeTerrainFeaturesWatcher, this.NpcsWatcher, this.ObjectsWatcher, - this.TerrainFeaturesWatcher + this.TerrainFeaturesWatcher, + this.FurnitureWatcher }); this.UpdateChestWatcherList(added: location.Objects.Pairs, removed: new KeyValuePair[0]); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 6ae52fd0..6c9cc4f5 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -34,6 +34,9 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots /// Tracks added or removed terrain features. public SnapshotListDiff> TerrainFeatures { get; } = new SnapshotListDiff>(); + /// Tracks added or removed furniture. + public SnapshotListDiff Furniture { get; } = new SnapshotListDiff(); + /// Tracks changed chest inventories. public IDictionary ChestItems { get; } = new Dictionary(); @@ -59,6 +62,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots this.Npcs.Update(watcher.NpcsWatcher); this.Objects.Update(watcher.ObjectsWatcher); this.TerrainFeatures.Update(watcher.TerrainFeaturesWatcher); + this.Furniture.Update(watcher.FurnitureWatcher); // chest inventories this.ChestItems.Clear(); -- cgit From 9d7b31afc45b89278f5a74697da51a47215d6592 Mon Sep 17 00:00:00 2001 From: DiscipleOfEris Date: Wed, 26 May 2021 11:57:52 -0700 Subject: Update FurnitureListChangedEventArgs.cs Prune unnecessary `using` statements --- src/SMAPI/Events/FurnitureListChangedEventArgs.cs | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs index 04fcf9ff..683f4620 100644 --- a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs +++ b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using StardewValley; using StardewValley.Objects; -- cgit From 03f8777afd4f0707dc8e61eae6e5eae73a88d738 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 27 May 2021 22:59:06 -0400 Subject: add asset propagation for paint masks --- docs/release-notes.md | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 50 ++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 5904c192..b7b053b8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * For mod authors: * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). + * Added asset propagation for building/house paint masks. * Added validation for the manifest `Dependencies` field. * When using Visual Studio Code to edit JSON files [with a SMAPI JSON schema](technical/web.md#using-a-schema-file-directly), it will no longer warn about comments or trailing commas. * Fixed validation for mods with version `0.0.0`. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 623c65d5..5641f90f 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -233,6 +233,16 @@ namespace StardewModdingAPI.Metadata return true; } + case "buildings\\houses_paintmask": // Farm + { + bool removedFromCache = this.RemoveFromPaintMaskCache(key); + + Farm farm = Game1.getFarm(); + farm?.ApplyHousePaint(); + + return removedFromCache || farm != null; + } + /**** ** Content\Characters\Farmer ****/ @@ -613,7 +623,7 @@ namespace StardewModdingAPI.Metadata return this.ReloadFarmAnimalSprites(content, key); if (this.IsInFolder(key, "Buildings")) - return this.ReloadBuildings(content, key); + return this.ReloadBuildings(key); if (this.KeyStartsWith(key, "LooseSprites\\Fence")) return this.ReloadFenceTextures(key); @@ -717,28 +727,39 @@ namespace StardewModdingAPI.Metadata } /// Reload building textures. - /// The content manager through which to reload the asset. /// The asset key to reload. /// Returns whether any textures were reloaded. - private bool ReloadBuildings(LocalizedContentManager content, string key) + private bool ReloadBuildings(string key) { - // get buildings + // get paint mask info + const string paintMaskSuffix = "_PaintMask"; + bool isPaintMask = key.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase); + + // get building type string type = Path.GetFileName(key); + if (isPaintMask) + type = type.Substring(0, type.Length - paintMaskSuffix.Length); + + // get buildings Building[] buildings = this.GetLocations(buildingInteriors: false) .OfType() .SelectMany(p => p.buildings) .Where(p => p.buildingType.Value == type) .ToArray(); - // reload buildings + // remove from paint mask cache + bool removedFromCache = this.RemoveFromPaintMaskCache(key); + + // reload textures if (buildings.Any()) { - Lazy texture = new Lazy(() => content.Load(key)); foreach (Building building in buildings) - building.texture = texture; + building.resetTexture(); + return true; } - return false; + + return removedFromCache; } /// Reload map seat textures. @@ -1295,5 +1316,18 @@ namespace StardewModdingAPI.Metadata // else just (re)load it from the main content manager return this.MainContentManager.Load(key); } + + /// Remove a case-insensitive key from the paint mask cache. + /// The paint mask asset key. + private bool RemoveFromPaintMaskCache(string key) + { + // make cache case-insensitive + // This is needed for cache invalidation since mods may specify keys with a different capitalization + if (!object.ReferenceEquals(BuildingPainter.paintMaskLookup.Comparer, StringComparer.OrdinalIgnoreCase)) + BuildingPainter.paintMaskLookup = new Dictionary>>(BuildingPainter.paintMaskLookup, StringComparer.OrdinalIgnoreCase); + + // remove key from cache + return BuildingPainter.paintMaskLookup.Remove(key); + } } } -- cgit From dfa3beb6a6ba09d350aecc5bc3a8db3f2bd99ab7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 28 May 2021 20:24:29 -0400 Subject: fix i18n schema requiring wrong $schema value --- docs/release-notes.md | 3 ++- src/SMAPI.Web/wwwroot/schemas/i18n.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index b7b053b8..3475a26a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,7 +16,8 @@ * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). * Added asset propagation for building/house paint masks. * Added validation for the manifest `Dependencies` field. - * When using Visual Studio Code to edit JSON files [with a SMAPI JSON schema](technical/web.md#using-a-schema-file-directly), it will no longer warn about comments or trailing commas. + * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments or trailing commas. + * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. diff --git a/src/SMAPI.Web/wwwroot/schemas/i18n.json b/src/SMAPI.Web/wwwroot/schemas/i18n.json index 313daa59..36c04890 100644 --- a/src/SMAPI.Web/wwwroot/schemas/i18n.json +++ b/src/SMAPI.Web/wwwroot/schemas/i18n.json @@ -14,7 +14,7 @@ "title": "Schema", "description": "A reference to this JSON schema. Not part of the actual format, but useful for validation tools.", "type": "string", - "const": "https://smapi.io/schemas/manifest.json" + "const": "https://smapi.io/schemas/i18n.json" } }, -- cgit From c212ff15d28ead57588e130247e9bbfd0b3eab0d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 11 Jun 2021 18:39:38 -0400 Subject: fix installer error for some Windows players --- docs/release-notes.md | 1 + src/SMAPI.Installer/assets/windows-install.bat | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3475a26a..7ac1b518 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -20,6 +20,7 @@ * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. + * Fixed installer error on some older Windows systems (thanks to eddyballs!). * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.23. diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat index 18c40a64..2cd98554 100644 --- a/src/SMAPI.Installer/assets/windows-install.bat +++ b/src/SMAPI.Installer/assets/windows-install.bat @@ -1,9 +1,8 @@ @echo off -echo %~dp0 | findstr /C:"%TEMP%" 1>nul +echo "%~dp0" | findstr /C:"%TEMP%" 1>nul if not errorlevel 1 ( echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first. pause ) else ( - cd "%~dp0" - start /WAIT /B ./internal/windows-install.exe + start /WAIT /B internal\windows-install.exe ) -- cgit From 4df8f4a656ce68b5bb7dc76eefd6da9c27620928 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 11 Jun 2021 19:14:59 -0400 Subject: fix edge case where save constants aren't set correctly --- docs/release-notes.md | 3 ++- src/SMAPI/Constants.cs | 41 ++++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 7ac1b518..0879a072 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * For players: * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. + * Fixed installer error on some older Windows systems (thanks to eddyballs!). * For mod authors: * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). @@ -20,7 +21,7 @@ * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. - * Fixed installer error on some older Windows systems (thanks to eddyballs!). + * Fixed `Constants.SaveFolderName` and `Constants.CurrentSavePath` not set correctly in rare cases. * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.23. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 2e476208..38e8c500 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -321,32 +321,43 @@ namespace StardewModdingAPI *********/ /// Get the name of the save folder, if any. private static string GetSaveFolderName() + { + return Constants.GetSaveFolder()?.Name; + } + + /// Get the path to the current save folder, if any. + private static string GetSaveFolderPathIfExists() + { + DirectoryInfo saveFolder = Constants.GetSaveFolder(); + return saveFolder?.Exists == true + ? saveFolder.FullName + : null; + } + + /// Get the current save folder, if any. + private static DirectoryInfo GetSaveFolder() { // save not available if (Context.LoadStage == LoadStage.None) return null; // get basic info - string saveName = Game1.GetSaveGameName(set_value: false); + string rawSaveName = Game1.GetSaveGameName(set_value: false); ulong saveID = Context.LoadStage == LoadStage.SaveParsed ? SaveGame.loaded.uniqueIDForThisGame : Game1.uniqueIDForThisGame; - // build folder name - return $"{new string(saveName.Where(char.IsLetterOrDigit).ToArray())}_{saveID}"; - } - - /// Get the path to the current save folder, if any. - private static string GetSaveFolderPathIfExists() - { - string folderName = Constants.GetSaveFolderName(); - if (folderName == null) - return null; + // get best match (accounting for rare case where folder name isn't sanitized) + DirectoryInfo folder = null; + foreach (string saveName in new[] { rawSaveName, new string(rawSaveName.Where(char.IsLetterOrDigit).ToArray()) }) + { + folder = new DirectoryInfo(Path.Combine(Constants.SavesPath, $"{saveName}_{saveID}")); + if (folder.Exists) + return folder; + } - string path = Path.Combine(Constants.SavesPath, folderName); - return Directory.Exists(path) - ? path - : null; + // if save doesn't exist yet, return the default one we expect to be created + return folder; } } } -- cgit From 66f8920c29567de615dbcb0a06e78f774d128f6b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 24 Jun 2021 20:17:34 -0400 Subject: log trace message if conflicting software is detected --- docs/release-notes.md | 1 + src/SMAPI/Framework/SCore.cs | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 0879a072..f3f66430 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,6 +17,7 @@ * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). * Added asset propagation for building/house paint masks. * Added validation for the manifest `Dependencies` field. + * Added `TRACE` message if software known to cause issues is installed (currently MSI Afterburner and RivaTuner), to simplify troubleshooting. * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments or trailing commas. * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. * Fixed validation for mods with version `0.0.0`. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index bf88798b..2de2b21a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -11,6 +11,9 @@ using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; +#if SMAPI_FOR_WINDOWS +using Microsoft.Win32; +#endif using Microsoft.Xna.Framework; #if SMAPI_FOR_XNA using System.Windows.Forms; @@ -376,6 +379,9 @@ namespace StardewModdingAPI.Framework mods = resolver.ProcessDependencies(mods, modDatabase).ToArray(); this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase); + // check for software likely to cause issues + this.CheckForSoftwareConflicts(); + // check for updates this.CheckForUpdatesAsync(mods); } @@ -1251,6 +1257,55 @@ namespace StardewModdingAPI.Framework this.LogManager.SetConsoleTitle(consoleTitle); } + /// Log a warning if software known to cause issues is installed. + private void CheckForSoftwareConflicts() + { +#if SMAPI_FOR_WINDOWS + this.Monitor.Log("Checking for known software conflicts..."); + + try + { + string[] registryKeys = { @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" }; + + string[] installedNames = registryKeys + .SelectMany(registryKey => + { + using RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey); + if (key == null) + return new string[0]; + + return key + .GetSubKeyNames() + .Select(subkeyName => + { + using RegistryKey subkey = key.OpenSubKey(subkeyName); + string displayName = (string)subkey?.GetValue("DisplayName"); + string displayVersion = (string)subkey?.GetValue("DisplayVersion"); + + if (displayName != null && displayVersion != null && displayName.EndsWith($" {displayVersion}")) + displayName = displayName.Substring(0, displayName.Length - displayVersion.Length - 1); + + return displayName; + }) + .ToArray(); + }) + .Where(name => name != null && (name.Contains("MSI Afterburner") || name.Contains("RivaTuner"))) + .Distinct() + .OrderBy(name => name) + .ToArray(); + + if (installedNames.Any()) + this.Monitor.Log($" Found {string.Join(" and ", installedNames)} installed, which can conflict with SMAPI. If you experience errors or crashes, try disabling that software or adding an exception for SMAPI / Stardew Valley."); + else + this.Monitor.Log(" None found!"); + } + catch (Exception ex) + { + this.Monitor.Log($"Failed when checking for conflicting software. Technical details:\n{ex}"); + } +#endif + } + /// Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available. /// The mods to include in the update check (if eligible). private void CheckForUpdatesAsync(IModMetadata[] mods) -- cgit From 5e3a1abbd421196ee63442c82cb6606b6630a6ca Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Jun 2021 11:16:12 -0400 Subject: improve error if SMAPI fails to dispose on exit --- docs/release-notes.md | 7 ++++--- src/SMAPI/Framework/SCore.cs | 9 ++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index f3f66430..76539ff6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For players: + * Improved error if SMAPI fails to dispose on game exit. * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. * Fixed installer error on some older Windows systems (thanks to eddyballs!). @@ -18,14 +19,14 @@ * Added asset propagation for building/house paint masks. * Added validation for the manifest `Dependencies` field. * Added `TRACE` message if software known to cause issues is installed (currently MSI Afterburner and RivaTuner), to simplify troubleshooting. - * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments or trailing commas. - * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. * Fixed validation for mods with version `0.0.0`. * Fixed _loaded with custom settings_ trace log when using default settings. * Fixed `Constants.SaveFolderName` and `Constants.CurrentSavePath` not set correctly in rare cases. -* For the web UI: +* For the web UI and JSON validator: * Updated the JSON validator/schema for Content Patcher 1.23. + * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments or trailing commas. + * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. ## 3.10.1 Released 03 May 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2de2b21a..c3285979 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -297,7 +297,14 @@ namespace StardewModdingAPI.Framework } finally { - this.Dispose(); + try + { + this.Dispose(); + } + catch (Exception ex) + { + this.Monitor.Log($"The game ended, but SMAPI wasn't able to dispose correctly. Technical details: {ex}", LogLevel.Error); + } } } -- cgit From 7b097c4f6fd4f716b2afef6a684122ca0c060ac0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 2 Jul 2021 20:52:22 -0400 Subject: update launch script on Linux for 1.4.5 multiplayer hotfix (#789) --- docs/release-notes.md | 1 + src/SMAPI.Installer/assets/unix-launcher.sh | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 76539ff6..c9fcbc12 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * Improved error if SMAPI fails to dispose on game exit. + * Updated Linux launch script for Stardew Valley 1.4.5 multiplayer hotfix. * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. * Fixed installer error on some older Windows systems (thanks to eddyballs!). diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh index a33c0d7f..71056c21 100644 --- a/src/SMAPI.Installer/assets/unix-launcher.sh +++ b/src/SMAPI.Installer/assets/unix-launcher.sh @@ -79,44 +79,44 @@ else terminal|termite) # consumes only one argument after -e # options containing space characters are unsupported - exec $TERMINAL_NAME -e "env TERM=xterm $LAUNCH_FILE $@" + exec $TERMINAL_NAME -e "env TERM=xterm LC_ALL=\"C\" $LAUNCH_FILE $@" ;; xterm|konsole|alacritty) # consumes all arguments after -e - exec $TERMINAL_NAME -e env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -e env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; terminator|xfce4-terminal|mate-terminal) # consumes all arguments after -x - exec $TERMINAL_NAME -x env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -x env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; gnome-terminal) # consumes all arguments after -- - exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; kitty) # consumes all trailing arguments - exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; *) # If we don't know the terminal, just try to run it in the current shell. # If THAT fails, launch with no output. - env TERM=xterm $LAUNCH_FILE "$@" + env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" if [ $? -eq 127 ]; then - exec $LAUNCH_FILE --no-terminal "$@" + exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@" fi esac ## terminal isn't executable; fallback to current shell or no terminal else echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell." - env TERM=xterm $LAUNCH_FILE "$@" + env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" if [ $? -eq 127 ]; then - exec $LAUNCH_FILE --no-terminal "$@" + exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@" fi fi fi -- cgit From 40a4992170fe080111cb732f2ac79e0a59f1e199 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 7 Jul 2021 13:10:55 -0400 Subject: update launch script on macOS for 1.4.5 multiplayer hotfix (#789) Thanks to Mr. Podunkian for his help investigating this on macOS! --- docs/release-notes.md | 2 +- src/SMAPI.Installer/assets/unix-launcher.sh | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index c9fcbc12..3214c09e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,7 +10,7 @@ ## Upcoming release * For players: * Improved error if SMAPI fails to dispose on game exit. - * Updated Linux launch script for Stardew Valley 1.4.5 multiplayer hotfix. + * Updated Linux/macOS launch script for Stardew Valley 1.4.5 multiplayer hotfix. * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. * Fixed installer error on some older Windows systems (thanks to eddyballs!). diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh index 71056c21..d309f750 100644 --- a/src/SMAPI.Installer/assets/unix-launcher.sh +++ b/src/SMAPI.Installer/assets/unix-launcher.sh @@ -43,8 +43,34 @@ if [ "$UNAME" == "Darwin" ]; then cp -p StardewValley.bin.osx StardewModdingAPI.bin.osx fi + # Make sure we're running in Terminal (so the user can see errors/warnings/update alerts). + # Previously we would just use `open -a Terminal` to launch the .bin.osx file, but that + # doesn't let us set environment variables. + if [ ! -t 1 ]; then # https://stackoverflow.com/q/911168/262123 + # sanity check to make sure we don't have an infinite loop of opening windows + SKIP_TERMINAL=false + for argument in "$@"; do + if [ "$argument" == "--no-reopen-terminal" ]; then + SKIP_TERMINAL=true + break + fi + done + + # reopen in Terminal if needed + # https://stackoverflow.com/a/29511052/262123 + if [ "$SKIP_TERMINAL" == "false" ]; then + echo "Reopening in the Terminal app..." + echo "\"$0\" $@ --no-reopen-terminal" > /tmp/open-smapi-terminal.sh + chmod +x /tmp/open-smapi-terminal.sh + cat /tmp/open-smapi-terminal.sh + open -W -a Terminal /tmp/open-smapi-terminal.sh + rm /tmp/open-smapi-terminal.sh + exit 0 + fi + fi + # launch SMAPI - open -a Terminal ./StardewModdingAPI.bin.osx "$@" + LC_ALL="C" ./StardewModdingAPI.bin.osx "$@" else # choose binary file to launch LAUNCH_FILE="" -- cgit From 60af28760a6edc509b16e7c62aa8b00ba9798793 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 9 Jul 2021 22:29:43 -0400 Subject: prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 24 +++++++++++++----------- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.ErrorHandler/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 21 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/build/common.targets b/build/common.targets index d538cc53..ce805402 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,7 +1,7 @@ - 3.10.1 + 3.11.0 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 3214c09e..d0aa2fbf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,26 +7,28 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> -## Upcoming release +## 3.11.0 +Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). + * For players: - * Improved error if SMAPI fails to dispose on game exit. - * Updated Linux/macOS launch script for Stardew Valley 1.4.5 multiplayer hotfix. - * Fixed error when running the Windows installer as administrator (thanks to LostLogic!). + * Updated for Stardew Valley 1.4.5 multiplayer hotfix on Linux/macOS. + * Fixed installer error on Windows when running as administrator (thanks to LostLogic!). + * Fixed installer error on some Windows systems (thanks to eddyballs!). + * Fixed error if SMAPI fails to dispose on game exit. * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. - * Fixed installer error on some older Windows systems (thanks to eddyballs!). * For mod authors: * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). * Added asset propagation for building/house paint masks. - * Added validation for the manifest `Dependencies` field. - * Added `TRACE` message if software known to cause issues is installed (currently MSI Afterburner and RivaTuner), to simplify troubleshooting. - * Fixed validation for mods with version `0.0.0`. - * Fixed _loaded with custom settings_ trace log when using default settings. + * Added log message for troubleshooting if Windows software which often causes issues is installed (currently MSI Afterburner and RivaTuner). + * Improved validation for the manifest `Dependencies` field. + * Fixed validation for mods with invalid version `0.0.0`. + * Fixed _loaded with custom settings_ trace log added when using default settings. * Fixed `Constants.SaveFolderName` and `Constants.CurrentSavePath` not set correctly in rare cases. * For the web UI and JSON validator: - * Updated the JSON validator/schema for Content Patcher 1.23. - * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments or trailing commas. + * Updated the JSON validator/schema for Content Patcher 1.23.0. + * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments and trailing commas. * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. ## 3.10.1 diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index e9513309..1781c40d 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.10.1", + "Version": "3.11.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.10.1" + "MinimumApiVersion": "3.11.0" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index b0b29379..82e6152d 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.10.1", + "Version": "3.11.0", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.10.1" + "MinimumApiVersion": "3.11.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index fea9b62e..6042dee4 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.10.1", + "Version": "3.11.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.10.1" + "MinimumApiVersion": "3.11.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 38e8c500..9e93551c 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -61,7 +61,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "3.10.1"; + internal static string RawApiVersion = "3.11.0"; } /// Contains SMAPI's constants and assumptions. -- cgit