示例#1
0
        /// <summary>Apply the patch to a loaded asset.</summary>
        /// <typeparam name="T">The asset type.</typeparam>
        /// <param name="asset">The asset to edit.</param>
        public override void Edit <T>(IAssetData asset)
        {
            string errorPrefix = $"Can't apply map patch \"{this.LogName}\" to {this.TargetAsset}";

            // validate
            if (typeof(T) != typeof(Map))
            {
                this.Monitor.Log($"{errorPrefix}: this file isn't a map file (found {typeof(T)}).", LogLevel.Warn);
                return;
            }
            if (this.AppliesMapPatch && !this.FromAssetExists())
            {
                this.Monitor.Log($"{errorPrefix}: the {nameof(PatchConfig.FromFile)} file '{this.FromAsset}' doesn't exist.", LogLevel.Warn);
                return;
            }

            // get map
            IAssetDataForMap targetAsset = asset.AsMap();
            Map target = targetAsset.Data;

            // apply map area patch
            if (this.AppliesMapPatch)
            {
                Map source = this.ContentPack.Load <Map>(this.FromAsset);
                if (!this.TryApplyMapPatch(source, targetAsset, out string error))
                {
                    this.Monitor.Log($"{errorPrefix}: map patch couldn't be applied: {error}", LogLevel.Warn);
                }
            }

            // patch map tiles
            if (this.AppliesTilePatches)
            {
                int i = 0;
                foreach (EditMapPatchTile tilePatch in this.MapTiles)
                {
                    i++;
                    if (!this.TryApplyTile(target, tilePatch, out string error))
                    {
                        this.Monitor.Log($"{errorPrefix}: {nameof(PatchConfig.MapTiles)} > entry {i + 1} couldn't be applied: {error}", LogLevel.Warn);
                    }
                }
            }

            // patch map properties
            foreach (EditMapPatchProperty property in this.MapProperties)
            {
                string key   = property.Key.Value;
                string value = property.Value.Value;

                if (value == null)
                {
                    target.Properties.Remove(key);
                }
                else
                {
                    target.Properties[key] = value;
                }
            }
        }
示例#2
0
        /*********
        ** Private methods
        *********/
        /// <summary>Try to apply a map overlay patch.</summary>
        /// <param name="source">The source map to overlay.</param>
        /// <param name="targetAsset">The target map to overlay.</param>
        /// <param name="error">An error indicating why applying the patch failed, if applicable.</param>
        /// <returns>Returns whether applying the patch succeeded.</returns>
        private bool TryApplyMapPatch(Map source, IAssetDataForMap targetAsset, out string error)
        {
            Map target = targetAsset.Data;

            // read data
            Rectangle mapBounds = this.GetMapArea(source);
            if (!this.TryReadArea(this.FromArea, 0, 0, mapBounds.Width, mapBounds.Height, out Rectangle sourceArea, out error))
                return this.Fail($"the source area is invalid: {error}.", out error);
            if (!this.TryReadArea(this.ToArea, 0, 0, sourceArea.Width, sourceArea.Height, out Rectangle targetArea, out error))
                return this.Fail($"the target area is invalid: {error}.", out error);

            // validate area values
            string sourceAreaLabel = this.FromArea != null ? $"{nameof(this.FromArea)}" : "source map";
            string targetAreaLabel = this.ToArea != null ? $"{nameof(this.ToArea)}" : "target map";
            Point sourceMapSize = new Point(source.Layers.Max(p => p.LayerWidth), source.Layers.Max(p => p.LayerHeight));

            if (!this.TryValidateArea(sourceArea, sourceMapSize, "source", out error))
                return this.Fail(error, out error);
            if (!this.TryValidateArea(targetArea, null, "target", out error))
                return this.Fail(error, out error);
            if (sourceArea.Width != targetArea.Width || sourceArea.Height != targetArea.Height)
                return this.Fail($"{sourceAreaLabel} size (Width:{sourceArea.Width}, Height:{sourceArea.Height}) doesn't match {targetAreaLabel} size (Width:{targetArea.Width}, Height:{targetArea.Height}).", out error);

            // apply source map
            this.ExtendMap(target, minWidth: targetArea.Right, minHeight: targetArea.Bottom);
            this.PatchMap(targetAsset, source: source, patchMode: this.PatchMode, sourceArea: sourceArea, targetArea: targetArea);

            error = null;
            return true;
        }
示例#3
0
        /// <summary>Edit a matched asset.</summary>
        /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
        public void Edit <T>(IAssetData asset)
        {
            Monitor.Log("Editing asset: " + asset.AssetName);

            string mapName = asset.AssetName.Replace("Maps/", "").Replace("Maps\\", "");

            if (false && changeLocations.ContainsKey(mapName))
            {
                IAssetDataForMap map = asset.AsMap();
                for (int x = 0; x < map.Data.Layers[0].LayerWidth; x++)
                {
                    for (int y = 0; y < map.Data.Layers[0].LayerHeight; y++)
                    {
                        if (SwimUtils.doesTileHaveProperty(map.Data, x, y, "Water", "Back") != null)
                        {
                            Tile tile = map.Data.GetLayer("Back").PickTile(new Location(x, y) * Game1.tileSize, Game1.viewport.Size);
                            if (tile != null && (((mapName == "Beach" || mapName == "UnderwaterBeach") && x > 58 && x < 61 && y > 11 && y < 15) || mapName != "Beach"))
                            {
                                if (tile.TileIndexProperties.ContainsKey("Passable"))
                                {
                                    tile.TileIndexProperties.Remove("Passable");
                                }
                            }
                            tile = map.Data.GetLayer("Front").PickTile(new Location(x, y) * Game1.tileSize, Game1.viewport.Size);
                            if (tile != null)
                            {
                                if (tile.TileIndexProperties.ContainsKey("Passable"))
                                {
                                    //tile.TileIndexProperties.Remove("Passable");
                                }
                            }
                            if (map.Data.GetLayer("AlwaysFront") != null)
                            {
                                tile = map.Data.GetLayer("AlwaysFront").PickTile(new Location(x, y) * Game1.tileSize, Game1.viewport.Size);
                                if (tile != null)
                                {
                                    if (tile.TileIndexProperties.ContainsKey("Passable"))
                                    {
                                        //tile.TileIndexProperties.Remove("Passable");
                                    }
                                }
                            }
                            tile = map.Data.GetLayer("Buildings").PickTile(new Location(x, y) * Game1.tileSize, Game1.viewport.Size);
                            if (tile != null)
                            {
                                if (
                                    ((mapName == "Beach" || mapName == "UnderwaterBeach") && x > 58 && x < 61 && y > 11 && y < 15) ||
                                    (mapName != "Beach" && mapName != "UnderwaterBeach" &&
                                     ((tile.TileIndex > 1292 && tile.TileIndex < 1297) || (tile.TileIndex > 1317 && tile.TileIndex < 1322) ||
                                      (tile.TileIndex % 25 > 17 && tile.TileIndex / 25 < 53 && tile.TileIndex / 25 > 48) ||
                                      (tile.TileIndex % 25 > 1 && tile.TileIndex % 25 < 7 && tile.TileIndex / 25 < 53 && tile.TileIndex / 25 > 48) ||
                                      (tile.TileIndex % 25 > 11 && tile.TileIndex / 25 < 51 && tile.TileIndex / 25 > 48) ||
                                      (tile.TileIndex % 25 > 10 && tile.TileIndex % 25 < 14 && tile.TileIndex / 25 < 49 && tile.TileIndex / 25 > 46) ||
                                      tile.TileIndex == 734 || tile.TileIndex == 759 ||
                                      tile.TileIndex == 628 || tile.TileIndex == 629 ||
                                      (mapName == "Forest" && x == 119 && ((y > 42 && y < 48) || (y > 104 && y < 119)))

                                     )
                                    )
                                    )
                                {
                                    if (tile.TileIndexProperties.ContainsKey("Passable"))
                                    {
                                        tile.TileIndexProperties["Passable"] = "T";
                                    }
                                    else
                                    {
                                        tile.TileIndexProperties.Add("Passable", "T");
                                    }
                                }
                                else if (mapName == "Beach" && tile.TileIndex == 76)
                                {
                                    if (x > 58 && x < 61 && y > 11 && y < 15)
                                    {
                                        Game1.getLocationFromName(mapName).removeTile(x, y, "Buildings");
                                    }
                                    if (tile.TileIndexProperties.ContainsKey("Passable"))
                                    {
                                        tile.TileIndexProperties.Remove("Passable");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
示例#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;
                        }
                    }
                }
            }
        }
示例#5
0
 /// <summary>Edit a matched asset.</summary>
 /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
 public void Edit <T>(IAssetData asset)
 {
     IAssetDataForMap mapAsset = asset.AsMap();
     Map map = mapAsset.Data;
 }
示例#6
0
        /// <inheritdoc />
        public override void Edit <T>(IAssetData asset)
        {
            // validate
            if (typeof(T) != typeof(Map))
            {
                this.WarnForPatch($"this file isn't a map file (found {typeof(T)}).");
                return;
            }
            if (this.AppliesMapPatch && !this.FromAssetExists())
            {
                this.WarnForPatch($"the {nameof(PatchConfig.FromFile)} file '{this.FromAsset}' doesn't exist.");
                return;
            }

            // get map
            IAssetDataForMap targetAsset = asset.AsMap();
            Map target = targetAsset.Data;

            // apply map area patch
            if (this.AppliesMapPatch)
            {
                Map source = this.ContentPack.ModContent.Load <Map>(this.FromAsset !);
                if (!this.TryApplyMapPatch(source, targetAsset, out string?error))
                {
                    this.WarnForPatch($"map patch couldn't be applied: {error}");
                }
            }

            // patch map tiles
            if (this.AppliesTilePatches)
            {
                int i = 0;
                foreach (EditMapPatchTile tilePatch in this.MapTiles)
                {
                    i++;
                    if (!this.TryApplyTile(target, tilePatch, out string?error))
                    {
                        this.WarnForPatch($"{nameof(PatchConfig.MapTiles)} > entry {i} couldn't be applied: {error}");
                    }
                }
            }

            // patch map properties
            foreach (EditMapPatchProperty property in this.MapProperties)
            {
                string key   = property.Key.Value !;
                string?value = property.Value?.Value;

                if (value == null)
                {
                    target.Properties.Remove(key);
                }
                else
                {
                    target.Properties[key] = value;
                }
            }

            // apply map warps
            if (this.AddWarps.Any())
            {
                this.ApplyWarps(target, out IDictionary <string, string> errors);
                foreach ((string warp, string error) in errors)
                {
                    this.WarnForPatch($"{nameof(PatchConfig.AddWarps)} > warp '{warp}' couldn't be applied: {error}");
                }
            }

            // apply text operations
            for (int i = 0; i < this.TextOperations.Length; i++)
            {
                if (!this.TryApplyTextOperation(target, this.TextOperations[i], out string?error))
                {
                    this.WarnForPatch($"{nameof(PatchConfig.TextOperations)} > entry {i} couldn't be applied: {error}");
                }
            }
        }
示例#7
0
        /*********
        ** Private methods
        *********/
        /// <inheritdoc cref="IContentEvents.AssetRequested"/>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event data.</param>
        private void OnAssetRequested(object?sender, AssetRequestedEventArgs e)
        {
            // add farm type
            if (e.NameWithoutLocale.IsEquivalentTo("Data/AdditionalFarms"))
            {
                e.Edit(editor =>
                {
                    var data = editor.GetData <List <ModFarmType> >();
                    data.Add(new()
                    {
                        ID = this.ModManifest.UniqueID,
                        TooltipStringPath = "Strings/UI:Pathoschild_BeachFarm_Description",
                        MapName           = "Pathoschild_SmallBeachFarm"
                    });
                });
            }

            // add farm description
            else if (e.NameWithoutLocale.IsEquivalentTo("Strings/UI"))
            {
                e.Edit(editor =>
                {
                    var data = editor.AsDictionary <string, string>().Data;
                    data["Pathoschild_BeachFarm_Description"] = $"{I18n.Farm_Name()}_{I18n.Farm_Description()}";
                });
            }

            // load map
            else if (e.NameWithoutLocale.IsEquivalentTo("Maps/Pathoschild_SmallBeachFarm"))
            {
                e.LoadFrom(
                    () =>
                {
                    // load map
                    Map map = this.Helper.ModContent.Load <Map>("assets/farm.tmx");
                    IAssetDataForMap editor    = this.Helper.ModContent.GetPatchHelper(map).AsMap();
                    TileSheet outdoorTilesheet = map.GetTileSheet("untitled tile sheet");
                    Layer buildingsLayer       = map.GetLayer("Buildings");
                    Layer backLayer            = map.GetLayer("Back");

                    // add islands
                    if (this.Config.EnableIslands)
                    {
                        Map islands = this.Helper.ModContent.Load <Map>("assets/overlay_islands.tmx");
                        Size size   = islands.GetSizeInTiles();

                        editor.PatchMap(source: islands, targetArea: new Rectangle(0, 26, size.Width, size.Height));
                    }

                    // add campfire
                    if (this.Config.AddCampfire)
                    {
                        buildingsLayer.Tiles[65, 23] = new StaticTile(buildingsLayer, map.GetTileSheet("zbeach"), BlendMode.Alpha, 157); // driftwood pile
                        buildingsLayer.Tiles[64, 22] = new StaticTile(buildingsLayer, outdoorTilesheet, BlendMode.Alpha, 242);           // campfire
                    }

                    // remove shipping bin path
                    if (!this.Config.ShippingBinPath)
                    {
                        for (int x = 71; x <= 72; x++)
                        {
                            for (int y = 14; y <= 15; y++)
                            {
                                backLayer.Tiles[x, y] = new StaticTile(backLayer, outdoorTilesheet, BlendMode.Alpha, 175);     // grass tile
                            }
                        }
                    }

                    // add fishing pier
                    if (this.Config.AddFishingPier)
                    {
                        // load overlay
                        Map pier  = this.Helper.ModContent.Load <Map>("assets/overlay_pier.tmx");
                        Size size = pier.GetSizeInTiles();

                        // get target position
                        Point position = this.Config.CustomFishingPierPosition;
                        if (position == Point.Zero)
                        {
                            position = new Point(70, 26);
                        }

                        // remove building tiles which block movement on the pier
                        {
                            var pierBack = pier.GetLayer("Back");
                            for (int x = 0; x < size.Width; x++)
                            {
                                for (int y = 0; y < size.Height; y++)
                                {
                                    if (pierBack.Tiles[x, y] is not null)
                                    {
                                        buildingsLayer.Tiles[position.X + x, position.Y + y] = null;
                                    }
                                }
                            }
                        }

                        // apply overlay
                        editor.PatchMap(source: pier, targetArea: new Rectangle(position.X, position.Y, size.Width, size.Height));
                    }

                    // apply tilesheet recolors
                    foreach (TileSheet tilesheet in map.TileSheets)
                    {
                        IAssetName imageSource = this.Helper.GameContent.ParseAssetName(tilesheet.ImageSource);
                        if (imageSource.StartsWith($"{this.TilesheetsPath}/_default/"))
                        {
                            tilesheet.ImageSource = PathUtilities.NormalizeAssetName($"{this.FakeAssetPrefix}/{Path.GetFileNameWithoutExtension(tilesheet.ImageSource)}");
                        }
                    }

                    return(map);
                },
                    AssetLoadPriority.Exclusive
                    );
            }

            // load tilesheet
            else if (e.NameWithoutLocale.StartsWith(this.FakeAssetPrefix))
            {
                e.LoadFrom(
                    () =>
                {
                    string filename = Path.GetFileName(e.NameWithoutLocale.Name);
                    if (!Path.HasExtension(filename))
                    {
                        filename += ".png";
                    }

                    // get relative path to load
                    string?relativePath = new DirectoryInfo(this.GetFullPath(this.TilesheetsPath))
                                          .EnumerateDirectories()
                                          .FirstOrDefault(p => p.Name != "_default" && this.Helper.ModRegistry.IsLoaded(p.Name))
                                          ?.Name;
                    relativePath = Path.Combine(this.TilesheetsPath, relativePath ?? "_default", filename);

                    // load asset
                    Texture2D tilesheet = this.Helper.ModContent.Load <Texture2D>(relativePath);
                    return(tilesheet);
                },
                    AssetLoadPriority.Exclusive
                    );
            }
        }