summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md1
-rw-r--r--src/SMAPI.Tests/Core/ModResolverTests.cs29
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs4
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs10
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs32
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs10
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs28
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs14
-rw-r--r--src/SMAPI.Toolkit/Properties/AssemblyInfo.cs1
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/Manifest.cs2
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForMap.cs54
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForObject.cs23
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs3
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs6
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs16
-rw-r--r--src/SMAPI/Framework/ModHelpers/GameContentHelper.cs16
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModContentHelper.cs16
-rw-r--r--src/SMAPI/Framework/SCore.cs14
-rw-r--r--src/SMAPI/IAssetDataForImage.cs4
-rw-r--r--src/SMAPI/IAssetDataForMap.cs7
21 files changed, 211 insertions, 83 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 0687b888..7379cba3 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -45,6 +45,7 @@ the C# mod that loads them is updated.
_This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._
* Mod files loaded through SMAPI APIs (including `helper.Content.Load`) are now case-insensitive, even on Linux.
* Other improvements:
+ * Added `IAssetDataForImage.ExtendMap` to resize maps in asset editors.
* Added [command-line arguments](technical/smapi.md#command-line-arguments) to toggle developer mode (thanks to Tondorian!).
* Added `IContentPack.ModContent` property.
* Added `Constants.ContentPath`.
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs
index e1b56559..b8649a7d 100644
--- a/src/SMAPI.Tests/Core/ModResolverTests.cs
+++ b/src/SMAPI.Tests/Core/ModResolverTests.cs
@@ -145,7 +145,7 @@ namespace SMAPI.Tests.Core
{
// arrange
Mock<IModMetadata> mock = this.GetMetadata("Mod A", Array.Empty<string>(), allowStatusChange: true);
- this.SetupMetadataForValidation(mock, new ModDataRecordVersionedFields
+ this.SetupMetadataForValidation(mock, new ModDataRecordVersionedFields(this.GetModDataRecord())
{
Status = ModStatus.AssumeBroken
});
@@ -216,9 +216,9 @@ namespace SMAPI.Tests.Core
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll!), "");
// arrange
- Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> mock = new(MockBehavior.Strict);
mock.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
- mock.Setup(p => p.DataRecord).Returns(() => null);
+ mock.Setup(p => p.DataRecord).Returns(this.GetModDataRecordVersionedFields());
mock.Setup(p => p.Manifest).Returns(manifest);
mock.Setup(p => p.DirectoryPath).Returns(modFolder);
@@ -265,7 +265,7 @@ namespace SMAPI.Tests.Core
public void ProcessDependencies_Skips_Failed()
{
// arrange
- Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> mock = new(MockBehavior.Strict);
mock.Setup(p => p.Status).Returns(ModMetadataStatus.Failed);
// act
@@ -380,7 +380,7 @@ namespace SMAPI.Tests.Core
Mock<IModMetadata> modA = this.GetMetadata("Mod A");
Mock<IModMetadata> modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" });
Mock<IModMetadata> modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B" }, allowStatusChange: true);
- Mock<IModMetadata> modD = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> modD = new(MockBehavior.Strict);
modD.Setup(p => p.Manifest).Returns<IManifest>(null);
modD.Setup(p => p.Status).Returns(ModMetadataStatus.Failed);
@@ -516,8 +516,8 @@ namespace SMAPI.Tests.Core
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
private Mock<IModMetadata> GetMetadata(IManifest manifest, bool allowStatusChange = false)
{
- Mock<IModMetadata> mod = new Mock<IModMetadata>(MockBehavior.Strict);
- mod.Setup(p => p.DataRecord).Returns(() => null);
+ Mock<IModMetadata> mod = new(MockBehavior.Strict);
+ mod.Setup(p => p.DataRecord).Returns(this.GetModDataRecordVersionedFields());
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
mod.Setup(p => p.DisplayName).Returns(manifest.UniqueID);
mod.Setup(p => p.Manifest).Returns(manifest);
@@ -538,11 +538,22 @@ namespace SMAPI.Tests.Core
private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields? modRecord = null)
{
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
- mod.Setup(p => p.DataRecord).Returns(() => null);
mod.Setup(p => p.Manifest).Returns(this.GetManifest());
mod.Setup(p => p.DirectoryPath).Returns(Path.GetTempPath());
- mod.Setup(p => p.DataRecord).Returns(modRecord);
+ mod.Setup(p => p.DataRecord).Returns(modRecord ?? this.GetModDataRecordVersionedFields());
mod.Setup(p => p.GetUpdateKeys(It.IsAny<bool>())).Returns(Enumerable.Empty<UpdateKey>());
}
+
+ /// <summary>Generate a default mod data record.</summary>
+ private ModDataRecord GetModDataRecord()
+ {
+ return new("Default Display Name", new ModDataModel("Sample ID", null, ModWarning.None));
+ }
+
+ /// <summary>Generate a default mod data versioned fields instance.</summary>
+ private ModDataRecordVersionedFields GetModDataRecordVersionedFields()
+ {
+ return new ModDataRecordVersionedFields(this.GetModDataRecord());
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs
index 3fa70615..da678ac9 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
namespace StardewModdingAPI.Toolkit.Framework.ModData
@@ -11,6 +9,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
** Accessors
********/
/// <summary>Extra metadata about mods.</summary>
- public IDictionary<string, ModDataModel> ModData { get; set; }
+ public IDictionary<string, ModDataModel> ModData { get; } = new Dictionary<string, ModDataModel>();
}
}
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs
index 46cb81e1..9674d283 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Linq;
namespace StardewModdingAPI.Toolkit.Framework.ModData
@@ -20,10 +18,10 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
public bool IsDefault { get; }
/// <summary>The lowest version in the range, or <c>null</c> for all past versions.</summary>
- public ISemanticVersion LowerVersion { get; }
+ public ISemanticVersion? LowerVersion { get; }
/// <summary>The highest version in the range, or <c>null</c> for all future versions.</summary>
- public ISemanticVersion UpperVersion { get; }
+ public ISemanticVersion? UpperVersion { get; }
/*********
@@ -35,7 +33,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <param name="isDefault">Whether this field should only be applied if it's not already set.</param>
/// <param name="lowerVersion">The lowest version in the range, or <c>null</c> for all past versions.</param>
/// <param name="upperVersion">The highest version in the range, or <c>null</c> for all future versions.</param>
- public ModDataField(ModDataFieldKey key, string value, bool isDefault, ISemanticVersion lowerVersion, ISemanticVersion upperVersion)
+ public ModDataField(ModDataFieldKey key, string value, bool isDefault, ISemanticVersion? lowerVersion, ISemanticVersion? upperVersion)
{
this.Key = key;
this.Value = value;
@@ -46,7 +44,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <summary>Get whether this data field applies for the given manifest.</summary>
/// <param name="manifest">The mod manifest.</param>
- public bool IsMatch(IManifest manifest)
+ public bool IsMatch(IManifest? manifest)
{
return
manifest?.Version != null // ignore invalid manifest
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs
index 4d96a555..5912fb87 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -16,7 +14,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
** Accessors
*********/
/// <summary>The mod's current unique ID.</summary>
- public string ID { get; set; }
+ public string ID { get; }
/// <summary>The former mod IDs (if any).</summary>
/// <remarks>
@@ -25,14 +23,14 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// ID, if any. If the mod's ID changed over time, multiple variants can be separated by the
/// <c>|</c> character.
/// </remarks>
- public string FormerIDs { get; set; }
+ public string? FormerIDs { get; }
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
- public ModWarning SuppressWarnings { get; set; }
+ public ModWarning SuppressWarnings { get; }
/// <summary>This field stores properties that aren't mapped to another field before they're parsed into <see cref="Fields"/>.</summary>
[JsonExtensionData]
- public IDictionary<string, JToken> ExtensionData { get; set; }
+ public IDictionary<string, JToken> ExtensionData { get; } = new Dictionary<string, JToken>();
/// <summary>The versioned field data.</summary>
/// <remarks>
@@ -52,6 +50,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/*********
** Public methods
*********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="id">The mod's current unique ID.</param>
+ /// <param name="formerIds">The former mod IDs (if any).</param>
+ /// <param name="suppressWarnings">The mod warnings to suppress, even if they'd normally be shown.</param>
+ public ModDataModel(string id, string? formerIds, ModWarning suppressWarnings)
+ {
+ this.ID = id;
+ this.FormerIDs = formerIds;
+ this.SuppressWarnings = suppressWarnings;
+ }
+
/// <summary>Get a parsed representation of the <see cref="Fields"/>.</summary>
public IEnumerable<ModDataField> GetFields()
{
@@ -61,8 +70,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
string packedKey = pair.Key;
string value = pair.Value;
bool isDefault = false;
- ISemanticVersion lowerVersion = null;
- ISemanticVersion upperVersion = null;
+ ISemanticVersion? lowerVersion = null;
+ ISemanticVersion? upperVersion = null;
// parse
string[] parts = packedKey.Split('|').Select(p => p.Trim()).ToArray();
@@ -113,11 +122,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
- if (this.ExtensionData != null)
- {
- this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString());
- this.ExtensionData = null;
- }
+ this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString());
+ this.ExtensionData.Clear();
}
}
}
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs
index 4c09e1ba..e9ece387 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -22,7 +20,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
public string[] FormerIDs { get; }
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
- public ModWarning SuppressWarnings { get; set; }
+ public ModWarning SuppressWarnings { get; }
/// <summary>The versioned field data.</summary>
public ModDataField[] Fields { get; }
@@ -72,9 +70,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
}
/// <summary>Get the default update key for this mod, if any.</summary>
- public string GetDefaultUpdateKey()
+ public string? GetDefaultUpdateKey()
{
- string updateKey = this.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value;
+ string? updateKey = this.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value;
return !string.IsNullOrWhiteSpace(updateKey)
? updateKey
: null;
@@ -84,7 +82,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <param name="manifest">The manifest to match.</param>
public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest)
{
- ModDataRecordVersionedFields parsed = new() { DisplayName = this.DisplayName, DataRecord = this };
+ ModDataRecordVersionedFields parsed = new(this);
foreach (ModDataField field in this.Fields.Where(field => field.IsMatch(manifest)))
{
switch (field.Key)
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs
index b599b343..65fa424e 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
namespace StardewModdingAPI.Toolkit.Framework.ModData
{
/// <summary>The versioned fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary>
@@ -9,24 +7,32 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
** Accessors
*********/
/// <summary>The underlying data record.</summary>
- public ModDataRecord DataRecord { get; set; }
-
- /// <summary>The default mod name to display when the name isn't available (e.g. during dependency checks).</summary>
- public string DisplayName { get; set; }
+ public ModDataRecord DataRecord { get; }
- /// <summary>The update key to apply.</summary>
- public string UpdateKey { get; set; }
+ /// <summary>The update key to apply (if any).</summary>
+ public string? UpdateKey { get; set; }
/// <summary>The predefined compatibility status.</summary>
public ModStatus Status { get; set; } = ModStatus.None;
/// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary>
- public string StatusReasonPhrase { get; set; }
+ public string? StatusReasonPhrase { get; set; }
/// <summary>Technical details shown in TRACE logs for the <see cref="Status"/>, or <c>null</c> to omit it.</summary>
- public string StatusReasonDetails { get; set; }
+ public string? StatusReasonDetails { get; set; }
/// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary>
- public ISemanticVersion StatusUpperVersion { get; set; }
+ public ISemanticVersion? StatusUpperVersion { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="dataRecord">The underlying data record.</param>
+ public ModDataRecordVersionedFields(ModDataRecord dataRecord)
+ {
+ this.DataRecord = dataRecord;
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs
index a5237334..168b8aac 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -16,7 +14,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
private readonly ModDataRecord[] Records;
/// <summary>Get an update URL for an update key (if valid).</summary>
- private readonly Func<string, string> GetUpdateUrl;
+ private readonly Func<string, string?> GetUpdateUrl;
/*********
@@ -29,7 +27,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <summary>Construct an instance.</summary>
/// <param name="records">The underlying mod data records indexed by default display name.</param>
/// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param>
- public ModDatabase(IEnumerable<ModDataRecord> records, Func<string, string> getUpdateUrl)
+ public ModDatabase(IEnumerable<ModDataRecord> records, Func<string, string?> getUpdateUrl)
{
this.Records = records.ToArray();
this.GetUpdateUrl = getUpdateUrl;
@@ -43,7 +41,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <summary>Get a mod data record.</summary>
/// <param name="modID">The unique mod ID.</param>
- public ModDataRecord Get(string modID)
+ public ModDataRecord? Get(string? modID)
{
return !string.IsNullOrWhiteSpace(modID)
? this.Records.FirstOrDefault(p => p.HasID(modID))
@@ -52,11 +50,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <summary>Get the mod page URL for a mod (if available).</summary>
/// <param name="id">The unique mod ID.</param>
- public string GetModPageUrlFor(string id)
+ public string? GetModPageUrlFor(string? id)
{
// get update key
- ModDataRecord record = this.Get(id);
- ModDataField updateKeyField = record?.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey);
+ ModDataRecord? record = this.Get(id);
+ ModDataField? updateKeyField = record?.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey);
if (updateKeyField == null)
return null;
diff --git a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
index eede4562..6f5dffbe 100644
--- a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
+++ b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
@@ -2,4 +2,5 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StardewModdingAPI")]
[assembly: InternalsVisibleTo("SMAPI.Installer")]
+[assembly: InternalsVisibleTo("SMAPI.Tests")]
[assembly: InternalsVisibleTo("SMAPI.Web")]
diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
index 01010602..da3ad608 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
@@ -46,7 +46,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData]
- public IDictionary<string, object> ExtraFields { get; set; } = new Dictionary<string, object>();
+ public IDictionary<string, object> ExtraFields { get; } = new Dictionary<string, object>();
/*********
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,11 +4,14 @@ 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
{
@@ -16,6 +19,13 @@ namespace StardewModdingAPI.Framework.Content
internal class AssetDataForMap : AssetData<Map>, IAssetDataForMap
{
/*********
+ ** Fields
+ *********/
+ /// <summary>Simplifies access to private code.</summary>
+ private readonly Reflector Reflection;
+
+
+ /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -24,8 +34,12 @@ namespace StardewModdingAPI.Framework.Content
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
- public AssetDataForMap(string locale, IAssetName assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced)
- : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
+ /// <param name="reflection">Simplifies access to private code.</param>
+ public AssetDataForMap(string locale, IAssetName assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced, Reflector reflection)
+ : base(locale, assetName, data, getNormalizedPath, onDataReplaced)
+ {
+ this.Reflection = reflection;
+ }
/// <inheritdoc />
/// <remarks>Derived from <see cref="GameLocation.ApplyMapOverride(Map,string,Rectangle?,Rectangle?)"/> with a few changes:
@@ -137,6 +151,42 @@ namespace StardewModdingAPI.Framework.Content
}
}
+ /// <inheritdoc />
+ 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<Tile[,]>(layer, "m_tiles").SetValue(tiles);
+ this.Reflection.GetField<TileArray>(layer, "m_tileArray").SetValue(new TileArray(layer, tiles));
+ this.Reflection.GetField<Size>(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
@@ -11,6 +12,13 @@ namespace StardewModdingAPI.Framework.Content
internal class AssetDataForObject : AssetData<object>, IAssetData
{
/*********
+ ** Fields
+ *********/
+ /// <summary>Simplifies access to private code.</summary>
+ private readonly Reflector Reflection;
+
+
+ /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -18,15 +26,20 @@ namespace StardewModdingAPI.Framework.Content
/// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
- public AssetDataForObject(string locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath)
- : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { }
+ /// <param name="reflection">Simplifies access to private code.</param>
+ public AssetDataForObject(string locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath, Reflector reflection)
+ : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null)
+ {
+ this.Reflection = reflection;
+ }
/// <summary>Construct an instance.</summary>
/// <param name="info">The asset metadata.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
- public AssetDataForObject(IAssetInfo info, object data, Func<string, string> getNormalizedPath)
- : this(info.Locale, info.Name, data, getNormalizedPath) { }
+ /// <param name="reflection">Simplifies access to private code.</param>
+ public AssetDataForObject(IAssetInfo info, object data, Func<string, string> getNormalizedPath, Reflector reflection)
+ : this(info.Locale, info.Name, data, getNormalizedPath, reflection) { }
/// <inheritdoc />
public IAssetDataForDictionary<TKey, TValue> AsDictionary<TKey, TValue>()
@@ -43,7 +56,7 @@ namespace StardewModdingAPI.Framework.Content
/// <inheritdoc />
public IAssetDataForMap AsMap()
{
- return new AssetDataForMap(this.Locale, this.Name, this.GetData<Map>(), this.GetNormalizedPath, this.ReplaceWith);
+ return new AssetDataForMap(this.Locale, this.Name, this.GetData<Map>(), this.GetNormalizedPath, this.ReplaceWith, this.Reflection);
}
/// <inheritdoc />
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
/// <summary>Encapsulates monitoring and logging.</summary>
protected readonly IMonitor Monitor;
+ /// <summary>Simplifies access to private code.</summary>
+ protected readonly Reflector Reflection;
+
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
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<T>(info)
- ?? new AssetDataForObject(info, this.RawLoad<T>(assetName, useCache), this.AssertAndNormalizeAssetName);
+ ?? new AssetDataForObject(info, this.RawLoad<T>(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection);
asset = this.ApplyEditors<T>(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
/// <param name="asset">The loaded asset.</param>
private IAssetData ApplyEditors<T>(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
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
+ /// <summary>Simplifies access to private code.</summary>
+ private readonly Reflector Reflection;
+
/*********
** Accessors
@@ -94,7 +98,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <param name="modID">The unique ID of the relevant mod.</param>
/// <param name="modName">The friendly mod name for use in errors.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor)
+ /// <param name="reflection">Simplifies access to private code.</param>
+ 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;
}
/// <inheritdoc />
@@ -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
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
+ /// <summary>Simplifies access to private code.</summary>
+ private readonly Reflector Reflection;
+
/*********
** Accessors
@@ -46,7 +50,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <param name="modID">The unique ID of the relevant mod.</param>
/// <param name="modName">The friendly mod name for use in errors.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor)
+ /// <param name="reflection">Simplifies access to private code.</param>
+ 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;
}
/// <inheritdoc />
@@ -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
+ );
}
/// <summary>Get the underlying game content manager.</summary>
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
/// <summary>A case-insensitive lookup of relative paths within the <see cref="ContentManager.RootDirectory"/>.</summary>
private readonly CaseInsensitivePathCache RelativePathCache;
+ /// <summary>Simplifies access to private code.</summary>
+ private readonly Reflector Reflection;
+
/*********
** Public methods
@@ -38,7 +42,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <param name="modName">The friendly mod name for use in errors.</param>
/// <param name="gameContentManager">The game content manager used for map tilesheets not provided by the mod.</param>
/// <param name="relativePathCache">A case-insensitive lookup of relative paths within the <paramref name="relativePathCache"/>.</param>
- public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache)
+ /// <param name="reflection">Simplifies access to private code.</param>
+ 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;
}
/// <inheritdoc />
@@ -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<IContentPack[]>(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);
/// <summary>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.</summary>
- /// <param name="minWidth">The minimum texture width.</param>
- /// <param name="minHeight">The minimum texture height.</param>
+ /// <param name="minWidth">The minimum texture width in pixels.</param>
+ /// <param name="minHeight">The minimum texture height in pixels.</param>
/// <returns>Whether the texture was resized.</returns>
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
/// <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>
/// <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);
+
+ /// <summary>Extend the map if needed to fit the given size. Note that this is an expensive operation and resizes the map in-place.</summary>
+ /// <param name="map">The map to resize.</param>
+ /// <param name="minWidth">The minimum map width in tiles.</param>
+ /// <param name="minHeight">The minimum map height in tiles.</param>
+ /// <returns>Whether the map was resized.</returns>
+ bool ExtendMap(Map map, int minWidth, int minHeight);
}
}