add map overlay patches
This commit is contained in:
parent
cb378a1c55
commit
fe675540b5
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
Tile newTile = sourceTile != null
|
||||
? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet])
|
||||
: null;
|
||||
newTile?.Properties.CopyFrom(sourceTile.Properties);
|
||||
|
||||
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;
|
||||
// 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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue