diff --git a/src/SMAPI/Events/CheckedForActionEventArgs.cs b/src/SMAPI/Events/CheckedForActionEventArgs.cs
new file mode 100644
index 00000000..953ce458
--- /dev/null
+++ b/src/SMAPI/Events/CheckedForActionEventArgs.cs
@@ -0,0 +1,56 @@
+using System;
+using Microsoft.Xna.Framework;
+using StardewValley;
+
+namespace StardewModdingAPI.Events
+{
+ /// Event arguments for an event.
+ public class CheckedForActionEventArgs : EventArgs
+ {
+ /*********
+ ** Properties
+ *********/
+ /// The underlying field for .
+ private readonly Lazy ActionPropertyValueImpl;
+
+ /*********
+ ** Accessors
+ *********/
+ /// The player who checked for an action.
+ public Farmer Player { get; }
+
+ /// The tile checked.
+ public Vector2 Tile { get; }
+
+ /// The value of the Action tile property, if any.
+ public string ActionPropertyValue => this.ActionPropertyValueImpl.Value;
+
+ /// The current cursor position. This may differ from , due to how the game selects the target tile for actions in some cases.
+ public ICursorPosition Cursor { get; }
+
+ /// Whether the affected player is the local one.
+ public bool IsLocalPlayer => this.Player.IsLocalPlayer;
+
+ /// Whether the game performed an action in response to the check. Note that the game sometimes handles input without marking it handled (e.g. when activating a TV or fireplace).
+ public bool WasHandled { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The player for whom the action was checked.
+ /// The tile checked.
+ /// The current cursor position.
+ /// The value of the Action tile property, if any.
+ /// Whether the game performed an action in response to the check.
+ internal CheckedForActionEventArgs(Farmer player, Vector2 tile, ICursorPosition cursorPosition, Lazy actionPropertyValue, bool wasHandled)
+ {
+ this.Player = player;
+ this.Tile = tile;
+ this.Cursor = cursorPosition;
+ this.ActionPropertyValueImpl = actionPropertyValue;
+ this.WasHandled = wasHandled;
+ }
+ }
+}
diff --git a/src/SMAPI/Events/CheckingForActionEventArgs.cs b/src/SMAPI/Events/CheckingForActionEventArgs.cs
new file mode 100644
index 00000000..3857aae4
--- /dev/null
+++ b/src/SMAPI/Events/CheckingForActionEventArgs.cs
@@ -0,0 +1,51 @@
+using System;
+using Microsoft.Xna.Framework;
+using StardewValley;
+
+namespace StardewModdingAPI.Events
+{
+ /// Event arguments for an event.
+ public class CheckingForActionEventArgs : EventArgs
+ {
+ /*********
+ ** Properties
+ *********/
+ /// The underlying field for .
+ private readonly Lazy ActionPropertyValueImpl;
+
+ /*********
+ ** Accessors
+ *********/
+ /// The player checking for an action.
+ public Farmer Player { get; }
+
+ /// The tile being checked.
+ public Vector2 Tile { get; }
+
+ /// The value of the Action tile property, if any.
+ public string ActionPropertyValue => this.ActionPropertyValueImpl.Value;
+
+ /// The current cursor position. This may differ from , due to how the game selects the target tile for actions in some cases.
+ public ICursorPosition Cursor { get; }
+
+ /// Whether the affected player is the local one.
+ public bool IsLocalPlayer => this.Player.IsLocalPlayer;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The player for whom the action is being checked.
+ /// The tile being checked.
+ /// The current cursor position.
+ /// The value of the Action tile property, if any.
+ internal CheckingForActionEventArgs(Farmer player, Vector2 tile, ICursorPosition cursorPosition, Lazy actionPropertyValue)
+ {
+ this.Player = player;
+ this.Tile = tile;
+ this.Cursor = cursorPosition;
+ this.ActionPropertyValueImpl = actionPropertyValue;
+ }
+ }
+}
diff --git a/src/SMAPI/Events/IPlayerEvents.cs b/src/SMAPI/Events/IPlayerEvents.cs
index 81e17b1a..a221e40d 100644
--- a/src/SMAPI/Events/IPlayerEvents.cs
+++ b/src/SMAPI/Events/IPlayerEvents.cs
@@ -5,6 +5,12 @@ namespace StardewModdingAPI.Events
/// Events raised when the player data changes.
public interface IPlayerEvents
{
+ /// Raised before the game checks for an action in response to a player input. That includes activating an interactive object, opening a chest, putting an item in a machine, etc. NOTE: this event is currently only raised for the current player.
+ event EventHandler CheckingForAction;
+
+ /// Raised after the game checks for an action in response to a player input. That includes activating an interactive object, opening a chest, putting an item in a machine, etc. NOTE: this event is currently only raised for the current player.
+ event EventHandler CheckedForAction;
+
/// Raised after items are added or removed to a player's inventory. NOTE: this event is currently only raised for the current player.
event EventHandler InventoryChanged;
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 13244601..72899229 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -121,6 +121,12 @@ namespace StardewModdingAPI.Framework.Events
/****
** Player
****/
+ /// Raised before the game checks for an action in response to a player click.
+ public readonly ManagedEvent CheckingForAction;
+
+ /// Raised after the game checks for an action in response to a player click.
+ public readonly ManagedEvent CheckedForAction;
+
/// Raised after items are added or removed to a player's inventory.
public readonly ManagedEvent InventoryChanged;
@@ -407,6 +413,8 @@ namespace StardewModdingAPI.Framework.Events
this.ModMessageReceived = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.ModMessageReceived));
this.PeerDisconnected = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerDisconnected));
+ this.CheckingForAction = ManageEventOf(nameof(IModEvents.Player), nameof(IPlayerEvents.CheckingForAction));
+ this.CheckedForAction = ManageEventOf(nameof(IModEvents.Player), nameof(IPlayerEvents.CheckedForAction));
this.InventoryChanged = ManageEventOf(nameof(IModEvents.Player), nameof(IPlayerEvents.InventoryChanged));
this.LevelChanged = ManageEventOf(nameof(IModEvents.Player), nameof(IPlayerEvents.LevelChanged));
this.Warped = ManageEventOf(nameof(IModEvents.Player), nameof(IPlayerEvents.Warped));
diff --git a/src/SMAPI/Framework/Events/ModPlayerEvents.cs b/src/SMAPI/Framework/Events/ModPlayerEvents.cs
index ca7cfd96..056127c4 100644
--- a/src/SMAPI/Framework/Events/ModPlayerEvents.cs
+++ b/src/SMAPI/Framework/Events/ModPlayerEvents.cs
@@ -9,6 +9,20 @@ namespace StardewModdingAPI.Framework.Events
/*********
** Accessors
*********/
+ /// Raised before the game checks for an action in response to a player click. That includes activating an interactive object, opening a chest, talking to an NPC, putting an item in a machine, etc. NOTE: this event is currently only raised for the current player.
+ public event EventHandler CheckingForAction
+ {
+ add => this.EventManager.CheckingForAction.Add(value);
+ remove => this.EventManager.CheckingForAction.Remove(value);
+ }
+
+ /// Raised after the game checks for an action in response to a player input. That includes activating an interactive object, opening a chest, putting an item in a machine, etc. NOTE: this event is currently only raised for the current player.
+ public event EventHandler CheckedForAction
+ {
+ add => this.EventManager.CheckedForAction.Add(value);
+ remove => this.EventManager.CheckedForAction.Remove(value);
+ }
+
/// Raised after items are added or removed to a player's inventory. NOTE: this event is currently only raised for the local player.
public event EventHandler InventoryChanged
{
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 957fd1d6..f84080d3 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -163,7 +163,7 @@ namespace StardewModdingAPI.Framework
this.OnGameExiting = onGameExiting;
Game1.input = new SInputState();
Game1.multiplayer = new SMultiplayer(monitor, eventManager, jsonHelper, modRegistry, reflection, this.OnModMessageReceived);
- Game1.hooks = new SModHooks(this.OnNewDayAfterFade);
+ Game1.hooks = new SModHooks(this.OnNewDayAfterFade, this.OnLocationCheckingAction);
// init observables
Game1.locations = new ObservableCollection();
@@ -193,9 +193,38 @@ namespace StardewModdingAPI.Framework
}
/// A callback invoked before runs.
- protected void OnNewDayAfterFade()
+ /// Resume the vanilla logic.
+ protected void OnNewDayAfterFade(Action resume)
{
this.Events.DayEnding.RaiseEmpty();
+ resume();
+ }
+
+ /// A callback invoked before runs.
+ /// The location being checked.
+ /// The tile coordinate being checked.
+ /// The player checking for an action.
+ /// Resume the default logic.
+ private bool OnLocationCheckingAction(GameLocation location, Location tileLocation, Farmer who, Func resume)
+ {
+ // check for event listeners
+ bool hasPreListeners = this.Events.CheckingForAction.HasListeners();
+ bool hasPostListeners = this.Events.CheckedForAction.HasListeners();
+ if (!hasPreListeners && !hasPostListeners)
+ return resume();
+
+ // get tile info
+ Vector2 tilePos = new Vector2(tileLocation.X, tileLocation.Y);
+ Lazy actionPropertyValue = new Lazy(() => location.doesTileHaveProperty(tileLocation.X, tileLocation.Y, "Action", "Buildings"));
+
+ // raise events
+ if (hasPreListeners)
+ this.Events.CheckingForAction.Raise(new CheckingForActionEventArgs(who, tilePos, this.Input.CursorPosition, actionPropertyValue));
+ bool result = resume();
+ if (hasPostListeners)
+ this.Events.CheckedForAction.Raise(new CheckedForActionEventArgs(who, tilePos, this.Input.CursorPosition, actionPropertyValue, result));
+
+ return result;
}
/// A callback invoked when a mod message is received.
@@ -1384,7 +1413,7 @@ namespace StardewModdingAPI.Framework
}
Game1.drawPlayerHeldObject(Game1.player);
}
- label_129:
+ label_129:
if ((Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && ((!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool) && (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), (int)Game1.player.Position.Y - 38), Game1.viewport.Size) != null && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)))
Game1.drawTool(Game1.player);
if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs
index 9f0201c8..6abd9898 100644
--- a/src/SMAPI/Framework/SModHooks.cs
+++ b/src/SMAPI/Framework/SModHooks.cs
@@ -1,5 +1,6 @@
using System;
using StardewValley;
+using xTile.Dimensions;
namespace StardewModdingAPI.Framework
{
@@ -10,25 +11,39 @@ namespace StardewModdingAPI.Framework
** Properties
*********/
/// A callback to invoke before runs.
- private readonly Action BeforeNewDayAfterFade;
+ private readonly Action BeforeNewDayAfterFade;
+ /// A callback to invoke before runs.
+ private readonly Func, bool> BeforeCheckAction;
/*********
** Public methods
*********/
/// Construct an instance.
/// A callback to invoke before runs.
- public SModHooks(Action beforeNewDayAfterFade)
+ /// A callback to invoke before runs.
+ public SModHooks(Action beforeNewDayAfterFade, Func, bool> beforeCheckAction)
{
this.BeforeNewDayAfterFade = beforeNewDayAfterFade;
+ this.BeforeCheckAction = beforeCheckAction;
}
/// A hook invoked when is called.
- /// The vanilla logic.
- public override void OnGame1_NewDayAfterFade(Action action)
+ /// Resume the vanilla logic.
+ public override void OnGame1_NewDayAfterFade(Action resume)
{
- this.BeforeNewDayAfterFade?.Invoke();
- action();
+ this.BeforeNewDayAfterFade(resume);
+ }
+
+ /// A hook invoked when is called.
+ /// The location being checked.
+ /// The tile coordinate being checked.
+ /// The current viewport.
+ /// The player checking for an action.
+ /// Resume the default logic.
+ public override bool OnGameLocation_CheckAction(GameLocation location, Location tileLocation, Rectangle viewport, Farmer who, Func resume)
+ {
+ return this.BeforeCheckAction(location, tileLocation, who, resume);
}
}
}
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index 5540f277..69660914 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -82,6 +82,8 @@
+
+