Esempio n. 1
0
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param>
        /// <param name="path">The path to the patch from the root content file.</param>
        /// <param name="assetName">The normalized asset name to intercept.</param>
        /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
        /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
        /// <param name="fromArea">The map area from which to read tiles.</param>
        /// <param name="patchMode">Indicates how the map should be patched.</param>
        /// <param name="toArea">The map area to overwrite.</param>
        /// <param name="mapProperties">The map properties to change when editing a map, if any.</param>
        /// <param name="textOperations">The text operations to apply to existing values.</param>
        /// <param name="mapTiles">The map tiles to change when editing a map.</param>
        /// <param name="updateRate">When the patch should be updated.</param>
        /// <param name="contentPack">The content pack which requested the patch.</param>
        /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="reflection">Simplifies access to private code.</param>
        /// <param name="normalizeAssetName">Normalize an asset name.</param>
        public EditMapPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMapMode patchMode, IEnumerable <EditMapPatchProperty> mapProperties, IEnumerable <EditMapPatchTile> mapTiles, IEnumerable <TextOperation> textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, IMonitor monitor, IReflectionHelper reflection, Func <string, string> normalizeAssetName)
            : base(
                indexPath: indexPath,
                path: path,
                type: PatchType.EditMap,
                assetName: assetName,
                fromAsset: fromAsset,
                conditions: conditions,
                updateRate: updateRate,
                contentPack: contentPack,
                parentPatch: parentPatch,
                normalizeAssetName: normalizeAssetName
                )
        {
            this.FromArea       = fromArea;
            this.ToArea         = toArea;
            this.PatchMode      = patchMode;
            this.MapProperties  = mapProperties?.ToArray() ?? new EditMapPatchProperty[0];
            this.MapTiles       = mapTiles?.ToArray() ?? new EditMapPatchTile[0];
            this.TextOperations = textOperations?.ToArray() ?? new TextOperation[0];
            this.Monitor        = monitor;
            this.Reflection     = reflection;

            this.Contextuals
            .Add(this.FromArea)
            .Add(this.ToArea)
            .Add(this.MapProperties)
            .Add(this.MapTiles)
            .Add(this.TextOperations);
        }
Esempio n. 2
0
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param>
        /// <param name="path">The path to the patch from the root content file.</param>
        /// <param name="assetName">The normalized asset name to intercept.</param>
        /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
        /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
        /// <param name="fromArea">The map area from which to read tiles.</param>
        /// <param name="patchMode">Indicates how the map should be patched.</param>
        /// <param name="toArea">The map area to overwrite.</param>
        /// <param name="mapProperties">The map properties to change when editing a map, if any.</param>
        /// <param name="mapTiles">The map tiles to change when editing a map.</param>
        /// <param name="addWarps">The warps to add to the location.</param>
        /// <param name="textOperations">The text operations to apply to existing values.</param>
        /// <param name="updateRate">When the patch should be updated.</param>
        /// <param name="contentPack">The content pack which requested the patch.</param>
        /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="parseAssetName">Parse an asset name.</param>
        public EditMapPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString?fromAsset, TokenRectangle?fromArea, TokenRectangle?toArea, PatchMapMode patchMode, IEnumerable <EditMapPatchProperty>?mapProperties, IEnumerable <EditMapPatchTile>?mapTiles, IEnumerable <IManagedTokenString>?addWarps, IEnumerable <ITextOperation>?textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch?parentPatch, IMonitor monitor, Func <string, IAssetName> parseAssetName)
            : base(
                indexPath: indexPath,
                path: path,
                type: PatchType.EditMap,
                assetName: assetName,
                fromAsset: fromAsset,
                conditions: conditions,
                updateRate: updateRate,
                contentPack: contentPack,
                parentPatch: parentPatch,
                parseAssetName: parseAssetName
                )
        {
            this.FromArea       = fromArea;
            this.ToArea         = toArea;
            this.PatchMode      = patchMode;
            this.MapProperties  = mapProperties?.ToArray() ?? Array.Empty <EditMapPatchProperty>();
            this.MapTiles       = mapTiles?.ToArray() ?? Array.Empty <EditMapPatchTile>();
            this.AddWarps       = addWarps?.Reverse().ToArray() ?? Array.Empty <IManagedTokenString>(); // reversing the warps allows later ones to 'overwrite' earlier ones, since the game checks them in the listed order
            this.TextOperations = textOperations?.ToArray() ?? Array.Empty <ITextOperation>();
            this.Monitor        = monitor;

            this.Contextuals
            .Add(this.FromArea)
            .Add(this.ToArea)
            .Add(this.MapProperties)
            .Add(this.MapTiles)
            .Add(this.AddWarps)
            .Add(this.TextOperations);
        }
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param>
        /// <param name="path">The path to the patch from the root content file.</param>
        /// <param name="assetName">The normalized asset name to intercept.</param>
        /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
        /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
        /// <param name="fromArea">The map area from which to read tiles.</param>
        /// <param name="patchMode">Indicates how the map should be patched.</param>
        /// <param name="toArea">The map area to overwrite.</param>
        /// <param name="mapProperties">The map properties to change when editing a map, if any.</param>
        /// <param name="mapTiles">The map tiles to change when editing a map.</param>
        /// <param name="addWarps">The warps to add to the location.</param>
        /// <param name="textOperations">The text operations to apply to existing values.</param>
        /// <param name="updateRate">When the patch should be updated.</param>
        /// <param name="contentPack">The content pack which requested the patch.</param>
        /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="reflection">Simplifies access to private code.</param>
        /// <param name="normalizeAssetName">Normalize an asset name.</param>
        public EditMapPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMapMode patchMode, IEnumerable <EditMapPatchProperty> mapProperties, IEnumerable <EditMapPatchTile> mapTiles, IEnumerable <IManagedTokenString> addWarps, IEnumerable <TextOperation> textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, IMonitor monitor, IReflectionHelper reflection, Func <string, string> normalizeAssetName)
            : base(
                indexPath: indexPath,
                path: path,
                type: PatchType.EditMap,
                assetName: assetName,
                fromAsset: fromAsset,
                conditions: conditions,
                updateRate: updateRate,
                contentPack: contentPack,
                parentPatch: parentPatch,
                normalizeAssetName: normalizeAssetName
                )
        {
            this.FromArea       = fromArea;
            this.ToArea         = toArea;
            this.PatchMode      = patchMode;
            this.MapProperties  = mapProperties?.ToArray() ?? new EditMapPatchProperty[0];
            this.MapTiles       = mapTiles?.ToArray() ?? new EditMapPatchTile[0];
            this.AddWarps       = addWarps?.Reverse().ToArray() ?? new IManagedTokenString[0]; // reversing the warps allows later ones to 'overwrite' earlier ones, since the game checks them in the listed order
            this.TextOperations = textOperations?.ToArray() ?? new TextOperation[0];
            this.Monitor        = monitor;
            this.Reflection     = reflection;

            this.Contextuals
            .Add(this.FromArea)
            .Add(this.ToArea)
            .Add(this.MapProperties)
            .Add(this.MapTiles)
            .Add(this.AddWarps)
            .Add(this.TextOperations);
        }
Esempio n. 4
0
        /// <summary>Copy layers, tiles, and tilesheets from another map onto the asset.</summary>
        /// <param name="asset">The asset being edited.</param>
        /// <param name="source">The map from which to copy.</param>
        /// <param name="patchMode">Indicates how the map should be patched.</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>
        /// <remarks>
        /// This is temporarily duplicated from SMAPI's <see cref="IAssetDataForMap"/>, to add map overlay support before the feature is added to SMAPI.
        /// </remarks>
        public void PatchMap(IAssetDataForMap asset, Map source, PatchMapMode patchMode, Rectangle?sourceArea = null, Rectangle?targetArea = null)
        {
            Map target = asset.Data;

            // get areas
            {
                Rectangle sourceBounds = this.GetMapArea(source);
                Rectangle targetBounds = this.GetMapArea(target);
                sourceArea ??= new Rectangle(0, 0, sourceBounds.Width, sourceBounds.Height);
                targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, targetBounds.Width), Math.Min(sourceArea.Value.Height, targetBounds.Height));

                // validate
                if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > sourceBounds.Width || sourceArea.Value.Bottom > sourceBounds.Height)
                {
                    throw new ArgumentOutOfRangeException(nameof(sourceArea), $"The source area ({sourceArea}) is outside the bounds of the source map ({sourceBounds}).");
                }
                if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > targetBounds.Width || targetArea.Value.Bottom > targetBounds.Height)
                {
                    throw new ArgumentOutOfRangeException(nameof(targetArea), $"The target area ({targetArea}) is outside the bounds of the target map ({targetBounds}).");
                }
                if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height)
                {
                    throw new InvalidOperationException($"The source area ({sourceArea}) and target area ({targetArea}) must be the same size.");
                }
            }

            // apply tilesheets
            IDictionary <TileSheet, TileSheet> tilesheetMap = new Dictionary <TileSheet, TileSheet>();

            foreach (TileSheet sourceSheet in source.TileSheets)
            {
                // copy tilesheets
                TileSheet targetSheet = target.GetTileSheet(sourceSheet.Id);
                if (targetSheet == null || this.NormalizeTilesheetPathForComparison(targetSheet.ImageSource) != this.NormalizeTilesheetPathForComparison(sourceSheet.ImageSource))
                {
                    // change ID if needed so new tilesheets are added after vanilla ones (to avoid errors in hardcoded game logic)
                    string id = sourceSheet.Id;
                    if (!id.StartsWith("z_", StringComparison.OrdinalIgnoreCase))
                    {
                        id = $"z_{id}";
                    }

                    // change ID if it conflicts with an existing tilesheet
                    if (target.GetTileSheet(id) != null)
                    {
                        int disambiguator = Enumerable.Range(2, int.MaxValue - 1).First(p => target.GetTileSheet($"{id}_{p}") == null);
                        id = $"{id}_{disambiguator}";
                    }

                    // add tilesheet
                    targetSheet = new TileSheet(id, target, sourceSheet.ImageSource, sourceSheet.SheetSize, sourceSheet.TileSize);
                    for (int i = 0, tileCount = sourceSheet.TileCount; i < tileCount; ++i)
                    {
                        targetSheet.TileIndexProperties[i].CopyFrom(sourceSheet.TileIndexProperties[i]);
                    }
                    target.AddTileSheet(targetSheet);
                }

                tilesheetMap[sourceSheet] = targetSheet;
            }

            // 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++)
                {
                    // calculate tile positions
                    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 = sourceToTargetLayers[sourceLayer];
                        if (targetLayer == null)
                        {
                            target.AddLayer(targetLayer       = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize));
                            sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id);
                        }

                        // copy layer properties
                        targetLayer.Properties.CopyFrom(sourceLayer.Properties);

                        // create new tile
                        Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y];
                        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;
                        }
                    }
                }
            }
        }