summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs44
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs4
-rw-r--r--src/SMAPI/Framework/SCore.cs15
-rw-r--r--src/SMAPI/Framework/SGame.cs14
-rw-r--r--src/SMAPI/Framework/SGameRunner.cs10
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs60
-rw-r--r--src/SMAPI/SMAPI.csproj2
8 files changed, 109 insertions, 42 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index d0c693bf..fbc00d1d 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -54,7 +54,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.12.6";
+ internal static string RawApiVersion = "3.12.7";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 63cd1759..38bcf153 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Content;
@@ -205,28 +206,35 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <remarks>Derived from <see cref="LocalizedContentManager.Load{T}(string, LocalizedContentManager.LanguageCode)"/>.</remarks>
private T RawLoad<T>(string assetName, LanguageCode language, bool useCache)
{
- // use cached key
- if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName, out string cachedKey))
- return base.RawLoad<T>(cachedKey, useCache);
-
- // try translated key
- if (language != LocalizedContentManager.LanguageCode.en)
+ try
{
- string translatedKey = $"{assetName}.{this.GetLocale(language)}";
- try
- {
- T obj = base.RawLoad<T>(translatedKey, useCache);
- this.LocalizedAssetNames[assetName] = translatedKey;
- return obj;
- }
- catch (ContentLoadException)
+ // use cached key
+ if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName, out string cachedKey))
+ return base.RawLoad<T>(cachedKey, useCache);
+
+ // try translated key
+ if (language != LocalizedContentManager.LanguageCode.en)
{
- this.LocalizedAssetNames[assetName] = assetName;
+ string translatedKey = $"{assetName}.{this.GetLocale(language)}";
+ try
+ {
+ T obj = base.RawLoad<T>(translatedKey, useCache);
+ this.LocalizedAssetNames[assetName] = translatedKey;
+ return obj;
+ }
+ catch (ContentLoadException)
+ {
+ this.LocalizedAssetNames[assetName] = assetName;
+ }
}
- }
- // try base asset
- return base.RawLoad<T>(assetName, useCache);
+ // try base asset
+ return base.RawLoad<T>(assetName, useCache);
+ }
+ catch (ContentLoadException ex) when (ex.InnerException is FileNotFoundException innerEx && innerEx.InnerException == null)
+ {
+ throw new SContentLoadException($"Error loading \"{assetName}\": it isn't in the Content folder and no mod provided it.");
+ }
}
/// <summary>Parse an asset key that contains an explicit language into its asset name and language, if applicable.</summary>
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index 6fe44d98..f2876146 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -406,6 +406,10 @@ namespace StardewModdingAPI.Framework.Logging
}
}
+ // simplify exception messages
+ if (level == LogLevel.Error)
+ message = ExceptionHelper.SimplifyExtensionMessage(message);
+
// forward to monitor
gameMonitor.Log(message, level);
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index df6cd129..6dffb1de 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -247,7 +247,7 @@ namespace StardewModdingAPI.Framework
multiplayer: this.Multiplayer,
exitGameImmediately: this.ExitGameImmediately,
- onGameContentLoaded: this.OnGameContentLoaded,
+ onGameContentLoaded: this.OnInstanceContentLoaded,
onGameUpdating: this.OnGameUpdating,
onPlayerInstanceUpdating: this.OnPlayerInstanceUpdating,
onGameExiting: this.OnGameExiting
@@ -289,7 +289,7 @@ namespace StardewModdingAPI.Framework
this.UpdateWindowTitles();
// start game
- this.Monitor.Log("Starting game...", LogLevel.Debug);
+ this.Monitor.Log("Waiting for game to launch...", LogLevel.Debug);
try
{
this.IsGameRunning = true;
@@ -377,7 +377,7 @@ namespace StardewModdingAPI.Framework
// load mods
{
- this.Monitor.Log("Loading mod metadata...");
+ this.Monitor.Log("Loading mod metadata...", LogLevel.Debug);
ModResolver resolver = new ModResolver();
// log loose files
@@ -429,8 +429,8 @@ namespace StardewModdingAPI.Framework
).Start();
}
- /// <summary>Raised after the game finishes loading its initial content.</summary>
- private void OnGameContentLoaded()
+ /// <summary>Raised after an instance finishes loading its initial content.</summary>
+ private void OnInstanceContentLoaded()
{
// override map display device
Game1.mapDisplayDevice = new SDisplayDevice(Game1.content, Game1.game1.GraphicsDevice);
@@ -1487,7 +1487,7 @@ namespace StardewModdingAPI.Framework
/// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param>
private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase)
{
- this.Monitor.Log("Loading mods...");
+ this.Monitor.Log("Loading mods...", LogLevel.Debug);
// load mods
IList<IModMetadata> skippedMods = new List<IModMetadata>();
@@ -1523,6 +1523,7 @@ namespace StardewModdingAPI.Framework
this.ReloadTranslations(loaded);
// initialize loaded non-content-pack mods
+ this.Monitor.Log("Launching mods...", LogLevel.Debug);
foreach (IModMetadata metadata in loadedMods)
{
// add interceptors
@@ -1572,6 +1573,8 @@ namespace StardewModdingAPI.Framework
// unlock mod integrations
this.ModRegistry.AreAllModsInitialized = true;
+
+ this.Monitor.Log("Mods loaded and ready!", LogLevel.Debug);
}
/// <summary>Raised after a mod adds or removes asset interceptors.</summary>
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 55ab8377..4e134455 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -54,6 +54,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Raised when the instance is updating its state (roughly 60 times per second).</summary>
private readonly Action<SGame, GameTime, Action> OnUpdating;
+ /// <summary>Raised after the instance finishes loading its initial content.</summary>
+ private readonly Action OnContentLoaded;
+
/*********
** Accessors
@@ -106,7 +109,8 @@ namespace StardewModdingAPI.Framework
/// <param name="multiplayer">The core multiplayer logic.</param>
/// <param name="exitGameImmediately">Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.</param>
/// <param name="onUpdating">Raised when the instance is updating its state (roughly 60 times per second).</param>
- public SGame(PlayerIndex playerIndex, int instanceIndex, Monitor monitor, Reflector reflection, EventManager eventManager, SInputState input, SModHooks modHooks, SMultiplayer multiplayer, Action<string> exitGameImmediately, Action<SGame, GameTime, Action> onUpdating)
+ /// <param name="onContentLoaded">Raised after the game finishes loading its initial content.</param>
+ public SGame(PlayerIndex playerIndex, int instanceIndex, Monitor monitor, Reflector reflection, EventManager eventManager, SInputState input, SModHooks modHooks, SMultiplayer multiplayer, Action<string> exitGameImmediately, Action<SGame, GameTime, Action> onUpdating, Action onContentLoaded)
: base(playerIndex, instanceIndex)
{
// init XNA
@@ -124,6 +128,7 @@ namespace StardewModdingAPI.Framework
this.Reflection = reflection;
this.ExitGameImmediately = exitGameImmediately;
this.OnUpdating = onUpdating;
+ this.OnContentLoaded = onContentLoaded;
}
/// <summary>Get the current input state for a button.</summary>
@@ -138,6 +143,13 @@ namespace StardewModdingAPI.Framework
return input.GetState(button);
}
+ /// <inheritdoc />
+ protected override void LoadContent()
+ {
+ base.LoadContent();
+
+ this.OnContentLoaded();
+ }
/*********
** Protected methods
diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs
index 45e7369c..b816bb7c 100644
--- a/src/SMAPI/Framework/SGameRunner.cs
+++ b/src/SMAPI/Framework/SGameRunner.cs
@@ -94,7 +94,7 @@ namespace StardewModdingAPI.Framework
public override Game1 CreateGameInstance(PlayerIndex playerIndex = PlayerIndex.One, int instanceIndex = 0)
{
SInputState inputState = new SInputState();
- return new SGame(playerIndex, instanceIndex, this.Monitor, this.Reflection, this.Events, inputState, this.ModHooks, this.Multiplayer, this.ExitGameImmediately, this.OnPlayerInstanceUpdating);
+ return new SGame(playerIndex, instanceIndex, this.Monitor, this.Reflection, this.Events, inputState, this.ModHooks, this.Multiplayer, this.ExitGameImmediately, this.OnPlayerInstanceUpdating, this.OnGameContentLoaded);
}
/// <inheritdoc />
@@ -129,14 +129,6 @@ namespace StardewModdingAPI.Framework
/*********
** Protected methods
*********/
- /// <summary>Load content when the game is launched.</summary>
- protected override void LoadContent()
- {
- base.LoadContent();
-
- this.OnGameContentLoaded();
- }
-
/// <summary>Perform cleanup logic when the game exits.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event args.</param>
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 35ae26b3..7efd99a0 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
@@ -183,8 +184,10 @@ namespace StardewModdingAPI.Metadata
if (!ignoreWorld)
{
- foreach (GameLocation location in this.GetLocations())
+ foreach (LocationInfo info in this.GetLocationsWithInfo())
{
+ GameLocation location = info.Location;
+
if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
{
static ISet<string> GetWarpSet(GameLocation location)
@@ -195,7 +198,7 @@ namespace StardewModdingAPI.Metadata
}
var oldWarps = GetWarpSet(location);
- this.ReloadMap(location);
+ this.ReloadMap(info);
var newWarps = GetWarpSet(location);
changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
@@ -905,9 +908,12 @@ namespace StardewModdingAPI.Metadata
}
/// <summary>Reload the map for a location.</summary>
- /// <param name="location">The location whose map to reload.</param>
- private void ReloadMap(GameLocation location)
+ /// <param name="locationInfo">The location whose map to reload.</param>
+ private void ReloadMap(LocationInfo locationInfo)
{
+ GameLocation location = locationInfo.Location;
+ Vector2? playerPos = Game1.player?.Position;
+
if (this.AggressiveMemoryOptimizations)
location.map.DisposeTileSheets(Game1.mapDisplayDevice);
@@ -926,6 +932,15 @@ namespace StardewModdingAPI.Metadata
// update for changes
location.updateWarps();
location.updateDoors();
+ locationInfo.ParentBuilding?.updateInteriorWarps();
+
+ // reset player position
+ // The game may move the player as part of the map changes, even if they're not in that
+ // location. That's not needed in this case, and it can have weird effects like players
+ // warping onto the wrong tile (or even off-screen) if a patch changes the farmhouse
+ // map on location change.
+ if (playerPos.HasValue)
+ Game1.player.Position = playerPos.Value;
}
/// <summary>Reload the disposition data for matching NPCs.</summary>
@@ -1202,6 +1217,13 @@ namespace StardewModdingAPI.Metadata
/// <param name="buildingInteriors">Whether to also get the interior locations for constructable buildings.</param>
private IEnumerable<GameLocation> GetLocations(bool buildingInteriors = true)
{
+ return this.GetLocationsWithInfo(buildingInteriors).Select(info => info.Location);
+ }
+
+ /// <summary>Get all locations in the game.</summary>
+ /// <param name="buildingInteriors">Whether to also get the interior locations for constructable buildings.</param>
+ private IEnumerable<LocationInfo> GetLocationsWithInfo(bool buildingInteriors = true)
+ {
// get available root locations
IEnumerable<GameLocation> rootLocations = Game1.locations;
if (SaveGame.loaded?.locations != null)
@@ -1210,7 +1232,7 @@ namespace StardewModdingAPI.Metadata
// yield root + child locations
foreach (GameLocation location in rootLocations)
{
- yield return location;
+ yield return new LocationInfo(location, null);
if (buildingInteriors && location is BuildableGameLocation buildableLocation)
{
@@ -1218,7 +1240,7 @@ namespace StardewModdingAPI.Metadata
{
GameLocation indoors = building.indoors.Value;
if (indoors != null)
- yield return indoors;
+ yield return new LocationInfo(indoors, building);
}
}
}
@@ -1306,5 +1328,31 @@ namespace StardewModdingAPI.Metadata
// remove key from cache
return BuildingPainter.paintMaskLookup.Remove(key);
}
+
+ /// <summary>Metadata about a location used in asset propagation.</summary>
+ private readonly struct LocationInfo
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The location instance.</summary>
+ public GameLocation Location { get; }
+
+ /// <summary>The building which contains the location, if any.</summary>
+ public Building ParentBuilding { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="location">The location instance.</param>
+ /// <param name="parentBuilding">The building which contains the location, if any.</param>
+ public LocationInfo(GameLocation location, Building parentBuilding)
+ {
+ this.Location = location;
+ this.ParentBuilding = parentBuilding;
+ }
+ }
}
}
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index 0f1b0516..c147e7dc 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -3,7 +3,7 @@
<AssemblyName>StardewModdingAPI</AssemblyName>
<RootNamespace>StardewModdingAPI</RootNamespace>
<Description>The modding API for Stardew Valley.</Description>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<PlatformTarget>x86</PlatformTarget>
<OutputType>Exe</OutputType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>