From 558fb8a865b638cf5536856e7dcab44823feeaf3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 31 May 2018 22:47:56 -0400 Subject: [PATCH] move location events into new event system (#310) --- .../Events/EventArgsLocationObjectsChanged.cs | 1 - src/SMAPI/Events/IModEvents.cs | 9 +++ src/SMAPI/Events/IWorldEvents.cs | 20 +++++++ .../Events/WorldBuildingsChangedEventArgs.cs | 39 +++++++++++++ .../Events/WorldLocationsChangedEventArgs.cs | 33 +++++++++++ .../Events/WorldObjectsChangedEventArgs.cs | 40 +++++++++++++ src/SMAPI/Framework/Events/EventManager.cs | 26 ++++++++- src/SMAPI/Framework/Events/ManagedEvent.cs | 20 ++++++- .../Framework/Events/ManagedEventBase.cs | 9 +-- src/SMAPI/Framework/Events/ModEvents.cs | 26 +++++++++ src/SMAPI/Framework/Events/ModWorldEvents.cs | 56 +++++++++++++++++++ src/SMAPI/Framework/ModHelpers/ModHelper.cs | 16 ++++-- src/SMAPI/Framework/SGame.cs | 3 + src/SMAPI/IModHelper.cs | 5 ++ src/SMAPI/Program.cs | 3 +- src/SMAPI/StardewModdingAPI.csproj | 7 +++ 16 files changed, 297 insertions(+), 16 deletions(-) create mode 100644 src/SMAPI/Events/IModEvents.cs create mode 100644 src/SMAPI/Events/IWorldEvents.cs create mode 100644 src/SMAPI/Events/WorldBuildingsChangedEventArgs.cs create mode 100644 src/SMAPI/Events/WorldLocationsChangedEventArgs.cs create mode 100644 src/SMAPI/Events/WorldObjectsChangedEventArgs.cs create mode 100644 src/SMAPI/Framework/Events/ModEvents.cs create mode 100644 src/SMAPI/Framework/Events/ModWorldEvents.cs diff --git a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs index 410ef6e6..3bb387d5 100644 --- a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs +++ b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; -using Netcode; using StardewValley; using SObject = StardewValley.Object; diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs new file mode 100644 index 00000000..99e5523f --- /dev/null +++ b/src/SMAPI/Events/IModEvents.cs @@ -0,0 +1,9 @@ +namespace StardewModdingAPI.Events +{ + /// Manages access to events raised by SMAPI. + public interface IModEvents + { + /// Events raised when something changes in the world. + IWorldEvents World { get; } + } +} diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/IWorldEvents.cs new file mode 100644 index 00000000..5c713250 --- /dev/null +++ b/src/SMAPI/Events/IWorldEvents.cs @@ -0,0 +1,20 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Provides events raised when something changes in the world. + public interface IWorldEvents + { + /********* + ** Events + *********/ + /// Raised after a game location is added or removed. + event EventHandler LocationsChanged; + + /// Raised after buildings are added or removed in a location. + event EventHandler BuildingsChanged; + + /// Raised after objects are added or removed in a location. + event EventHandler ObjectsChanged; + } +} diff --git a/src/SMAPI/Events/WorldBuildingsChangedEventArgs.cs b/src/SMAPI/Events/WorldBuildingsChangedEventArgs.cs new file mode 100644 index 00000000..1f68fd02 --- /dev/null +++ b/src/SMAPI/Events/WorldBuildingsChangedEventArgs.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; +using StardewValley.Buildings; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class WorldBuildingsChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The location which changed. + public GameLocation Location { get; } + + /// The buildings added to the location. + public IEnumerable Added { get; } + + /// The buildings removed from the location. + public IEnumerable Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The location which changed. + /// The buildings added to the location. + /// The buildings removed from the location. + public WorldBuildingsChangedEventArgs(GameLocation location, IEnumerable added, IEnumerable removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldLocationsChangedEventArgs.cs b/src/SMAPI/Events/WorldLocationsChangedEventArgs.cs new file mode 100644 index 00000000..5cf77959 --- /dev/null +++ b/src/SMAPI/Events/WorldLocationsChangedEventArgs.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class WorldLocationsChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The added locations. + public IEnumerable Added { get; } + + /// The removed locations. + public IEnumerable Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The added locations. + /// The removed locations. + public WorldLocationsChangedEventArgs(IEnumerable added, IEnumerable removed) + { + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldObjectsChangedEventArgs.cs b/src/SMAPI/Events/WorldObjectsChangedEventArgs.cs new file mode 100644 index 00000000..fb20acd4 --- /dev/null +++ b/src/SMAPI/Events/WorldObjectsChangedEventArgs.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using StardewValley; +using Object = StardewValley.Object; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class WorldObjectsChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The location which changed. + public GameLocation Location { get; } + + /// The objects added to the location. + public IEnumerable> Added { get; } + + /// The objects removed from the location. + public IEnumerable> Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The location which changed. + /// The objects added to the location. + /// The objects removed from the location. + public WorldObjectsChangedEventArgs(GameLocation location, IEnumerable> added, IEnumerable> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 84036127..53ea699a 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework.Input; using StardewModdingAPI.Events; @@ -10,7 +9,23 @@ namespace StardewModdingAPI.Framework.Events internal class EventManager { /********* - ** Properties + ** Events (new) + *********/ + /**** + ** World + ****/ + /// Raised after a game location is added or removed. + public readonly ManagedEvent World_LocationsChanged; + + /// Raised after buildings are added or removed in a location. + public readonly ManagedEvent World_BuildingsChanged; + + /// Raised after objects are added or removed in a location. + public readonly ManagedEvent World_ObjectsChanged; + + + /********* + ** Events (old) *********/ /**** ** ContentEvents @@ -209,7 +224,12 @@ namespace StardewModdingAPI.Framework.Events ManagedEvent ManageEventOf(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry); ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry); - // init events + // init events (new) + this.World_BuildingsChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.LocationsChanged)); + this.World_LocationsChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.BuildingsChanged)); + this.World_ObjectsChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectsChanged)); + + // init events (old) this.Content_LocaleChanged = ManageEventOf>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged)); this.Control_ControllerButtonPressed = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonPressed)); diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index e54a4fd3..c1ebf6c7 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -27,9 +27,17 @@ namespace StardewModdingAPI.Framework.Events /// Add an event handler. /// The event handler. public void Add(EventHandler handler) + { + this.Add(handler, this.ModRegistry.GetFromStack()); + } + + /// Add an event handler. + /// The event handler. + /// The mod which added the event handler. + public void Add(EventHandler handler, IModMetadata mod) { this.Event += handler; - this.AddTracking(handler, this.Event?.GetInvocationList().Cast>()); + this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast>()); } /// Remove an event handler. @@ -84,9 +92,17 @@ namespace StardewModdingAPI.Framework.Events /// Add an event handler. /// The event handler. public void Add(EventHandler handler) + { + this.Add(handler, this.ModRegistry.GetFromStack()); + } + + /// Add an event handler. + /// The event handler. + /// The mod which added the event handler. + public void Add(EventHandler handler, IModMetadata mod) { this.Event += handler; - this.AddTracking(handler, this.Event?.GetInvocationList().Cast()); + this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast()); } /// Remove an event handler. diff --git a/src/SMAPI/Framework/Events/ManagedEventBase.cs b/src/SMAPI/Framework/Events/ManagedEventBase.cs index 7e42d613..f3a278dc 100644 --- a/src/SMAPI/Framework/Events/ManagedEventBase.cs +++ b/src/SMAPI/Framework/Events/ManagedEventBase.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI.Framework.Events private readonly IMonitor Monitor; /// The mod registry with which to identify mods. - private readonly ModRegistry ModRegistry; + protected readonly ModRegistry ModRegistry; /// The display names for the mods which added each delegate. private readonly IDictionary SourceMods = new Dictionary(); @@ -50,11 +50,12 @@ namespace StardewModdingAPI.Framework.Events } /// Track an event handler. + /// The mod which added the handler. /// The event handler. /// The updated event invocation list. - protected void AddTracking(TEventHandler handler, IEnumerable invocationList) + protected void AddTracking(IModMetadata mod, TEventHandler handler, IEnumerable invocationList) { - this.SourceMods[handler] = this.ModRegistry.GetFromStack(); + this.SourceMods[handler] = mod; this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; } @@ -64,7 +65,7 @@ namespace StardewModdingAPI.Framework.Events protected void RemoveTracking(TEventHandler handler, IEnumerable invocationList) { this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; - if(!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) + if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) this.SourceMods.Remove(handler); } diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs new file mode 100644 index 00000000..cc4cf8d7 --- /dev/null +++ b/src/SMAPI/Framework/Events/ModEvents.cs @@ -0,0 +1,26 @@ +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// Manages access to events raised by SMAPI. + internal class ModEvents : IModEvents + { + /********* + ** Accessors + *********/ + /// Events raised when something changes in the world. + public IWorldEvents World { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod which uses this instance. + /// The underlying event manager. + public ModEvents(IModMetadata mod, EventManager eventManager) + { + this.World = new ModWorldEvents(mod, eventManager); + } + } +} diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs new file mode 100644 index 00000000..a76a7eb5 --- /dev/null +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -0,0 +1,56 @@ +using System; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// Events raised when something changes in the world. + public class ModWorldEvents : IWorldEvents + { + /********* + ** Properties + *********/ + /// The underlying event manager. + private readonly EventManager EventManager; + + /// The mod which uses this instance. + private readonly IModMetadata Mod; + + + /********* + ** Accessors + *********/ + /// Raised after a game location is added or removed. + public event EventHandler LocationsChanged + { + add => this.EventManager.World_LocationsChanged.Add(value, this.Mod); + remove => this.EventManager.World_LocationsChanged.Remove(value); + } + + /// Raised after buildings are added or removed in a location. + public event EventHandler BuildingsChanged + { + add => this.EventManager.World_BuildingsChanged.Add(value, this.Mod); + remove => this.EventManager.World_BuildingsChanged.Remove(value); + } + + /// Raised after objects are added or removed in a location. + public event EventHandler ObjectsChanged + { + add => this.EventManager.World_ObjectsChanged.Add(value); + remove => this.EventManager.World_ObjectsChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod which uses this instance. + /// The underlying event manager. + internal ModWorldEvents(IModMetadata mod, EventManager eventManager) + { + this.Mod = mod; + this.EventManager = eventManager; + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 26fe7198..92cb9d94 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Framework.Utilities; @@ -33,6 +34,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The full path to the mod's folder. public string DirectoryPath { get; } + /// Manages access to events raised by SMAPI, which let your mod react when something happens in the game. + public IModEvents Events { get; } + /// An API for loading content assets. public IContentHelper Content { get; } @@ -59,6 +63,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod's unique ID. /// The full path to the mod's folder. /// Encapsulate SMAPI's JSON parsing. + /// Manages access to events raised by SMAPI. /// An API for loading content assets. /// An API for managing console commands. /// an API for fetching metadata about loaded mods. @@ -70,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Manages deprecation warnings. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) : base(modID) { // validate directory @@ -91,6 +96,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ContentPacks = contentPacks.ToArray(); this.CreateContentPack = createContentPack; this.DeprecationManager = deprecationManager; + this.Events = events; } /**** @@ -157,13 +163,13 @@ namespace StardewModdingAPI.Framework.ModHelpers this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice); // validate - if(string.IsNullOrWhiteSpace(directoryPath)) + if (string.IsNullOrWhiteSpace(directoryPath)) throw new ArgumentNullException(nameof(directoryPath)); - if(string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id)); - if(string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - if(!Directory.Exists(directoryPath)) + if (!Directory.Exists(directoryPath)) throw new ArgumentException($"Can't create content pack for directory path '{directoryPath}' because no such directory exists."); // create manifest diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 369f1f40..e7e9f74f 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -543,6 +543,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); } + this.Events.World_LocationsChanged.Raise(new WorldLocationsChangedEventArgs(added, removed)); this.Events.Location_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); } @@ -559,6 +560,7 @@ namespace StardewModdingAPI.Framework var removed = watcher.ObjectsWatcher.Removed.ToArray(); watcher.ObjectsWatcher.Reset(); + this.Events.World_ObjectsChanged.Raise(new WorldObjectsChangedEventArgs(location, added, removed)); this.Events.Location_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); } @@ -570,6 +572,7 @@ namespace StardewModdingAPI.Framework var removed = watcher.BuildingsWatcher.Removed.ToArray(); watcher.BuildingsWatcher.Reset(); + this.Events.World_BuildingsChanged.Raise(new WorldBuildingsChangedEventArgs(location, added, removed)); this.Events.Location_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); } } diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index 5e39161d..68c2f1c4 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using StardewModdingAPI.Events; namespace StardewModdingAPI { @@ -12,6 +13,10 @@ namespace StardewModdingAPI /// The full path to the mod's folder. string DirectoryPath { get; } + /// Manages access to events raised by SMAPI, which let your mod react when something happens in the game. + [Obsolete("This is an experimental interface which may change at any time. Don't depend on this for released mods.")] + IModEvents Events { get; } + /// An API for loading content assets. IContentHelper Content { get; } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 844dc5d8..48ad922b 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -840,6 +840,7 @@ namespace StardewModdingAPI IMonitor monitor = this.GetSecondaryMonitor(metadata.DisplayName); IModHelper modHelper; { + IModEvents events = new ModEvents(metadata, this.EventManager); ICommandHelper commandHelper = new CommandHelper(manifest.UniqueID, metadata.DisplayName, this.CommandManager); IContentHelper contentHelper = new ContentHelper(contentCore, metadata.DirectoryPath, manifest.UniqueID, metadata.DisplayName, monitor); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection, this.DeprecationManager); @@ -854,7 +855,7 @@ namespace StardewModdingAPI return new ContentPack(packDirPath, packManifest, packContentHelper, this.JsonHelper); } - modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager); + modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, events, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager); } // init mod diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index e9e0ea54..6a062930 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -86,17 +86,23 @@ Properties\GlobalAssemblyInfo.cs + + + + + + @@ -129,6 +135,7 @@ +