summaryrefslogtreecommitdiff
path: root/src/SMAPI/Utilities/DelegatingModHooks.cs
blob: 3ebcf997a576a1421d13ad8316b75bb7079ca83e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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.");
        }
    }
}