diff options
Diffstat (limited to 'src/SMAPI')
-rw-r--r-- | src/SMAPI/Constants.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 44 | ||||
-rw-r--r-- | src/SMAPI/Framework/Logging/LogManager.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 15 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 14 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGameRunner.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Metadata/CoreAssetPropagator.cs | 60 | ||||
-rw-r--r-- | src/SMAPI/SMAPI.csproj | 2 |
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> |