From e5407417a0cc80f9cb3d7eb7b43bdb207322a4ec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Jan 2023 22:05:22 -0500 Subject: add delegating mod hooks for mod use --- src/SMAPI/Utilities/DelegatingModHooks.cs | 137 ++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/SMAPI/Utilities/DelegatingModHooks.cs (limited to 'src/SMAPI/Utilities') 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 +{ + /// An implementation of which automatically calls the parent instance for any method that's not overridden. + /// 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. + public class DelegatingModHooks : ModHooks + { + /********* + ** Accessors + *********/ + /// The underlying instance to delegate to by default. + public ModHooks Parent { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The underlying instance to delegate to by default. + public DelegatingModHooks(ModHooks modHooks) + { + this.AssertSmapiInChain(modHooks); + + this.Parent = modHooks; + } + + /// Raised before the in-game clock changes. + /// Run the vanilla update logic. + /// In mods, consider using instead. + public override void OnGame1_PerformTenMinuteClockUpdate(Action action) + { + this.Parent.OnGame1_PerformTenMinuteClockUpdate(action); + } + + /// Raised before initializing the new day and saving. + /// Run the vanilla update logic. + /// In mods, consider using or instead. + public override void OnGame1_NewDayAfterFade(Action action) + { + this.Parent.OnGame1_NewDayAfterFade(action); + } + + /// Raised before showing the end-of-day menus (e.g. shipping menus, level-up screen, etc). + /// Run the vanilla update logic. + public override void OnGame1_ShowEndOfNightStuff(Action action) + { + this.Parent.OnGame1_ShowEndOfNightStuff(action); + } + + /// Raised before updating the gamepad, mouse, and keyboard input state. + /// The keyboard state. + /// The mouse state. + /// The gamepad state. + /// Run the vanilla update logic. + /// In mods, consider using instead. + 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); + } + + /// Raised before a location is updated for the local player entering it. + /// The location that will be updated. + /// Run the vanilla update logic. + /// In mods, consider using instead. + public override void OnGameLocation_ResetForPlayerEntry(GameLocation location, Action action) + { + this.Parent.OnGameLocation_ResetForPlayerEntry(location, action); + } + + /// Raised before the game checks for an action to trigger for a player interaction with a tile. + /// The location being checked. + /// The tile position being checked. + /// The game's current position and size within the map, measured in pixels. + /// The player interacting with the tile. + /// Run the vanilla update logic. + /// Returns whether the interaction was handled. + public override bool OnGameLocation_CheckAction(GameLocation location, xTile.Dimensions.Location tileLocation, xTile.Dimensions.Rectangle viewport, Farmer who, Func action) + { + return this.Parent.OnGameLocation_CheckAction(location, tileLocation, viewport, who, action); + } + + /// Raised before the game picks a night event to show on the farm after the player sleeps. + /// Run the vanilla update logic. + /// Returns the selected farm event. + public override FarmEvent OnUtility_PickFarmEvent(Func action) + { + return this.Parent.OnUtility_PickFarmEvent(action); + } + + /// Start an asynchronous task for the game. + /// The task to start. + /// A unique key which identifies the task. + public override Task StartTask(Task task, string id) + { + return this.Parent.StartTask(task, id); + } + + /// Start an asynchronous task for the game. + /// The type returned by the task when it completes. + /// The task to start. + /// A unique key which identifies the task. + public override Task StartTask(Task task, string id) + { + return this.Parent.StartTask(task, id); + } + + + /********* + ** Private methods + *********/ + /// Assert that SMAPI's mod hook implementation is in the inheritance chain. + /// The mod hooks to check. + 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."); + } + } +} -- cgit