From 417c04076634ea87d7b3030a1acf46825da6e3e6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Aug 2018 01:53:35 -0400 Subject: add data API (#468) --- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 155 +++++++++++++++++++++++++++ src/SMAPI/Framework/ModHelpers/ModHelper.cs | 28 +++-- 2 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 src/SMAPI/Framework/ModHelpers/DataHelper.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs new file mode 100644 index 00000000..6ba099b4 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Utilities; +using StardewValley; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// Provides an API for reading and storing local mod data. + internal class DataHelper : BaseHelper, IDataHelper + { + /********* + ** Properties + *********/ + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + + /// The absolute path to the mod folder. + private readonly string ModFolderPath; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The unique ID of the relevant mod. + /// The absolute path to the mod folder. + /// The absolute path to the mod folder. + public DataHelper(string modID, string modFolderPath, JsonHelper jsonHelper) + : base(modID) + { + this.ModFolderPath = modFolderPath; + this.JsonHelper = jsonHelper; + } + + /**** + ** JSON file + ****/ + /// Read data from a JSON file in the mod's folder. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The file path relative to the mod folder. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + /// The is not relative or contains directory climbing (../). + public TModel ReadJsonFile(string path) where TModel : class + { + if (!PathUtilities.IsSafeRelativePath(path)) + throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.ReadJsonFile)} with a relative path."); + + path = Path.Combine(this.ModFolderPath, PathUtilities.NormalisePathSeparators(path)); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; + } + + /// Save data to a JSON file in the mod's folder. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The file path relative to the mod folder. + /// The arbitrary data to save. + /// The is not relative or contains directory climbing (../). + public void WriteJsonFile(string path, TModel data) where TModel : class + { + if (!PathUtilities.IsSafeRelativePath(path)) + throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path."); + + path = Path.Combine(this.ModFolderPath, PathUtilities.NormalisePathSeparators(path)); + this.JsonHelper.WriteJsonFile(path, data); + } + + /**** + ** Save file + ****/ + /// Read arbitrary data stored in the current save slot. This is only possible if a save has been loaded. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The unique key identifying the data. + /// Returns the parsed data, or null if the entry doesn't exist or is empty. + /// The player hasn't loaded a save file yet. + public TModel ReadSaveData(string key) where TModel : class + { + if (!Context.IsSaveLoaded) + throw new InvalidOperationException($"Can't invoke {nameof(this.ReadSaveData)} when a save file isn't loaded."); + + return Game1.CustomData.TryGetValue(this.GetSaveFileKey(key), out string value) + ? this.JsonHelper.Deserialise(value) + : null; + } + + /// Save arbitrary data to the current save slot. This is only possible if a save has been loaded, and the data will be lost if the player exits without saving the current day. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The unique key identifying the data. + /// The arbitrary data to save. + /// The player hasn't loaded a save file yet. + public void WriteSaveData(string key, TModel data) where TModel : class + { + if (!Context.IsSaveLoaded) + throw new InvalidOperationException($"Can't invoke {nameof(this.WriteSaveData)} when a save file isn't loaded."); + + Game1.CustomData[this.GetSaveFileKey(key)] = this.JsonHelper.Serialise(data, Formatting.None); + } + + /**** + ** Global app data + ****/ + /// Read arbitrary data stored on the local computer, synchronised by GOG/Steam if applicable. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The unique key identifying the data. + /// Returns the parsed data, or null if the entry doesn't exist or is empty. + public TModel ReadGlobalData(string key) where TModel : class + { + string path = this.GetGlobalDataPath(key); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; + } + + /// Save arbitrary data to the local computer, synchronised by GOG/Steam if applicable. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The unique key identifying the data. + /// The arbitrary data to save. + public void WriteGlobalData(string key, TModel data) where TModel : class + { + string path = this.GetGlobalDataPath(key); + this.JsonHelper.WriteJsonFile(path, data); + } + + + /********* + ** Public methods + *********/ + /// Get the unique key for a save file data entry. + /// The unique key identifying the data. + private string GetSaveFileKey(string key) + { + this.AssertSlug(key, nameof(key)); + return $"smapi-mod-data/{this.ModID}/{key}".ToLower(); + } + + /// Get the absolute path for a global data file. + /// The unique key identifying the data. + private string GetGlobalDataPath(string key) + { + this.AssertSlug(key, nameof(key)); + return Path.Combine(Constants.SavesPath, ".smapi-mod-data", this.ModID.ToLower(), $"{key}.json".ToLower()); + } + + /// Assert that a key contains only characters that are safe in all contexts. + /// The key to check. + /// The argument name for any assertion error. + private void AssertSlug(string key, string paramName) + { + if (!PathUtilities.IsSlug(key)) + throw new ArgumentException("The data key is invalid (keys must only contain letters, numbers, underscores, periods, or hyphens).", paramName); + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 0ba258b4..0ed3df12 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -4,9 +4,7 @@ using System.IO; using System.Linq; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; -using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Serialisation.Models; -using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { @@ -16,9 +14,6 @@ namespace StardewModdingAPI.Framework.ModHelpers /********* ** Properties *********/ - /// Encapsulates SMAPI's JSON file parsing. - private readonly JsonHelper JsonHelper; - /// The content packs loaded for this mod. private readonly IContentPack[] ContentPacks; @@ -41,6 +36,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for loading content assets. public IContentHelper Content { get; } + /// An API for reading and writing persistent mod data. + public IDataHelper Data { get; } + /// An API for checking and changing input state. public IInputHelper Input { get; } @@ -66,11 +64,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The mod's unique ID. /// The full path to the mod's folder. - /// Encapsulate SMAPI's JSON parsing. /// Manages the game's input state. /// Manages access to events raised by SMAPI. /// An API for loading content assets. /// An API for managing console commands. + /// An API for reading and writing persistent mod data. /// an API for fetching metadata about loaded mods. /// An API for accessing private game code. /// Provides multiplayer utilities. @@ -80,7 +78,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, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) + public ModHelper(string modID, string modDirectory, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) : base(modID) { // validate directory @@ -91,8 +89,8 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialise this.DirectoryPath = modDirectory; - this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper)); this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper)); this.Input = new InputHelper(modID, inputState); this.ModRegistry = modRegistry ?? throw new ArgumentNullException(nameof(modRegistry)); this.ConsoleCommands = commandHelper ?? throw new ArgumentNullException(nameof(commandHelper)); @@ -113,7 +111,7 @@ namespace StardewModdingAPI.Framework.ModHelpers public TConfig ReadConfig() where TConfig : class, new() { - TConfig config = this.ReadJsonFile("config.json") ?? new TConfig(); + TConfig config = this.Data.ReadJsonFile("config.json") ?? new TConfig(); this.WriteConfig(config); // create file or fill in missing fields return config; } @@ -124,7 +122,7 @@ namespace StardewModdingAPI.Framework.ModHelpers public void WriteConfig(TConfig config) where TConfig : class, new() { - this.WriteJsonFile("config.json", config); + this.Data.WriteJsonFile("config.json", config); } /**** @@ -134,24 +132,22 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The model type. /// The file path relative to the mod directory. /// Returns the deserialised model, or null if the file doesn't exist or is empty. + [Obsolete("Use " + nameof(ModHelper.Data) + "." + nameof(IDataHelper.ReadJsonFile) + " instead")] public TModel ReadJsonFile(string path) where TModel : class { - path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) - ? data - : null; + return this.Data.ReadJsonFile(path); } /// Save to a JSON file. /// The model type. /// The file path relative to the mod directory. /// The model to save. + [Obsolete("Use " + nameof(ModHelper.Data) + "." + nameof(IDataHelper.WriteJsonFile) + " instead")] public void WriteJsonFile(string path, TModel model) where TModel : class { - path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - this.JsonHelper.WriteJsonFile(path, model); + this.Data.WriteJsonFile(path, model); } /**** -- cgit From 5dfbae2010960b854bd470316b27423dd05fbed2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Aug 2018 22:51:30 -0400 Subject: add error when using Read/WriteSaveData when not main player (#468) --- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 6ba099b4..cdb3718f 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -61,7 +61,7 @@ namespace StardewModdingAPI.Framework.ModHelpers public void WriteJsonFile(string path, TModel data) where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) - throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path."); + throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); path = Path.Combine(this.ModFolderPath, PathUtilities.NormalisePathSeparators(path)); this.JsonHelper.WriteJsonFile(path, data); @@ -74,11 +74,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The unique key identifying the data. /// Returns the parsed data, or null if the entry doesn't exist or is empty. - /// The player hasn't loaded a save file yet. + /// The player hasn't loaded a save file yet or isn't the main player. public TModel ReadSaveData(string key) where TModel : class { if (!Context.IsSaveLoaded) - throw new InvalidOperationException($"Can't invoke {nameof(this.ReadSaveData)} when a save file isn't loaded."); + throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} when a save file isn't loaded."); + if (!Context.IsMainPlayer) + throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} because this isn't the main player. (Save files are stored on the main player's computer.)"); return Game1.CustomData.TryGetValue(this.GetSaveFileKey(key), out string value) ? this.JsonHelper.Deserialise(value) @@ -89,11 +91,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The unique key identifying the data. /// The arbitrary data to save. - /// The player hasn't loaded a save file yet. + /// The player hasn't loaded a save file yet or isn't the main player. public void WriteSaveData(string key, TModel data) where TModel : class { if (!Context.IsSaveLoaded) - throw new InvalidOperationException($"Can't invoke {nameof(this.WriteSaveData)} when a save file isn't loaded."); + throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when a save file isn't loaded."); + if (!Context.IsMainPlayer) + throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} because this isn't the main player. (Save files are stored on the main player's computer.)"); Game1.CustomData[this.GetSaveFileKey(key)] = this.JsonHelper.Serialise(data, Formatting.None); } -- cgit From 6443fb12317932c749730fa42457b7c3295cebf4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Aug 2018 02:24:30 -0400 Subject: fix deprecated Read/WriteJsonFiles method enforcing newer restrictions (#468) --- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 0ed3df12..ae0368f0 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -4,7 +4,9 @@ using System.IO; using System.Linq; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Serialisation.Models; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { @@ -30,6 +32,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The full path to the mod's folder. public string DirectoryPath { get; } + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + /// Manages access to events raised by SMAPI, which let your mod react when something happens in the game. public IModEvents Events { get; } @@ -64,6 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The mod's unique ID. /// The full path to the mod's folder. + /// Encapsulate SMAPI's JSON parsing. /// Manages the game's input state. /// Manages access to events raised by SMAPI. /// An API for loading content assets. @@ -78,7 +84,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, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) : base(modID) { // validate directory @@ -89,6 +95,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialise this.DirectoryPath = modDirectory; + this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper)); this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper)); this.Input = new InputHelper(modID, inputState); @@ -136,7 +143,10 @@ namespace StardewModdingAPI.Framework.ModHelpers public TModel ReadJsonFile(string path) where TModel : class { - return this.Data.ReadJsonFile(path); + path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; } /// Save to a JSON file. @@ -147,7 +157,8 @@ namespace StardewModdingAPI.Framework.ModHelpers public void WriteJsonFile(string path, TModel model) where TModel : class { - this.Data.WriteJsonFile(path, model); + path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); + this.JsonHelper.WriteJsonFile(path, model); } /**** -- cgit From ceac1de6ec7ed5f7ecc32cb99a665af891863657 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Aug 2018 23:03:09 -0400 Subject: change mod registry to return a container interface (#534) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 008a80f5..5cc2a20f 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -40,17 +40,17 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// Get metadata for all loaded mods. - public IEnumerable GetAll() + public IEnumerable GetAll() { - return this.Registry.GetAll().Select(p => p.Manifest); + return this.Registry.GetAll(); } /// Get metadata for a loaded mod. /// The mod's unique ID. /// Returns the matching mod's metadata, or null if not found. - public IManifest Get(string uniqueID) + public IModInfo Get(string uniqueID) { - return this.Registry.Get(uniqueID)?.Manifest; + return this.Registry.Get(uniqueID); } /// Get whether a mod has been loaded. -- cgit From 6ef7de33e8251f5d836a8ed2936939d9ce2fadb4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 23 Aug 2018 23:01:46 -0400 Subject: tweak data API keys (#468) --- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index cdb3718f..506c9c54 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -136,7 +136,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private string GetSaveFileKey(string key) { this.AssertSlug(key, nameof(key)); - return $"smapi-mod-data/{this.ModID}/{key}".ToLower(); + return $"smapi/mod-data/{this.ModID}/{key}".ToLower(); } /// Get the absolute path for a global data file. @@ -144,7 +144,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private string GetGlobalDataPath(string key) { this.AssertSlug(key, nameof(key)); - return Path.Combine(Constants.SavesPath, ".smapi-mod-data", this.ModID.ToLower(), $"{key}.json".ToLower()); + return Path.Combine(Constants.SavesPath, ".smapi", "mod-data", this.ModID.ToLower(), $"{key}.json".ToLower()); } /// Assert that a key contains only characters that are safe in all contexts. -- cgit From 73c389df74a8df796b3c1cacf2c035ac7e34a063 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Aug 2018 19:08:38 -0400 Subject: delete data API entries when they're set to null (#468) --- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 506c9c54..e5100aed 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -99,7 +99,11 @@ namespace StardewModdingAPI.Framework.ModHelpers if (!Context.IsMainPlayer) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} because this isn't the main player. (Save files are stored on the main player's computer.)"); - Game1.CustomData[this.GetSaveFileKey(key)] = this.JsonHelper.Serialise(data, Formatting.None); + string internalKey = this.GetSaveFileKey(key); + if (data != null) + Game1.CustomData[internalKey] = this.JsonHelper.Serialise(data, Formatting.None); + else + Game1.CustomData.Remove(internalKey); } /**** @@ -124,7 +128,10 @@ namespace StardewModdingAPI.Framework.ModHelpers public void WriteGlobalData(string key, TModel data) where TModel : class { string path = this.GetGlobalDataPath(key); - this.JsonHelper.WriteJsonFile(path, data); + if (data != null) + this.JsonHelper.WriteJsonFile(path, data); + else + File.Delete(path); } -- cgit From c531acb6599b4e115e8b6f6d12e9194b3f83ff9d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Sep 2018 18:30:14 -0400 Subject: fix command errors logged as SMAPI instead of the affected mod --- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index bdedb07c..5a3304f3 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -8,8 +8,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /********* ** Accessors *********/ - /// The friendly mod name for this instance. - private readonly string ModName; + /// The mod using this instance. + private readonly IModMetadata Mod; /// Manages console commands. private readonly CommandManager CommandManager; @@ -19,13 +19,12 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. - /// The friendly mod name for this instance. + /// The mod using this instance. /// Manages console commands. - public CommandHelper(string modID, string modName, CommandManager commandManager) - : base(modID) + public CommandHelper(IModMetadata mod, CommandManager commandManager) + : base(mod?.Manifest?.UniqueID ?? "SMAPI") { - this.ModName = modName; + this.Mod = mod; this.CommandManager = commandManager; } @@ -38,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// There's already a command with that name. public ICommandHelper Add(string name, string documentation, Action callback) { - this.CommandManager.Add(this.ModName, name, documentation, callback); + this.CommandManager.Add(this.Mod, name, documentation, callback); return this; } -- cgit From e5e4ce411cc5a5e5066552978517904b21900066 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 31 Oct 2018 17:29:32 -0400 Subject: sync SMAPI context between players in multiplayer (#480) --- src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index c449a51b..86f8e012 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using StardewModdingAPI.Framework.Networking; using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers @@ -36,5 +37,21 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.Multiplayer.getNewID(); } + + /// Get a connected player. + /// The player's unique ID. + /// Returns the connected player, or null if no such player is connected. + public IMultiplayerPeer GetConnectedPlayer(long id) + { + return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer peer) + ? peer + : null; + } + + /// Get all connected players. + public IEnumerable GetConnectedPlayers() + { + return this.Multiplayer.Peers.Values; + } } } -- cgit From 02a46bf13f29ce0dd8ac2f422113083c59dae42d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 3 Nov 2018 01:29:01 -0400 Subject: add APIs to send/receive messages in multiplayer (#480) --- .../Framework/ModHelpers/MultiplayerHelper.cs | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index 86f8e012..eedad0bc 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using StardewModdingAPI.Framework.Networking; using StardewValley; @@ -26,18 +27,18 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Multiplayer = multiplayer; } - /// Get the locations which are being actively synced from the host. - public IEnumerable GetActiveLocations() - { - return this.Multiplayer.activeLocations(); - } - /// Get a new multiplayer ID. public long GetNewID() { return this.Multiplayer.getNewID(); } + /// Get the locations which are being actively synced from the host. + public IEnumerable GetActiveLocations() + { + return this.Multiplayer.activeLocations(); + } + /// Get a connected player. /// The player's unique ID. /// Returns the connected player, or null if no such player is connected. @@ -53,5 +54,23 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.Multiplayer.Peers.Values; } + + /// Send a message to mods installed by connected players. + /// The data type. This can be a class with a default constructor, or a value type. + /// The data to send over the network. + /// A message type which receiving mods can use to decide whether it's the one they want to handle, like SetPlayerLocation. This doesn't need to be globally unique, since mods should check the originating mod ID. + /// The mod IDs which should receive the message on the destination computers, or null for all mods. Specifying mod IDs is recommended to improve performance, unless it's a general-purpose broadcast. + /// The values for the players who should receive the message, or null for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency. + /// The or is null. + public void SendMessage(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null) + { + this.Multiplayer.BroadcastModMessage( + message: message, + messageType: messageType, + fromModID: this.ModID, + toModIDs: modIDs, + toPlayerIDs: playerIDs + ); + } } } -- cgit From dcfae980bf74386c624b0d059a83e95ec1aedc0b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 16 Nov 2018 21:29:28 -0500 Subject: fix content packs always failing to load if they declare a dependency on a SMAPI mod --- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index ae0368f0..5e190e55 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Toolkit.Serialisation; @@ -17,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Properties *********/ /// The content packs loaded for this mod. - private readonly IContentPack[] ContentPacks; + private readonly Lazy ContentPacks; /// Create a transitional content pack. private readonly Func CreateContentPack; @@ -84,7 +83,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, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable contentPacks, Func createContentPack, DeprecationManager deprecationManager) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, Func contentPacks, Func createContentPack, DeprecationManager deprecationManager) : base(modID) { // validate directory @@ -104,7 +103,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Reflection = reflectionHelper ?? throw new ArgumentNullException(nameof(reflectionHelper)); this.Multiplayer = multiplayer ?? throw new ArgumentNullException(nameof(multiplayer)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); - this.ContentPacks = contentPacks.ToArray(); + this.ContentPacks = new Lazy(contentPacks); this.CreateContentPack = createContentPack; this.DeprecationManager = deprecationManager; this.Events = events; @@ -204,7 +203,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Get all content packs loaded for this mod. public IEnumerable GetContentPacks() { - return this.ContentPacks; + return this.ContentPacks.Value; } /**** -- cgit