From 1e61309d3dce484d5b646b1a8d15c825beac813f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 11 Apr 2022 22:56:14 -0400 Subject: add IAssetDataForMap.ExtendMap --- src/SMAPI/Framework/Content/AssetDataForMap.cs | 54 +++++++++++++++++++++- src/SMAPI/Framework/Content/AssetDataForObject.cs | 23 +++++++-- src/SMAPI/Framework/ContentCoordinator.cs | 3 +- .../ContentManagers/BaseContentManager.cs | 4 ++ .../ContentManagers/GameContentManager.cs | 6 +-- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 16 ++++++- .../Framework/ModHelpers/GameContentHelper.cs | 16 ++++++- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 16 ++++++- src/SMAPI/Framework/SCore.cs | 14 +++--- src/SMAPI/IAssetDataForImage.cs | 4 +- src/SMAPI/IAssetDataForMap.cs | 7 +++ 11 files changed, 137 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 0425e195..93148277 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -4,17 +4,27 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; +using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using xTile; +using xTile.Dimensions; using xTile.Layers; using xTile.Tiles; +using Rectangle = Microsoft.Xna.Framework.Rectangle; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to image content being read from a data file. internal class AssetDataForMap : AssetData, IAssetDataForMap { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private readonly Reflector Reflection; + + /********* ** Public methods *********/ @@ -24,8 +34,12 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced) - : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } + /// Simplifies access to private code. + public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced, Reflector reflection) + : base(locale, assetName, data, getNormalizedPath, onDataReplaced) + { + this.Reflection = reflection; + } /// /// Derived from with a few changes: @@ -137,6 +151,42 @@ namespace StardewModdingAPI.Framework.Content } } + /// + public bool ExtendMap(Map map, int minWidth, int minHeight) + { + bool resized = false; + + // resize layers + foreach (Layer layer in map.Layers) + { + // check if resize needed + if (layer.LayerWidth >= minWidth && layer.LayerHeight >= minHeight) + continue; + resized = true; + + // build new tile matrix + int width = Math.Max(minWidth, layer.LayerWidth); + int height = Math.Max(minHeight, layer.LayerHeight); + Tile[,] tiles = new Tile[width, height]; + for (int x = 0; x < layer.LayerWidth; x++) + { + for (int y = 0; y < layer.LayerHeight; y++) + tiles[x, y] = layer.Tiles[x, y]; + } + + // update fields + this.Reflection.GetField(layer, "m_tiles").SetValue(tiles); + this.Reflection.GetField(layer, "m_tileArray").SetValue(new TileArray(layer, tiles)); + this.Reflection.GetField(layer, "m_layerSize").SetValue(new Size(width, height)); + } + + // resize map + if (resized) + this.Reflection.GetMethod(map, "UpdateDisplaySize").Invoke(); + + return resized; + } + /********* ** Private methods diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index 4a6df64b..bb3966b9 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; using xTile; namespace StardewModdingAPI.Framework.Content @@ -10,6 +11,13 @@ namespace StardewModdingAPI.Framework.Content /// Encapsulates access and changes to content being read from a data file. internal class AssetDataForObject : AssetData, IAssetData { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private readonly Reflector Reflection; + + /********* ** Public methods *********/ @@ -18,15 +26,20 @@ namespace StardewModdingAPI.Framework.Content /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. - public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath) - : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { } + /// Simplifies access to private code. + public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath, Reflector reflection) + : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) + { + this.Reflection = reflection; + } /// Construct an instance. /// The asset metadata. /// The content data being read. /// Normalizes an asset key to match the cache key. - public AssetDataForObject(IAssetInfo info, object data, Func getNormalizedPath) - : this(info.Locale, info.Name, data, getNormalizedPath) { } + /// Simplifies access to private code. + public AssetDataForObject(IAssetInfo info, object data, Func getNormalizedPath, Reflector reflection) + : this(info.Locale, info.Name, data, getNormalizedPath, reflection) { } /// public IAssetDataForDictionary AsDictionary() @@ -43,7 +56,7 @@ namespace StardewModdingAPI.Framework.Content /// public IAssetDataForMap AsMap() { - return new AssetDataForMap(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); + return new AssetDataForMap(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith, this.Reflection); } /// diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 81820b05..4e48b08c 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -727,7 +727,8 @@ namespace StardewModdingAPI.Framework locale: null, assetName: legacyName, data: asset.Data, - getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName + getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName, + reflection: this.Reflection ); } diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 4594d235..f1ccab48 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates monitoring and logging. protected readonly IMonitor Monitor; + /// Simplifies access to private code. + protected readonly Reflector Reflection; + /// Whether to enable more aggressive memory optimizations. protected readonly bool AggressiveMemoryOptimizations; @@ -90,6 +93,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Coordinator = coordinator ?? throw new ArgumentNullException(nameof(coordinator)); this.Cache = new ContentCache(this, reflection); this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); + this.Reflection = reflection; this.OnDisposing = onDisposing; this.IsNamespaced = isNamespaced; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index f4e1bda4..e494559d 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName); IAssetData asset = this.ApplyLoader(info) - ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName); + ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection); asset = this.ApplyEditors(info, asset); return (T)asset.Data; }); @@ -187,7 +187,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // return matched asset return this.TryFixAndValidateLoadedAsset(info, data, loader) - ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName) + ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection) : null; } @@ -197,7 +197,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The loaded asset. private IAssetData ApplyEditors(IAssetInfo info, IAssetData asset) { - IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName); + IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection); // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. { diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index e72e397e..12ef0439 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -10,6 +10,7 @@ using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers @@ -36,6 +37,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Accessors @@ -94,7 +98,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. - public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor) + /// Simplifies access to private code. + public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -104,6 +109,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, this.GameContentManager); this.ModName = modName; this.Monitor = monitor; + this.Reflection = reflection; } /// @@ -185,7 +191,13 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName, allowLocales: true/* no way to know if it's a game or mod asset here*/), data, this.NormalizeAssetName); + return new AssetDataForObject( + locale: this.CurrentLocale, + assetName: this.ContentCore.ParseAssetName(assetName, allowLocales: true/* no way to know if it's a game or mod asset here*/), + data: data, + getNormalizedPath: this.NormalizeAssetName, + reflection: this.Reflection + ); } diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 956bac7f..6d0c2f5f 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -5,6 +5,7 @@ using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers @@ -27,6 +28,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Accessors @@ -46,7 +50,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. - public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor) + /// Simplifies access to private code. + public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -55,6 +60,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); this.ModName = modName; this.Monitor = monitor; + this.Reflection = reflection; } /// @@ -119,7 +125,13 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName, allowLocales: true), data, key => this.ParseAssetName(key).Name); + return new AssetDataForObject( + locale: this.CurrentLocale, + assetName: this.ContentCore.ParseAssetName(assetName, allowLocales: true), + data: data, + getNormalizedPath: key => this.ParseAssetName(key).Name, + reflection: this.Reflection + ); } /// Get the underlying game content manager. diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 90064354..b149ed82 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Framework.ModHelpers @@ -27,6 +28,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// A case-insensitive lookup of relative paths within the . private readonly CaseInsensitivePathCache RelativePathCache; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Public methods @@ -38,7 +42,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The friendly mod name for use in errors. /// The game content manager used for map tilesheets not provided by the mod. /// A case-insensitive lookup of relative paths within the . - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache) + /// Simplifies access to private code. + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -47,6 +52,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); this.ModName = modName; this.RelativePathCache = relativePathCache; + this.Reflection = reflection; } /// @@ -83,7 +89,13 @@ namespace StardewModdingAPI.Framework.ModHelpers ? this.RelativePathCache.GetAssetName(relativePath) : $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath, allowLocales: false), data, key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name); + return new AssetDataForObject( + locale: this.ContentCore.GetLocale(), + assetName: this.ContentCore.ParseAssetName(relativePath, allowLocales: false), + data: data, + getNormalizedPath: key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name, + reflection: this.Reflection + ); } } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 1a58d84b..364a7632 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1754,8 +1754,8 @@ namespace StardewModdingAPI.Framework IManifest manifest = mod.Manifest; IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); - GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor); - IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); + GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper, relativePathCache); mod.SetMod(contentPack, monitor, translationHelper); @@ -1838,8 +1838,8 @@ namespace StardewModdingAPI.Framework CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(packDirPath); - GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor); - IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); + GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor, this.Reflection); + IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); TranslationHelper packTranslationHelper = new(packManifest.UniqueID, contentCore.GetLocale(), contentCore.Language); ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper, relativePathCache); @@ -1852,10 +1852,10 @@ namespace StardewModdingAPI.Framework ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); #pragma warning disable CS0612 // deprecated code - ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); #pragma warning restore CS0612 - GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor); - IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); + GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 388caa68..1416592e 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -23,8 +23,8 @@ namespace StardewModdingAPI void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); /// Extend the image if needed to fit the given size. Note that this is an expensive operation, creates a new texture instance, and that extending a spritesheet horizontally may cause game errors or bugs. - /// The minimum texture width. - /// The minimum texture height. + /// The minimum texture width in pixels. + /// The minimum texture height in pixels. /// Whether the texture was resized. bool ExtendImage(int minWidth, int minHeight); } diff --git a/src/SMAPI/IAssetDataForMap.cs b/src/SMAPI/IAssetDataForMap.cs index 89ee28f2..0b637baf 100644 --- a/src/SMAPI/IAssetDataForMap.cs +++ b/src/SMAPI/IAssetDataForMap.cs @@ -17,5 +17,12 @@ namespace StardewModdingAPI /// 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. /// Indicates how the map should be patched. void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay); + + /// Extend the map if needed to fit the given size. Note that this is an expensive operation and resizes the map in-place. + /// The map to resize. + /// The minimum map width in tiles. + /// The minimum map height in tiles. + /// Whether the map was resized. + bool ExtendMap(Map map, int minWidth, int minHeight); } } -- cgit