From 6efaa651cb2b026b7bace55ffeba9d7c5a3d0567 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Aug 2021 21:26:01 -0400 Subject: drop support for XNA Framework Stardew Valley 1.5.5 migrates to MonoGame on all platforms. --- src/SMAPI/Framework/Content/ContentCache.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'src/SMAPI/Framework/Content') diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 7edc9ab9..87d15e32 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; -using Microsoft.Xna.Framework; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -52,13 +51,7 @@ namespace StardewModdingAPI.Framework.Content this.Cache = reflection.GetField>(contentManager, "loadedAssets").GetValue(); // get key normalization logic - if (Constants.GameFramework == GameFramework.Xna) - { - IReflectedMethod method = reflection.GetMethod(typeof(TitleContainer), "GetCleanPath"); - this.NormalizeAssetNameForPlatform = path => method.Invoke(path); - } - else - this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic + this.NormalizeAssetNameForPlatform = PathUtilities.NormalizePath; //this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic } /**** -- cgit From 8321cbf6eba7ca3fa46d6e452fd9ebff6baa99d0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Aug 2021 21:26:10 -0400 Subject: update for asset name format change MonoGame uses Linux-style paths for assets on all platforms, which breaks the previous equivalence between path and asset name formats. --- src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs | 8 ++------ src/SMAPI.Toolkit/Utilities/PathUtilities.cs | 2 +- src/SMAPI/Framework/Content/ContentCache.cs | 14 ++++---------- 3 files changed, 7 insertions(+), 17 deletions(-) (limited to 'src/SMAPI/Framework/Content') diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs index c18f47a5..ab4c2618 100644 --- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs +++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs @@ -182,18 +182,14 @@ namespace SMAPI.Tests.Utilities [TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))] public void NormalizeAssetName(SamplePath path) { - if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith("/") || path.OriginalPath.StartsWith("\\")) + if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith('/') || path.OriginalPath.StartsWith('\\')) Assert.Ignore("Absolute paths can't be used as asset names."); // act string normalized = PathUtilities.NormalizeAssetName(path.OriginalPath); // assert -#if SMAPI_FOR_WINDOWS - Assert.AreEqual(path.NormalizedOnWindows, normalized); -#else - Assert.AreEqual(path.NormalizedOnUnix, normalized); -#endif + Assert.AreEqual(path.NormalizedOnUnix, normalized); // MonoGame uses the Linux format } /**** diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index c17bb354..1d378042 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Toolkit.Utilities public static readonly char PreferredPathSeparator = Path.DirectorySeparatorChar; /// The preferred directory separator character in an asset key. - public static readonly char PreferredAssetSeparator = PathUtilities.PreferredPathSeparator; + public static readonly char PreferredAssetSeparator = '/'; /********* diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 87d15e32..8e0c6228 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -17,9 +17,6 @@ namespace StardewModdingAPI.Framework.Content /// The underlying asset cache. private readonly IDictionary Cache; - /// Applies platform-specific asset key normalization so it's consistent with the underlying cache. - private readonly Func NormalizeAssetNameForPlatform; - /********* ** Accessors @@ -47,11 +44,7 @@ namespace StardewModdingAPI.Framework.Content /// Simplifies access to private game code. public ContentCache(LocalizedContentManager contentManager, Reflector reflection) { - // init this.Cache = reflection.GetField>(contentManager, "loadedAssets").GetValue(); - - // get key normalization logic - this.NormalizeAssetNameForPlatform = PathUtilities.NormalizePath; //this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic } /**** @@ -68,23 +61,24 @@ namespace StardewModdingAPI.Framework.Content /**** ** Normalize ****/ - /// Normalize path separators in a file path. For asset keys, see instead. + /// Normalize path separators in an asset name. /// The file path to normalize. [Pure] public string NormalizePathSeparators(string path) { - return PathUtilities.NormalizePath(path); + return PathUtilities.NormalizeAssetName(path); } /// Normalize a cache key so it's consistent with the underlying cache. /// The asset key. + /// This is equivalent to with added file extension logic. [Pure] public string NormalizeKey(string key) { key = this.NormalizePathSeparators(key); return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) ? key.Substring(0, key.Length - 4) - : this.NormalizeAssetNameForPlatform(key); + : key; } /**** -- cgit From fe675540b5c3a48764e70c596132a3ed65a790fb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 25 Sep 2021 01:28:15 -0400 Subject: add map overlay patches --- docs/release-notes.md | 1 + src/SMAPI/Framework/Content/AssetDataForMap.cs | 90 ++++++++++++++++---------- src/SMAPI/IAssetDataForMap.cs | 3 +- src/SMAPI/PatchMapMode.cs | 15 +++++ 4 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 src/SMAPI/PatchMapMode.cs (limited to 'src/SMAPI/Framework/Content') diff --git a/docs/release-notes.md b/docs/release-notes.md index f8c1b495..69eab3ec 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ * For mod authors: * Migrated to 64-bit MonoGame and .NET 5 on all platforms (see [migration guide for mod authors](https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.5.5)). + * Added support for [map overlays via `asset.AsMap().PatchMap`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content#Edit_a_map). **Update note for players with older systems:** The game now has two branches: the _main branch_ which you'll get by default, and an optional diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 4f810948..0a5fa7e7 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI.Toolkit.Utilities; +using StardewValley; using xTile; using xTile.Layers; using xTile.Tiles; @@ -25,18 +26,17 @@ namespace StardewModdingAPI.Framework.Content : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// - /// Derived from with a few changes: + /// Derived from with a few changes: /// - can be applied directly to the maps when loading, before the location is created; - /// - added support for source/target areas; + /// - added support for patch modes (overlay, replace by layer, or fully replace); /// - added disambiguation if source has a modified version of the same tilesheet, instead of copying tiles into the target tilesheet; - /// - changed to always overwrite tiles within the target area (to avoid edge cases where some tiles are only partly applied); /// - fixed copying tilesheets (avoid "The specified TileSheet was not created for use with this map" error); /// - fixed tilesheets not added at the end (via z_ prefix), which can cause crashes in game code which depends on hardcoded tilesheet indexes; /// - fixed issue where different tilesheets are linked by ID. /// - public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null) + public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay) { - var target = this.Data; + Map target = this.Data; // get areas { @@ -84,10 +84,13 @@ namespace StardewModdingAPI.Framework.Content tilesheetMap[sourceSheet] = targetSheet; } - // get layer map - IDictionary layerMap = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id)); + // get target layers + IDictionary sourceToTargetLayers = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id)); + HashSet orphanedTargetLayers = new HashSet(target.Layers.Except(sourceToTargetLayers.Values)); // apply tiles + bool replaceAll = patchMode == PatchMapMode.Replace; + bool replaceByLayer = patchMode == PatchMapMode.ReplaceByLayer; for (int x = 0; x < sourceArea.Value.Width; x++) { for (int y = 0; y < sourceArea.Value.Height; y++) @@ -96,47 +99,37 @@ namespace StardewModdingAPI.Framework.Content Point sourcePos = new Point(sourceArea.Value.X + x, sourceArea.Value.Y + y); Point targetPos = new Point(targetArea.Value.X + x, targetArea.Value.Y + y); + // replace tiles on target-only layers + if (replaceAll) + { + foreach (Layer targetLayer in orphanedTargetLayers) + targetLayer.Tiles[targetPos.X, targetPos.Y] = null; + } + // merge layers foreach (Layer sourceLayer in source.Layers) { // get layer - Layer targetLayer = layerMap[sourceLayer]; + Layer targetLayer = sourceToTargetLayers[sourceLayer]; if (targetLayer == null) { target.AddLayer(targetLayer = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize)); - layerMap[sourceLayer] = target.GetLayer(sourceLayer.Id); + sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id); } // copy layer properties targetLayer.Properties.CopyFrom(sourceLayer.Properties); - // copy tiles + // create new tile Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y]; - Tile targetTile; - switch (sourceTile) - { - case StaticTile _: - targetTile = new StaticTile(targetLayer, tilesheetMap[sourceTile.TileSheet], sourceTile.BlendMode, sourceTile.TileIndex); - break; - - case AnimatedTile animatedTile: - { - StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length]; - for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame) - { - StaticTile frameTile = animatedTile.TileFrames[frame]; - tileFrames[frame] = new StaticTile(targetLayer, tilesheetMap[frameTile.TileSheet], frameTile.BlendMode, frameTile.TileIndex); - } - targetTile = new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval); - } - break; - - default: // null or unhandled type - targetTile = null; - break; - } - targetTile?.Properties.CopyFrom(sourceTile.Properties); - targetLayer.Tiles[targetPos.X, targetPos.Y] = targetTile; + Tile newTile = sourceTile != null + ? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet]) + : null; + newTile?.Properties.CopyFrom(sourceTile.Properties); + + // replace tile + if (newTile != null || replaceByLayer || replaceAll) + targetLayer.Tiles[targetPos.X, targetPos.Y] = newTile; } } } @@ -146,6 +139,33 @@ namespace StardewModdingAPI.Framework.Content /********* ** Private methods *********/ + /// Create a new tile for the target map. + /// The source tile to copy. + /// The target layer. + /// The target tilesheet. + private Tile CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet) + { + switch (sourceTile) + { + case StaticTile _: + return new StaticTile(targetLayer, targetSheet, sourceTile.BlendMode, sourceTile.TileIndex); + + case AnimatedTile animatedTile: + { + StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length]; + for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame) + { + StaticTile frameTile = animatedTile.TileFrames[frame]; + tileFrames[frame] = new StaticTile(targetLayer, targetSheet, frameTile.BlendMode, frameTile.TileIndex); + } + + return new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval); + } + + default: // null or unhandled type + return null; + } + } /// Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path. /// The path to normalize. private string NormalizeTilesheetPathForComparison(string path) diff --git a/src/SMAPI/IAssetDataForMap.cs b/src/SMAPI/IAssetDataForMap.cs index bfaba9ba..47a33de8 100644 --- a/src/SMAPI/IAssetDataForMap.cs +++ b/src/SMAPI/IAssetDataForMap.cs @@ -13,6 +13,7 @@ namespace StardewModdingAPI /// The map from which to copy. /// The tile area within the source map to copy, or null for the entire source map size. This must be within the bounds of the map. /// The tile area within the target map to overwrite, or null to patch the whole map. The original content within this area will be erased. This must be within the bounds of the existing map. - void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null); + /// Indicates how the map should be patched. + void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay); } } diff --git a/src/SMAPI/PatchMapMode.cs b/src/SMAPI/PatchMapMode.cs new file mode 100644 index 00000000..1f1ac6a9 --- /dev/null +++ b/src/SMAPI/PatchMapMode.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI +{ + /// Indicates how a map should be patched. + public enum PatchMapMode + { + /// Replace matching tiles. Target tiles missing in the source area are kept as-is. + Overlay, + + /// Replace all tiles on layers that exist in the source map. + ReplaceByLayer, + + /// Replace all tiles with the source map. + Replace + } +} -- cgit