diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | src/SMAPI/Framework/Content/AssetDataForMap.cs | 90 | ||||
-rw-r--r-- | src/SMAPI/IAssetDataForMap.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/PatchMapMode.cs | 15 |
4 files changed, 73 insertions, 36 deletions
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) { } /// <inheritdoc /> - /// <remarks>Derived from <see cref="StardewValley.GameLocation.ApplyMapOverride"/> with a few changes: + /// <remarks>Derived from <see cref="GameLocation.ApplyMapOverride(Map,string,Rectangle?,Rectangle?)"/> 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. /// </remarks> - 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<Layer, Layer> layerMap = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id)); + // get target layers + IDictionary<Layer, Layer> sourceToTargetLayers = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id)); + HashSet<Layer> orphanedTargetLayers = new HashSet<Layer>(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 *********/ + /// <summary>Create a new tile for the target map.</summary> + /// <param name="sourceTile">The source tile to copy.</param> + /// <param name="targetLayer">The target layer.</param> + /// <param name="targetSheet">The target tilesheet.</param> + 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; + } + } /// <summary>Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path.</summary> /// <param name="path">The path to normalize.</param> 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 /// <param name="source">The map from which to copy.</param> /// <param name="sourceArea">The tile area within the source map to copy, or <c>null</c> for the entire source map size. This must be within the bounds of the <paramref name="source"/> map.</param> /// <param name="targetArea">The tile area within the target map to overwrite, or <c>null</c> to patch the whole map. The original content within this area will be erased. This must be within the bounds of the existing map.</param> - void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null); + /// <param name="patchMode">Indicates how the map should be patched.</param> + 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 +{ + /// <summary>Indicates how a map should be patched.</summary> + public enum PatchMapMode + { + /// <summary>Replace matching tiles. Target tiles missing in the source area are kept as-is.</summary> + Overlay, + + /// <summary>Replace all tiles on layers that exist in the source map.</summary> + ReplaceByLayer, + + /// <summary>Replace all tiles with the source map.</summary> + Replace + } +} |