diff options
Diffstat (limited to 'src/SMAPI')
-rw-r--r-- | src/SMAPI/Constants.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Context.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModResolver.cs | 13 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 12 | ||||
-rw-r--r-- | src/SMAPI/Framework/SModHooks.cs | 18 | ||||
-rw-r--r-- | src/SMAPI/SMAPI.csproj | 8 | ||||
-rw-r--r-- | src/SMAPI/Utilities/DelegatingModHooks.cs | 137 | ||||
-rw-r--r-- | src/SMAPI/Utilities/PerScreen.cs | 6 | ||||
-rw-r--r-- | src/SMAPI/i18n/ko.json | 4 |
10 files changed, 180 insertions, 32 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 3ff3159b..e5601747 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// <summary>SMAPI's current raw semantic version.</summary> - internal static string RawApiVersion = "3.18.1"; + internal static string RawApiVersion = "3.18.3"; } /// <summary>Contains SMAPI's constants and assumptions.</summary> diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs index c822908e..978459e8 100644 --- a/src/SMAPI/Context.cs +++ b/src/SMAPI/Context.cs @@ -60,7 +60,7 @@ namespace StardewModdingAPI public static bool IsWorldReady { get => Context.IsWorldReadyForScreen.Value; - set => Context.IsWorldReadyForScreen.Value = value; + internal set => Context.IsWorldReadyForScreen.Value = value; } /// <summary>Whether <see cref="IsWorldReady"/> is true and the player is free to act in the world (no menu is displayed, no cutscene is in progress, etc).</summary> diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index badbd766..2c068784 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -130,7 +130,7 @@ namespace StardewModdingAPI.Framework.ContentManagers ".png" => this.LoadImageFile<T>(assetName, file), ".tbin" or ".tmx" => this.LoadMapFile<T>(assetName, file), ".xnb" => this.LoadXnbFile<T>(assetName), - _ => this.HandleUnknownFileType<T>(assetName, file) + _ => (T)this.HandleUnknownFileType(assetName, file, typeof(T)) }; } catch (Exception ex) @@ -323,13 +323,15 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// <summary>Handle a request to load a file type that isn't supported by SMAPI.</summary> - /// <typeparam name="T">The expected file type.</typeparam> /// <param name="assetName">The asset name relative to the loader root directory.</param> /// <param name="file">The file to load.</param> - private T HandleUnknownFileType<T>(IAssetName assetName, FileInfo file) + /// <param name="assetType">The expected file type.</param> + private object HandleUnknownFileType(IAssetName assetName, FileInfo file, Type assetType) { this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); - return default; + return assetType.IsValueType + ? Activator.CreateInstance(assetType) + : null; } /// <summary>Assert that the asset type is compatible with one of the allowed types.</summary> diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index cb62e16f..607bb70d 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -180,13 +180,16 @@ namespace StardewModdingAPI.Framework.ModLoading return mods .OrderBy(mod => { - string id = mod.Manifest.UniqueID; + string? id = mod.Manifest?.UniqueID; - if (modIdsToLoadEarly.TryGetValue(id, out string? actualId)) - return -int.MaxValue + Array.IndexOf(earlyArray, actualId); + if (id is not null) + { + if (modIdsToLoadEarly.TryGetValue(id, out string? actualId)) + return -int.MaxValue + Array.IndexOf(earlyArray, actualId); - if (modIdsToLoadLate.TryGetValue(id, out actualId)) - return int.MaxValue - Array.IndexOf(lateArray, actualId); + if (modIdsToLoadLate.TryGetValue(id, out actualId)) + return int.MaxValue - Array.IndexOf(lateArray, actualId); + } return 0; }) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index fea0f7d0..3e179ef7 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -260,7 +260,11 @@ namespace StardewModdingAPI.Framework monitor: this.Monitor, reflection: this.Reflection, eventManager: this.EventManager, - modHooks: new SModHooks(this.OnNewDayAfterFade, this.Monitor), + modHooks: new SModHooks( + parent: new ModHooks(), + beforeNewDayAfterFade: this.OnNewDayAfterFade, + monitor: this.Monitor + ), multiplayer: this.Multiplayer, exitGameImmediately: this.ExitGameImmediately, @@ -431,7 +435,7 @@ namespace StardewModdingAPI.Framework // apply load order customizations if (this.Settings.ModsToLoadEarly.Any() || this.Settings.ModsToLoadLate.Any()) { - HashSet<string> installedIds = new HashSet<string>(mods.Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase); + HashSet<string> installedIds = new HashSet<string>(mods.Select(p => p.Manifest?.UniqueID).Where(p => p is not null), StringComparer.OrdinalIgnoreCase); string[] missingEarlyMods = this.Settings.ModsToLoadEarly.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray(); string[] missingLateMods = this.Settings.ModsToLoadLate.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray(); @@ -1797,7 +1801,7 @@ namespace StardewModdingAPI.Framework // call entry method try { - mod.Entry(mod.Helper!); + mod.Entry(mod.Helper); } catch (Exception ex) { @@ -1824,7 +1828,7 @@ namespace StardewModdingAPI.Framework } // validate mod doesn't implement both GetApi() and GetApi(mod) - if (metadata.Api != null && mod.GetType().GetMethod(nameof(Mod.GetApi), new Type[] { typeof(IModInfo) })!.DeclaringType != typeof(Mod)) + if (metadata.Api != null && mod.GetType().GetMethod(nameof(Mod.GetApi), new[] { typeof(IModInfo) })!.DeclaringType != typeof(Mod)) metadata.LogAsMod($"Mod implements both {nameof(Mod.GetApi)}() and {nameof(Mod.GetApi)}({nameof(IModInfo)}), which isn't allowed. The latter will be ignored.", LogLevel.Error); } Context.HeuristicModsRunningCode.TryPop(out _); diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs index a7736c8b..ac4f242c 100644 --- a/src/SMAPI/Framework/SModHooks.cs +++ b/src/SMAPI/Framework/SModHooks.cs @@ -1,11 +1,12 @@ using System; using System.Threading.Tasks; +using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Framework { /// <summary>Invokes callbacks for mod hooks provided by the game.</summary> - internal class SModHooks : ModHooks + internal class SModHooks : DelegatingModHooks { /********* ** Fields @@ -21,25 +22,24 @@ namespace StardewModdingAPI.Framework ** Public methods *********/ /// <summary>Construct an instance.</summary> + /// <param name="parent">The underlying hooks to call by default.</param> /// <param name="beforeNewDayAfterFade">A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</param> /// <param name="monitor">Writes messages to the console.</param> - public SModHooks(Action beforeNewDayAfterFade, IMonitor monitor) + public SModHooks(ModHooks parent, Action beforeNewDayAfterFade, IMonitor monitor) + : base(parent) { this.BeforeNewDayAfterFade = beforeNewDayAfterFade; this.Monitor = monitor; } - /// <summary>A hook invoked when <see cref="Game1.newDayAfterFade"/> is called.</summary> - /// <param name="action">The vanilla <see cref="Game1.newDayAfterFade"/> logic.</param> + /// <inheritdoc /> public override void OnGame1_NewDayAfterFade(Action action) { this.BeforeNewDayAfterFade(); action(); } - /// <summary>Start an asynchronous task for the game.</summary> - /// <param name="task">The task to start.</param> - /// <param name="id">A unique key which identifies the task.</param> + /// <inheritdoc /> public override Task StartTask(Task task, string id) { this.Monitor.Log($"Synchronizing '{id}' task..."); @@ -48,9 +48,7 @@ namespace StardewModdingAPI.Framework return task; } - /// <summary>Start an asynchronous task for the game.</summary> - /// <param name="task">The task to start.</param> - /// <param name="id">A unique key which identifies the task.</param> + /// <inheritdoc /> public override Task<T> StartTask<T>(Task<T> task, string id) { this.Monitor.Log($"Synchronizing '{id}' task..."); diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index e5d8937c..530b75aa 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -8,7 +8,6 @@ <OutputType>Exe</OutputType> <GenerateDocumentationFile>true</GenerateDocumentationFile> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> - <LargeAddressAware Condition="'$(OS)' == 'Windows_NT'">true</LargeAddressAware> <ApplicationIcon>icon.ico</ApplicationIcon> <!--copy dependency DLLs to bin folder so we can include them in installer bundle --> @@ -22,12 +21,11 @@ <Import Project="..\..\build\common.targets" /> <ItemGroup> - <PackageReference Include="LargeAddressAware" Version="1.0.5" /> <PackageReference Include="Mono.Cecil" Version="0.11.4" /> <PackageReference Include="MonoMod.Common" Version="22.3.5.1" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> - <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.2.0" /> - <PackageReference Include="Pintail" Version="2.2.1" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> + <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.3.0" /> + <PackageReference Include="Pintail" Version="2.2.2" /> <PackageReference Include="Platonymous.TMXTile" Version="1.5.9" /> <PackageReference Include="System.Reflection.Emit" Version="4.7.0" /> diff --git a/src/SMAPI/Utilities/DelegatingModHooks.cs b/src/SMAPI/Utilities/DelegatingModHooks.cs new file mode 100644 index 00000000..3ebcf997 --- /dev/null +++ b/src/SMAPI/Utilities/DelegatingModHooks.cs @@ -0,0 +1,137 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Xna.Framework.Input; +using StardewModdingAPI.Events; +using StardewModdingAPI.Framework; +using StardewValley; +using StardewValley.Events; + +namespace StardewModdingAPI.Utilities +{ + /// <summary>An implementation of <see cref="ModHooks"/> which automatically calls the parent instance for any method that's not overridden.</summary> + /// <remarks>The mod hooks are primarily meant for SMAPI to use. Using this directly in mods is a last resort, since it's very easy to break SMAPI this way. This class requires that SMAPI is present in the parent chain.</remarks> + public class DelegatingModHooks : ModHooks + { + /********* + ** Accessors + *********/ + /// <summary>The underlying instance to delegate to by default.</summary> + public ModHooks Parent { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="modHooks">The underlying instance to delegate to by default.</param> + public DelegatingModHooks(ModHooks modHooks) + { + this.AssertSmapiInChain(modHooks); + + this.Parent = modHooks; + } + + /// <summary>Raised before the in-game clock changes.</summary> + /// <param name="action">Run the vanilla update logic.</param> + /// <remarks>In mods, consider using <see cref="IGameLoopEvents.TimeChanged"/> instead.</remarks> + public override void OnGame1_PerformTenMinuteClockUpdate(Action action) + { + this.Parent.OnGame1_PerformTenMinuteClockUpdate(action); + } + + /// <summary>Raised before initializing the new day and saving.</summary> + /// <param name="action">Run the vanilla update logic.</param> + /// <remarks>In mods, consider using <see cref="IGameLoopEvents.DayEnding"/> or <see cref="IGameLoopEvents.Saving"/> instead.</remarks> + public override void OnGame1_NewDayAfterFade(Action action) + { + this.Parent.OnGame1_NewDayAfterFade(action); + } + + /// <summary>Raised before showing the end-of-day menus (e.g. shipping menus, level-up screen, etc).</summary> + /// <param name="action">Run the vanilla update logic.</param> + public override void OnGame1_ShowEndOfNightStuff(Action action) + { + this.Parent.OnGame1_ShowEndOfNightStuff(action); + } + + /// <summary>Raised before updating the gamepad, mouse, and keyboard input state.</summary> + /// <param name="keyboardState">The keyboard state.</param> + /// <param name="mouseState">The mouse state.</param> + /// <param name="gamePadState">The gamepad state.</param> + /// <param name="action">Run the vanilla update logic.</param> + /// <remarks>In mods, consider using <see cref="IInputEvents"/> instead.</remarks> + public override void OnGame1_UpdateControlInput(ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState, Action action) + { + this.Parent.OnGame1_UpdateControlInput(ref keyboardState, ref mouseState, ref gamePadState, action); + } + + /// <summary>Raised before a location is updated for the local player entering it.</summary> + /// <param name="location">The location that will be updated.</param> + /// <param name="action">Run the vanilla update logic.</param> + /// <remarks>In mods, consider using <see cref="IPlayerEvents.Warped"/> instead.</remarks> + public override void OnGameLocation_ResetForPlayerEntry(GameLocation location, Action action) + { + this.Parent.OnGameLocation_ResetForPlayerEntry(location, action); + } + + /// <summary>Raised before the game checks for an action to trigger for a player interaction with a tile.</summary> + /// <param name="location">The location being checked.</param> + /// <param name="tileLocation">The tile position being checked.</param> + /// <param name="viewport">The game's current position and size within the map, measured in pixels.</param> + /// <param name="who">The player interacting with the tile.</param> + /// <param name="action">Run the vanilla update logic.</param> + /// <returns>Returns whether the interaction was handled.</returns> + public override bool OnGameLocation_CheckAction(GameLocation location, xTile.Dimensions.Location tileLocation, xTile.Dimensions.Rectangle viewport, Farmer who, Func<bool> action) + { + return this.Parent.OnGameLocation_CheckAction(location, tileLocation, viewport, who, action); + } + + /// <summary>Raised before the game picks a night event to show on the farm after the player sleeps.</summary> + /// <param name="action">Run the vanilla update logic.</param> + /// <returns>Returns the selected farm event.</returns> + public override FarmEvent OnUtility_PickFarmEvent(Func<FarmEvent> action) + { + return this.Parent.OnUtility_PickFarmEvent(action); + } + + /// <summary>Start an asynchronous task for the game.</summary> + /// <param name="task">The task to start.</param> + /// <param name="id">A unique key which identifies the task.</param> + public override Task StartTask(Task task, string id) + { + return this.Parent.StartTask(task, id); + } + + /// <summary>Start an asynchronous task for the game.</summary> + /// <typeparam name="T">The type returned by the task when it completes.</typeparam> + /// <param name="task">The task to start.</param> + /// <param name="id">A unique key which identifies the task.</param> + public override Task<T> StartTask<T>(Task<T> task, string id) + { + return this.Parent.StartTask<T>(task, id); + } + + + /********* + ** Private methods + *********/ + /// <summary>Assert that SMAPI's mod hook implementation is in the inheritance chain.</summary> + /// <param name="hooks">The mod hooks to check.</param> + private void AssertSmapiInChain(ModHooks hooks) + { + // this is SMAPI + if (this is SModHooks) + return; + + // SMAPI in delegated chain + for (ModHooks? cur = hooks; cur != null; cur = (cur as DelegatingModHooks)?.Parent) + { + if (cur is SModHooks) + return; + } + + // SMAPI not found + throw new InvalidOperationException($"Can't create a {nameof(DelegatingModHooks)} instance without SMAPI's mod hooks in the parent chain."); + } + } +} diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 87bf2027..674ec760 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -101,6 +101,12 @@ namespace StardewModdingAPI.Utilities this.RemoveScreens(_ => true); } + /// <summary>Get whether the current screen has a value created yet.</summary> + public bool IsActiveForScreen() + { + return this.States.ContainsKey(Context.ScreenId); + } + /********* ** Private methods diff --git a/src/SMAPI/i18n/ko.json b/src/SMAPI/i18n/ko.json index 8d267e5e..8122a9e2 100644 --- a/src/SMAPI/i18n/ko.json +++ b/src/SMAPI/i18n/ko.json @@ -1,6 +1,6 @@ { // short date format for SDate // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) - "generic.date": "{{season}} {{day}}", - "generic.date-with-year": "{{year}} 학년 {{season}} {{day}}" + "generic.date": "{{season}} {{day}}일", + "generic.date-with-year": "{{year}}년차 {{season}} {{day}}일" } |