From daf1d7543876f2ea0de5f0e96e32f9f2aaeae95b Mon Sep 17 00:00:00 2001 From: Falki-git <72734856+Falki-git@users.noreply.github.com> Date: Sun, 5 Nov 2023 21:47:59 +0100 Subject: [PATCH 01/18] Add API registration for mods that want to use game's built-in saving and loading of data stored in the save game files --- .../API/SaveGameManager/ModSaves.cs | 82 +++++++++++++++++ .../Backend/SaveGameManager/PluginSaveData.cs | 17 ++++ .../SpaceWarpSerializedSavedGame.cs | 13 +++ .../InternalUtilities/InternalExtensions.cs | 23 +++++ .../SaveGameManager/SaveGamePatches.cs | 89 +++++++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 SpaceWarp.Core/API/SaveGameManager/ModSaves.cs create mode 100644 SpaceWarp.Core/Backend/SaveGameManager/PluginSaveData.cs create mode 100644 SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs create mode 100644 SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs diff --git a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs new file mode 100644 index 00000000..f6597011 --- /dev/null +++ b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs @@ -0,0 +1,82 @@ +using JetBrains.Annotations; +using SpaceWarp.Backend.SaveGameManager; +using System; +using System.Collections.Generic; +using SpaceWarp.API.Logging; + +namespace SpaceWarp.API.SaveGameManager; + +[PublicAPI] +public static class ModSaves +{ + private static readonly ILogger _logger = new UnityLogSource("SpaceWarp.ModSaves"); + + internal static List InternalPluginSaveData = new(); + + /// + /// Registers your mod data for saving and loading events. + /// + /// + /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. + /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. + public static void RegisterSaveLoadGameData(string modGuid, T saveData, Action saveEventCallback, Action loadEventCallback) + { + // Create adapter functions to convert Action to CallbackFunctionDelegate + SaveGameCallbackFunctionDelegate saveCallbackAdapter = (object saveData) => + { + if (saveEventCallback != null && saveData is T data) + { + saveEventCallback(data); + } + }; + + SaveGameCallbackFunctionDelegate loadCallbackAdapter = (object saveData) => + { + if (loadEventCallback != null && saveData is T data) + { + loadEventCallback(data); + } + }; + + // Check if this GUID is already registered + if (InternalPluginSaveData.Find(p => p.ModGuid == modGuid) != null) + { + throw new ArgumentException($"Mod GUID '{modGuid}' is already registered. Skipping.", "modGuid"); + } + else + { + InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveData = saveData, SaveEventCallback = saveCallbackAdapter, LoadEventCallback = loadCallbackAdapter }); + _logger.LogInfo($"Registered '{modGuid}' for save/load events."); + } + } + + /// + /// Unregister your previously registered mod data for saving and loading. Use this if you no longer need your data to be saved and loaded. + /// + /// Your mod GUID you used when registering. + public static void UnRegisterSaveLoadGameData(string modGuid) + { + var toRemove = InternalPluginSaveData.Find(p => p.ModGuid == modGuid); + if (toRemove != null) + { + InternalPluginSaveData.Remove(toRemove); + _logger.LogInfo($"Unregistered '{modGuid}' for save/load events."); + } + } + + /// + /// Unregisters then again registers your mod data for saving and loading events + /// + /// + /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. + /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. + public static void ReregisterSaveLoadGameData(string modGuid, T saveData, Action saveEventCallback, Action loadEventCallback) + { + UnRegisterSaveLoadGameData(modGuid); + RegisterSaveLoadGameData(modGuid, saveData, saveEventCallback, loadEventCallback); + } +} diff --git a/SpaceWarp.Core/Backend/SaveGameManager/PluginSaveData.cs b/SpaceWarp.Core/Backend/SaveGameManager/PluginSaveData.cs new file mode 100644 index 00000000..768e5bf3 --- /dev/null +++ b/SpaceWarp.Core/Backend/SaveGameManager/PluginSaveData.cs @@ -0,0 +1,17 @@ +using System; + +namespace SpaceWarp.Backend.SaveGameManager; + +internal delegate void SaveGameCallbackFunctionDelegate(object data); + +[Serializable] +public class PluginSaveData +{ + public string ModGuid { get; set; } + public object SaveData { get; set; } + + [NonSerialized] + internal SaveGameCallbackFunctionDelegate SaveEventCallback; + [NonSerialized] + internal SaveGameCallbackFunctionDelegate LoadEventCallback; +} diff --git a/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs b/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs new file mode 100644 index 00000000..2ec048d0 --- /dev/null +++ b/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace SpaceWarp.Backend.SaveGameManager; + +/// +/// Extension of game's save/load data class +/// +[Serializable] +public class SpaceWarpSerializedSavedGame : KSP.Sim.SerializedSavedGame +{ + public List SerializedPluginSaveData = new(); +} diff --git a/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs index 46db62f8..0e953df8 100644 --- a/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs +++ b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs @@ -1,3 +1,5 @@ +using System.Reflection; +using System; using UnityEngine; namespace SpaceWarp.InternalUtilities; @@ -9,4 +11,25 @@ internal static void Persist(this UnityObject obj) UnityObject.DontDestroyOnLoad(obj); obj.hideFlags |= HideFlags.HideAndDontSave; } + + internal static void CopyFieldAndPropertyDataFromSourceToTargetObject(object source, object target) + { + foreach (FieldInfo field in source.GetType().GetFields()) + { + object value = field.GetValue(source); + + try + { + field.SetValue(target, value); + } + catch (FieldAccessException) + { /* some fields are constants */ } + } + + foreach (PropertyInfo property in source.GetType().GetProperties()) + { + object value = property.GetValue(source); + property.SetValue(target, value); + } + } } \ No newline at end of file diff --git a/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs new file mode 100644 index 00000000..49fcbc4f --- /dev/null +++ b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs @@ -0,0 +1,89 @@ +using HarmonyLib; +using KSP.Game.Load; +using KSP.IO; +using SpaceWarp.API.Logging; +using SpaceWarp.API.SaveGameManager; +using SpaceWarp.Backend.SaveGameManager; +using SpaceWarp.InternalUtilities; +using System; + +namespace SpaceWarp.Patching.SaveGameManager; + +[HarmonyPatch] +internal class SaveLoadPatches +{ + private static readonly ILogger _logger = new UnityLogSource("SpaceWarp.SaveLoadPatches"); + + /// SAVING /// + + [HarmonyPatch(typeof(SerializeGameDataFlowAction), MethodType.Constructor), HarmonyPostfix] + [HarmonyPatch(new Type[] { typeof(string), typeof(LoadGameData) })] + private static void InjectPluginSaveGameData(string filename, LoadGameData data, SerializeGameDataFlowAction __instance) + { + // Skip plugin data injection if there are not mods that have registered for save/load actions + if (ModSaves.InternalPluginSaveData.Count == 0) + return; + + // Take the game's LoadGameData, extend it with our own class and copy plugin save data to it + SpaceWarpSerializedSavedGame modSaveData = new(); + InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(data.SavedGame, modSaveData); + modSaveData.SerializedPluginSaveData = ModSaves.InternalPluginSaveData; + data.SavedGame = modSaveData; + + // Initiate save callback for plugins that specified a callback function + foreach (var plugin in ModSaves.InternalPluginSaveData) + { + plugin.SaveEventCallback(plugin.SaveData); + } + } + + /// LOADING /// + + [HarmonyPatch(typeof(DeserializeContentsFlowAction), "DoAction"), HarmonyPrefix] + private static bool DeserializeLoadedPluginData(Action resolve, Action reject, DeserializeContentsFlowAction __instance) + { + // Skip plugin deserialization if there are no mods that have registered for save/load actions + if (ModSaves.InternalPluginSaveData.Count == 0) + return true; + + __instance._game.UI.SetLoadingBarText(__instance.Description); + try + { + // Deserialize save data to our own class that extends game's SerializedSavedGame + SpaceWarpSerializedSavedGame serializedSavedGame = new(); + IOProvider.FromJsonFile(__instance._filename, out serializedSavedGame); + __instance._data.SavedGame = serializedSavedGame; + __instance._data.DataLength = IOProvider.GetFileSize(__instance._filename); + + // Perform plugin load data if plugin data is found in the save file + if (serializedSavedGame.SerializedPluginSaveData.Count > 0) + { + // Iterate through each plugin + foreach (var loadedData in serializedSavedGame.SerializedPluginSaveData) + { + // Match registered plugin GUID with the GUID found in the save file + var existingData = ModSaves.InternalPluginSaveData.Find(p => p.ModGuid == loadedData.ModGuid); + if (existingData == null) + { + _logger.LogWarning($"Saved data for plugin '{loadedData.ModGuid}' found during a load event, however that plugin isn't registered for save/load events. Skipping load for this plugin."); + continue; + } + + // Perform a callback if plugin specified a callback function. This is done before plugin data is actually updated. + existingData.LoadEventCallback(loadedData.SaveData); + + // Copy loaded data to the SaveData object plugin registered + InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(loadedData.SaveData, existingData.SaveData); + } + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + reject(ex.Message); + } + resolve(); + + return false; + } +} From a80cedd7603f61c0c04f6c3a88e551624d926f68 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 6 Nov 2023 14:57:11 -0500 Subject: [PATCH 02/18] Allow for per campaign configuration with `CampaignConfiguration.Bind` in the mod base plugin --- .../API/Configuration/BepInExConfigFile.cs | 4 +- .../API/Configuration/IConfigFile.cs | 5 +- .../API/Configuration/JsonConfigFile.cs | 60 ++++++++++++-- .../API/Configuration/SaveConfigEntry.cs | 81 +++++++++++++++++++ .../API/Configuration/SaveConfigFile.cs | 69 ++++++++++++++++ .../API/Mods/BaseKspLoaderSpaceWarpMod.cs | 2 + .../API/Mods/BaseSpaceWarpPlugin.cs | 2 + SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs | 2 + .../API/Mods/SpaceWarpPluginDescriptor.cs | 5 +- .../Backend/Modding/AssetOnlyMod.cs | 3 + .../Backend/Modding/BepInExModAdapter.cs | 3 + .../Backend/Modding/KspModAdapter.cs | 3 + .../Backend/Modding/PluginRegister.cs | 21 +++-- .../LoadCampaignConfiguration.cs | 31 +++++++ SpaceWarp.Core/SpaceWarpPlugin.cs | 6 +- 15 files changed, 277 insertions(+), 20 deletions(-) create mode 100644 SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs create mode 100644 SpaceWarp.Core/API/Configuration/SaveConfigFile.cs create mode 100644 SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs index 720a2ff3..87d32df0 100644 --- a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Reflection; using BepInEx.Configuration; using JetBrains.Annotations; diff --git a/SpaceWarp.Core/API/Configuration/IConfigFile.cs b/SpaceWarp.Core/API/Configuration/IConfigFile.cs index 7247c455..87aedf23 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigFile.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using JetBrains.Annotations; namespace SpaceWarp.API.Configuration; @@ -11,7 +12,7 @@ public interface IConfigFile public IConfigEntry this[string section, string key] { get; } public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = ""); - + public IReadOnlyList Sections { get; } public IReadOnlyList this[string section] { get; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs index 8dd50620..0f263a0c 100644 --- a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs @@ -16,7 +16,7 @@ public class JsonConfigFile : IConfigFile { [CanBeNull] private JObject _previousConfigObject; - private Dictionary> _currentEntries = new(); + internal Dictionary> CurrentEntries = new(); private readonly string _file; public JsonConfigFile(string file) @@ -40,11 +40,11 @@ public JsonConfigFile(string file) public void Save() { - if (!_currentEntries.Any(value => value.Value.Count > 0)) return; + if (!CurrentEntries.Any(value => value.Value.Count > 0)) return; var result = new StringBuilder(); result.AppendLine("{"); var hadPreviousSection = false; - foreach (var section in _currentEntries.Where(section => section.Value.Count > 0)) + foreach (var section in CurrentEntries.Where(section => section.Value.Count > 0)) { hadPreviousSection = DumpSection(hadPreviousSection, result, section); } @@ -128,15 +128,15 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu return true; } - public IConfigEntry this[string section, string key] => _currentEntries[section][key]; + public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") { // So now we have to check if its already bound, and/or if the previous config object has it - if (!_currentEntries.TryGetValue(section, out var previousSection)) + if (!CurrentEntries.TryGetValue(section, out var previousSection)) { previousSection = new Dictionary(); - _currentEntries.Add(section,previousSection); + CurrentEntries.Add(section,previousSection); } if (previousSection.TryGetValue(key, out var result)) @@ -173,7 +173,51 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default return previousSection[key]; } - public IReadOnlyList Sections => _currentEntries.Keys.ToList(); + public IConfigEntry Bind(Type type, string section, string key, object defaultValue = default, + string description = "") + { + // So now we have to check if its already bound, and/or if the previous config object has it + if (!CurrentEntries.TryGetValue(section, out var previousSection)) + { + previousSection = new Dictionary(); + CurrentEntries.Add(section,previousSection); + } + + if (previousSection.TryGetValue(key, out var result)) + { + return result; + } + + if (_previousConfigObject != null && _previousConfigObject.TryGetValue(section, out var sect)) + { + try + { + if (sect is JObject obj && obj.TryGetValue(key, out var value)) + { + var previousValue = value.ToObject(type); + previousSection[key] = new JsonConfigEntry(this, type, description, previousValue); + } + else + { + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + } + } + catch + { + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + // ignored + } + } + else + { + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + } + + Save(); + return previousSection[key]; + } + + public IReadOnlyList Sections => CurrentEntries.Keys.ToList(); - public IReadOnlyList this[string section] => _currentEntries[section].Keys.ToList(); + public IReadOnlyList this[string section] => CurrentEntries[section].Keys.ToList(); } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs new file mode 100644 index 00000000..bf704cce --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs @@ -0,0 +1,81 @@ +using System; +using JetBrains.Annotations; + +namespace SpaceWarp.API.Configuration; + +public class SaveConfigEntry : IConfigEntry +{ + public IConfigEntry BoundEntry; + + internal object InMemoryValue; + internal object DefaultValue; + + public SaveConfigEntry(string section, string key, string description, Type valueType, object defaultValue) + { + Section = section; + Key = key; + Description = description; + ValueType = valueType; + DefaultValue = defaultValue; + InMemoryValue = defaultValue; + } + + public object Value + { + get + { + return BoundEntry != null ? BoundEntry.Value : InMemoryValue; + } + set + { + if (BoundEntry != null) + { + BoundEntry.Value = value; + } + else + { + InMemoryValue = value; + } + } + } + + public Type ValueType { get; } + public T Get() where T : class + { + + if (!typeof(T).IsAssignableFrom(ValueType)) + { + throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); + } + + return Value as T; + } + + public void Set(T value) + { + if (!ValueType.IsAssignableFrom(typeof(T))) + { + throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); + } + + Value = Convert.ChangeType(value, ValueType); + } + + public string Section { get; } + public string Key { get; } + + public string Description { get; } + + // The moment we bind to a new config file, reset the defaults + internal void Bind(JsonConfigFile newConfigFile) + { + BoundEntry = newConfigFile.Bind(ValueType, Section, Key, InMemoryValue, Description); + InMemoryValue = DefaultValue; + } + + internal void Reset() + { + BoundEntry = null; + InMemoryValue = DefaultValue; + } +} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs b/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs new file mode 100644 index 00000000..dd92d64b --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace SpaceWarp.API.Configuration; + +public class SaveConfigFile : IConfigFile +{ + [CanBeNull] private JsonConfigFile _internalConfigFile; + internal Dictionary> CurrentEntries = new(); + + public JsonConfigFile CurrentConfigFile + { + get => _internalConfigFile; + set + { + if (_internalConfigFile == value) return; + Save(); + _internalConfigFile = value; + RebindConfigFile(); + } + } + + private void RebindConfigFile() + { + foreach (var subEntry in CurrentEntries.SelectMany(entry => entry.Value)) + { + if (_internalConfigFile != null) + subEntry.Value.Bind(_internalConfigFile); + else + subEntry.Value.Reset(); + } + } + public void Save() + { + if (CurrentConfigFile != null) + { + CurrentConfigFile.Save(); + } + } + + public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; + + public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") + { + if (!CurrentEntries.TryGetValue(section, out var previousSection)) + { + previousSection = new Dictionary(); + CurrentEntries.Add(section,previousSection); + } + if (previousSection.TryGetValue(key, out var result)) + { + return result; + } + + var entry = new SaveConfigEntry(section, key, description, typeof(T), defaultValue); + if (_internalConfigFile != null) entry.Bind(_internalConfigFile); + previousSection[key] = entry; + return entry; + } + + public IReadOnlyList Sections => CurrentEntries.Keys.ToList(); + + public IReadOnlyList this[string section] => CurrentEntries[section].Keys.ToList(); + + public void ResetToDefaults() + { + CurrentConfigFile = null; + } +} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs index 0127632e..422df16b 100644 --- a/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs +++ b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs @@ -32,4 +32,6 @@ public IConfigFile SWConfiguration { } public SpaceWarpPluginDescriptor SWMetadata { get; set; } + private SaveConfigFile _saveConfig; + public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs index 2f7551f8..e9e7f5d0 100644 --- a/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs +++ b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs @@ -64,6 +64,8 @@ public virtual void OnPostInitialized() public ILogger SWLogger => _logger ??= new BepInExLogger(Logger); private BepInExConfigFile _configFile; public IConfigFile SWConfiguration => _configFile ??= new BepInExConfigFile(Config); + private SaveConfigFile _saveConfig; + public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } internal static string GetGuidBySpec(PluginInfo pluginInfo, ModInfo modInfo) diff --git a/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs index c2f9d446..36bb39f6 100644 --- a/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs +++ b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs @@ -17,5 +17,7 @@ public interface ISpaceWarpMod public IConfigFile SWConfiguration { get; } + public SaveConfigFile CampaignConfiguration { get; } + public SpaceWarpPluginDescriptor SWMetadata { get; set; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs index fdf7edfb..207cbb73 100644 --- a/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs +++ b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs @@ -15,7 +15,8 @@ public SpaceWarpPluginDescriptor( ModInfo swInfo, DirectoryInfo folder, bool doLoadingActions = true, - [CanBeNull] IConfigFile configFile = null + [CanBeNull] IConfigFile configFile = null, + [CanBeNull] SaveConfigFile saveConfigFile = null ) { Plugin = plugin; @@ -25,6 +26,7 @@ public SpaceWarpPluginDescriptor( Folder = folder; DoLoadingActions = doLoadingActions; ConfigFile = configFile; + SaveConfigFile = saveConfigFile; } [CanBeNull] public ISpaceWarpMod Plugin; @@ -34,6 +36,7 @@ public SpaceWarpPluginDescriptor( public readonly DirectoryInfo Folder; public bool DoLoadingActions; [CanBeNull] public IConfigFile ConfigFile; + [CanBeNull] public SaveConfigFile SaveConfigFile; // Set by the version checking system public bool Outdated; diff --git a/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs index cd34c581..de6041f0 100644 --- a/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs +++ b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs @@ -27,4 +27,7 @@ public void OnPostInitialized() public ILogger SWLogger { get; } public IConfigFile SWConfiguration => new EmptyConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } + + private SaveConfigFile _saveConfig; + public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs index 84f61bda..641fa4a2 100644 --- a/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs +++ b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs @@ -29,4 +29,7 @@ public BepInExModAdapter(BaseUnityPlugin plugin) { Plugin = plugin; } + + private SaveConfigFile _saveConfig; + public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs index 2e85d23c..2cae25b3 100644 --- a/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs +++ b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs @@ -33,4 +33,7 @@ public void Update() public ILogger SWLogger { get; private set; } public IConfigFile SWConfiguration => new EmptyConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } + + private SaveConfigFile _saveConfig; + public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/PluginRegister.cs b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs index 88d24c1c..82c3e94c 100644 --- a/SpaceWarp.Core/Backend/Modding/PluginRegister.cs +++ b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs @@ -83,7 +83,8 @@ private static bool AssertFolderPath(BaseSpaceWarpPlugin plugin, string folderPa BepInExToSWInfo(plugin.Info), new DirectoryInfo(folderPath), false, - new BepInExConfigFile(plugin.Config) + new BepInExConfigFile(plugin.Config), + plugin.CampaignConfiguration ); PluginList.NoteBadDirectoryError(descriptor); return false; @@ -243,7 +244,8 @@ private static void RegisterSingleSpaceWarpPlugin(BaseSpaceWarpPlugin plugin) metadata, directoryInfo, true, - new BepInExConfigFile(plugin.Config) + plugin.SWConfiguration, + plugin.CampaignConfiguration ); descriptor.Plugin!.SWMetadata = descriptor; if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath)) return; @@ -264,14 +266,16 @@ private static void RegisterSingleBepInExPlugin(BaseUnityPlugin plugin) if (File.Exists(modInfoPath)) { if (!TryReadModInfo(plugin, modInfoPath, folderPath, out var metadata)) return; + var adapter = new BepInExModAdapter(plugin); var descriptor = new SpaceWarpPluginDescriptor( - new BepInExModAdapter(plugin), + adapter, metadata.Spec != SpecVersion.V1_2 ? metadata.ModID : plugin.Info.Metadata.GUID, metadata.Name, metadata, directoryInfo, false, - new BepInExConfigFile(plugin.Config) + adapter.SWConfiguration, + adapter.CampaignConfiguration ); descriptor.Plugin!.SWMetadata = descriptor; if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath)) @@ -294,7 +298,8 @@ private static SpaceWarpPluginDescriptor GetBepInExDescriptor(BaseUnityPlugin pl BepInExToSWInfo(plugin.Info), new DirectoryInfo(Path.GetDirectoryName(plugin.Info.Location)!), false, - new BepInExConfigFile(plugin.Config) + pluginAdapter.SWConfiguration, + pluginAdapter.CampaignConfiguration ); pluginAdapter.SWMetadata = descriptor; return descriptor; @@ -350,13 +355,15 @@ private static void RegisterAllCodelessMods() continue; } + var assetMod = new AssetOnlyMod(swinfoData.Name); var descriptor = new SpaceWarpPluginDescriptor( - new AssetOnlyMod(swinfoData.Name), + assetMod, swinfoData.ModID, swinfoData.Name, swinfoData, swinfo.Directory, true, - new BepInExConfigFile(FindOrCreateConfigFile(swinfoData.ModID)) + new BepInExConfigFile(FindOrCreateConfigFile(swinfoData.ModID)), + assetMod.CampaignConfiguration ); descriptor.Plugin!.SWMetadata = descriptor; diff --git a/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs new file mode 100644 index 00000000..486032c7 --- /dev/null +++ b/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; +using KSP.Game; +using KSP.Game.Flow; +using KSP.Game.Load; +using SpaceWarp.API.Configuration; +using SpaceWarp.API.Mods; + +namespace SpaceWarp.Patching.LoadingActions; + +public class LoadCampaignConfiguration : FlowAction +{ + public LoadCampaignConfiguration() : base("Loading per campaign mod configuration") + { + } + + public override void DoAction(Action resolve, Action reject) + { + var path = + $"{GameManager.Instance.Game.SaveLoadManager.ActiveCampaignFolderPath}{Path.DirectorySeparatorChar}mod_data"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + foreach (var mod in PluginList.AllEnabledAndActivePlugins) + { + if (mod.SaveConfigFile == null || mod.SaveConfigFile.Sections.Count == 0) continue; + var newConfigFile = new JsonConfigFile($"{path}{Path.DirectorySeparatorChar}{mod.Guid}.cfg"); + mod.SaveConfigFile.CurrentConfigFile = newConfigFile; + } + + resolve(); + } +} \ No newline at end of file diff --git a/SpaceWarp.Core/SpaceWarpPlugin.cs b/SpaceWarp.Core/SpaceWarpPlugin.cs index 3aede271..985c9aed 100644 --- a/SpaceWarp.Core/SpaceWarpPlugin.cs +++ b/SpaceWarp.Core/SpaceWarpPlugin.cs @@ -4,6 +4,7 @@ using System.IO; using System.Reflection; using BepInEx; +using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using I2.Loc; @@ -12,6 +13,7 @@ using KSP.ScriptInterop.impl.moonsharp; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; +using SpaceWarp.API.Configuration; using SpaceWarp.API.Loading; using SpaceWarp.API.Lua; using SpaceWarp.API.Mods; @@ -38,7 +40,7 @@ public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin internal ScriptEnvironment GlobalLuaState; internal new static ManualLogSource Logger; - + public SpaceWarpPlugin() { // Load the type forwarders @@ -101,6 +103,8 @@ public override void OnPreInitialized() { // Persist all game objects so I don't need to stomp on config ModuleManager.PreInitializeAllModules(); + // Start loading campaign configuration data + SaveLoad.AddFlowActionToCampaignLoadAfter("Deserializing Save File Contents"); } public override void OnInitialized() From f74ad7c93545f05edb8bc0a58cb354a0f9170728 Mon Sep 17 00:00:00 2001 From: Falki-git <72734856+Falki-git@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:46:47 +0100 Subject: [PATCH 03/18] Registration returns T; T saveData is optional, default value of T is created if parameter is omitted --- .../API/SaveGameManager/ModSaves.cs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs index f6597011..0c98189e 100644 --- a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs +++ b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs @@ -16,27 +16,28 @@ public static class ModSaves /// /// Registers your mod data for saving and loading events. /// - /// - /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Any object + /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. - /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. - /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. - public static void RegisterSaveLoadGameData(string modGuid, T saveData, Action saveEventCallback, Action loadEventCallback) + /// T saveData object you passed as a parameter, or a default instance of object T if you didn't pass anything + public static T RegisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default) { // Create adapter functions to convert Action to CallbackFunctionDelegate SaveGameCallbackFunctionDelegate saveCallbackAdapter = (object saveData) => { - if (saveEventCallback != null && saveData is T data) + if (onSave != null && saveData is T data) { - saveEventCallback(data); + onSave(data); } }; SaveGameCallbackFunctionDelegate loadCallbackAdapter = (object saveData) => { - if (loadEventCallback != null && saveData is T data) + if (onLoad != null && saveData is T data) { - loadEventCallback(data); + onLoad(data); } }; @@ -47,8 +48,12 @@ public static void RegisterSaveLoadGameData(string modGuid, T saveData, Actio } else { - InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveData = saveData, SaveEventCallback = saveCallbackAdapter, LoadEventCallback = loadCallbackAdapter }); + if (saveData == null) + saveData = Activator.CreateInstance(); + + InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveEventCallback = saveCallbackAdapter, LoadEventCallback = loadCallbackAdapter, SaveData = saveData }); _logger.LogInfo($"Registered '{modGuid}' for save/load events."); + return saveData; } } @@ -69,14 +74,15 @@ public static void UnRegisterSaveLoadGameData(string modGuid) /// /// Unregisters then again registers your mod data for saving and loading events /// - /// - /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Any object + /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. + /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. - /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. - /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. - public static void ReregisterSaveLoadGameData(string modGuid, T saveData, Action saveEventCallback, Action loadEventCallback) + /// T saveData object you passed as a parameter, or a default instance of object T if you didn't pass anything + public static T ReregisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default(T)) { UnRegisterSaveLoadGameData(modGuid); - RegisterSaveLoadGameData(modGuid, saveData, saveEventCallback, loadEventCallback); + return RegisterSaveLoadGameData(modGuid, onSave, onLoad, saveData); } } From 219d8ff460ae4f3f03d61ce879de4071d7e7dd2f Mon Sep 17 00:00:00 2001 From: Falki-git <72734856+Falki-git@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:57:41 +0100 Subject: [PATCH 04/18] Typo --- SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs index 49fcbc4f..13c032d3 100644 --- a/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs +++ b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs @@ -20,7 +20,7 @@ internal class SaveLoadPatches [HarmonyPatch(new Type[] { typeof(string), typeof(LoadGameData) })] private static void InjectPluginSaveGameData(string filename, LoadGameData data, SerializeGameDataFlowAction __instance) { - // Skip plugin data injection if there are not mods that have registered for save/load actions + // Skip plugin data injection if there are no mods that have registered for save/load actions if (ModSaves.InternalPluginSaveData.Count == 0) return; From 6338e33eba7ef8f74412f9cf3555710c68574855 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 08:34:22 -0500 Subject: [PATCH 05/18] Reset bindings for campaign configuration when returning to main menu --- SpaceWarp.Core/SpaceWarpPlugin.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/SpaceWarp.Core/SpaceWarpPlugin.cs b/SpaceWarp.Core/SpaceWarpPlugin.cs index 985c9aed..966feceb 100644 --- a/SpaceWarp.Core/SpaceWarpPlugin.cs +++ b/SpaceWarp.Core/SpaceWarpPlugin.cs @@ -9,7 +9,9 @@ using HarmonyLib; using I2.Loc; using JetBrains.Annotations; +using KSP.Game; using KSP.IO; +using KSP.Messages; using KSP.ScriptInterop.impl.moonsharp; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; @@ -40,7 +42,8 @@ public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin internal ScriptEnvironment GlobalLuaState; internal new static ManualLogSource Logger; - + + public SpaceWarpPlugin() { // Load the type forwarders @@ -111,6 +114,20 @@ public override void OnInitialized() { ModuleManager.InitializeAllModules(); SetupLuaState(); + Game.Messages.Subscribe(typeof(GameStateEnteredMessage), ResetToDefaults, false, true); + } + + private void ResetToDefaults(MessageCenterMessage msg) + { + var stateChange = msg as GameStateEnteredMessage; + if (stateChange!.StateBeingEntered != GameState.MainMenu) return; + foreach (var plugin in PluginList.AllEnabledAndActivePlugins) + { + if (plugin.SaveConfigFile != null && plugin.SaveConfigFile.Sections.Count > 0) + { + plugin.SaveConfigFile.ResetToDefaults(); + } + } } public override void OnPostInitialized() From 9495532daf9c5a0effcb54155ab42fcb79260b97 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 08:45:50 -0500 Subject: [PATCH 06/18] Cleanup game data saving code --- .../API/SaveGameManager/ModSaves.cs | 39 ++++++++----------- .../SpaceWarpSerializedSavedGame.cs | 3 +- .../SaveGameManager/SaveGamePatches.cs | 6 +-- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs index 0c98189e..0df14f29 100644 --- a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs +++ b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs @@ -9,7 +9,7 @@ namespace SpaceWarp.API.SaveGameManager; [PublicAPI] public static class ModSaves { - private static readonly ILogger _logger = new UnityLogSource("SpaceWarp.ModSaves"); + private static readonly ILogger Logger = new UnityLogSource("SpaceWarp.ModSaves"); internal static List InternalPluginSaveData = new(); @@ -25,36 +25,33 @@ public static class ModSaves public static T RegisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default) { // Create adapter functions to convert Action to CallbackFunctionDelegate - SaveGameCallbackFunctionDelegate saveCallbackAdapter = (object saveData) => + void SaveCallbackAdapter(object dataToBeSaved) { - if (onSave != null && saveData is T data) + if (onSave != null && dataToBeSaved is T data) { onSave(data); } - }; + } - SaveGameCallbackFunctionDelegate loadCallbackAdapter = (object saveData) => + void LoadCallbackAdapter(object dataToBeLoaded) { - if (onLoad != null && saveData is T data) + if (onLoad != null && dataToBeLoaded is T data) { onLoad(data); } - }; + } // Check if this GUID is already registered if (InternalPluginSaveData.Find(p => p.ModGuid == modGuid) != null) { - throw new ArgumentException($"Mod GUID '{modGuid}' is already registered. Skipping.", "modGuid"); + throw new ArgumentException($"Mod GUID '{modGuid}' is already registered. Skipping.", nameof(modGuid)); } - else - { - if (saveData == null) - saveData = Activator.CreateInstance(); - InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveEventCallback = saveCallbackAdapter, LoadEventCallback = loadCallbackAdapter, SaveData = saveData }); - _logger.LogInfo($"Registered '{modGuid}' for save/load events."); - return saveData; - } + saveData ??= Activator.CreateInstance(); + + InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveEventCallback = SaveCallbackAdapter, LoadEventCallback = LoadCallbackAdapter, SaveData = saveData }); + Logger.LogInfo($"Registered '{modGuid}' for save/load events."); + return saveData; } /// @@ -64,11 +61,9 @@ public static T RegisterSaveLoadGameData(string modGuid, Action onSave, Ac public static void UnRegisterSaveLoadGameData(string modGuid) { var toRemove = InternalPluginSaveData.Find(p => p.ModGuid == modGuid); - if (toRemove != null) - { - InternalPluginSaveData.Remove(toRemove); - _logger.LogInfo($"Unregistered '{modGuid}' for save/load events."); - } + if (toRemove == null) return; + InternalPluginSaveData.Remove(toRemove); + Logger.LogInfo($"Unregistered '{modGuid}' for save/load events."); } /// @@ -83,6 +78,6 @@ public static void UnRegisterSaveLoadGameData(string modGuid) public static T ReregisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default(T)) { UnRegisterSaveLoadGameData(modGuid); - return RegisterSaveLoadGameData(modGuid, onSave, onLoad, saveData); + return RegisterSaveLoadGameData(modGuid, onSave, onLoad, saveData); } } diff --git a/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs b/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs index 2ec048d0..a777997c 100644 --- a/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs +++ b/SpaceWarp.Core/Backend/SaveGameManager/SpaceWarpSerializedSavedGame.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using UnityEngine.Serialization; namespace SpaceWarp.Backend.SaveGameManager; @@ -9,5 +10,5 @@ namespace SpaceWarp.Backend.SaveGameManager; [Serializable] public class SpaceWarpSerializedSavedGame : KSP.Sim.SerializedSavedGame { - public List SerializedPluginSaveData = new(); + public List serializedPluginSaveData = new(); } diff --git a/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs index 13c032d3..5dad1816 100644 --- a/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs +++ b/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs @@ -27,7 +27,7 @@ private static void InjectPluginSaveGameData(string filename, LoadGameData data, // Take the game's LoadGameData, extend it with our own class and copy plugin save data to it SpaceWarpSerializedSavedGame modSaveData = new(); InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(data.SavedGame, modSaveData); - modSaveData.SerializedPluginSaveData = ModSaves.InternalPluginSaveData; + modSaveData.serializedPluginSaveData = ModSaves.InternalPluginSaveData; data.SavedGame = modSaveData; // Initiate save callback for plugins that specified a callback function @@ -56,10 +56,10 @@ private static bool DeserializeLoadedPluginData(Action resolve, Action r __instance._data.DataLength = IOProvider.GetFileSize(__instance._filename); // Perform plugin load data if plugin data is found in the save file - if (serializedSavedGame.SerializedPluginSaveData.Count > 0) + if (serializedSavedGame.serializedPluginSaveData.Count > 0) { // Iterate through each plugin - foreach (var loadedData in serializedSavedGame.SerializedPluginSaveData) + foreach (var loadedData in serializedSavedGame.serializedPluginSaveData) { // Match registered plugin GUID with the GUID found in the save file var existingData = ModSaves.InternalPluginSaveData.Find(p => p.ModGuid == loadedData.ModGuid); From 8622e67bb6d014a27ca7775eb06933a734d2fab4 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 08:46:37 -0500 Subject: [PATCH 07/18] A little bit more cleanup --- .../API/SaveGameManager/ModSaves.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs index 0df14f29..ccd338c8 100644 --- a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs +++ b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs @@ -24,15 +24,18 @@ public static class ModSaves /// T saveData object you passed as a parameter, or a default instance of object T if you didn't pass anything public static T RegisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default) { - // Create adapter functions to convert Action to CallbackFunctionDelegate - void SaveCallbackAdapter(object dataToBeSaved) + // Check if this GUID is already registered + if (InternalPluginSaveData.Find(p => p.ModGuid == modGuid) != null) { - if (onSave != null && dataToBeSaved is T data) - { - onSave(data); - } + throw new ArgumentException($"Mod GUID '{modGuid}' is already registered. Skipping.", nameof(modGuid)); } + saveData ??= Activator.CreateInstance(); + + InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveEventCallback = SaveCallbackAdapter, LoadEventCallback = LoadCallbackAdapter, SaveData = saveData }); + Logger.LogInfo($"Registered '{modGuid}' for save/load events."); + return saveData; + void LoadCallbackAdapter(object dataToBeLoaded) { if (onLoad != null && dataToBeLoaded is T data) @@ -41,17 +44,14 @@ void LoadCallbackAdapter(object dataToBeLoaded) } } - // Check if this GUID is already registered - if (InternalPluginSaveData.Find(p => p.ModGuid == modGuid) != null) + // Create adapter functions to convert Action to CallbackFunctionDelegate + void SaveCallbackAdapter(object dataToBeSaved) { - throw new ArgumentException($"Mod GUID '{modGuid}' is already registered. Skipping.", nameof(modGuid)); + if (onSave != null && dataToBeSaved is T data) + { + onSave(data); + } } - - saveData ??= Activator.CreateInstance(); - - InternalPluginSaveData.Add(new PluginSaveData { ModGuid = modGuid, SaveEventCallback = SaveCallbackAdapter, LoadEventCallback = LoadCallbackAdapter, SaveData = saveData }); - Logger.LogInfo($"Registered '{modGuid}' for save/load events."); - return saveData; } /// From c8285183265a501865e55d63c302af842c055319 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 08:48:16 -0500 Subject: [PATCH 08/18] Make the onSave/onLoad parameters default to to null --- SpaceWarp.Core/API/SaveGameManager/ModSaves.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs index ccd338c8..792d0ee1 100644 --- a/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs +++ b/SpaceWarp.Core/API/SaveGameManager/ModSaves.cs @@ -18,11 +18,11 @@ public static class ModSaves /// /// Any object /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. - /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. - /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a SAVE event is triggered. Defaults to null or no callback. + /// Function that will execute when a LOAD event is triggered. Defaults to null or no callback. /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. /// T saveData object you passed as a parameter, or a default instance of object T if you didn't pass anything - public static T RegisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default) + public static T RegisterSaveLoadGameData(string modGuid, Action onSave = null, Action onLoad = null, T saveData = default) { // Check if this GUID is already registered if (InternalPluginSaveData.Find(p => p.ModGuid == modGuid) != null) @@ -71,11 +71,11 @@ public static void UnRegisterSaveLoadGameData(string modGuid) /// /// Any object /// Your mod GUID. Or, technically, any kind of string can be passed here, but what is mandatory is that it's unique compared to what other mods will use. - /// Function that will execute when a SAVE event is triggered. 'NULL' is also valid here if you don't need a callback. - /// Function that will execute when a LOAD event is triggered. 'NULL' is also valid here if you don't need a callback. + /// Function that will execute when a SAVE event is triggered. Defaults to null or no callback. + /// Function that will execute when a LOAD event is triggered. Defaults to null or no callback. /// Your object that will be saved to a save file during a save event and that will be updated when a load event pulls new data. Ensure that a new instance of this object is NOT created after registration. /// T saveData object you passed as a parameter, or a default instance of object T if you didn't pass anything - public static T ReregisterSaveLoadGameData(string modGuid, Action onSave, Action onLoad, T saveData = default(T)) + public static T ReregisterSaveLoadGameData(string modGuid, Action onSave = null, Action onLoad = null, T saveData = default(T)) { UnRegisterSaveLoadGameData(modGuid); return RegisterSaveLoadGameData(modGuid, onSave, onLoad, saveData); From d2e080464bda698bd7daf4708ccec140c155c988 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 09:15:29 -0500 Subject: [PATCH 09/18] Start work on Value Constraints --- .../API/Configuration/BepInExConfigEntry.cs | 8 +++- .../API/Configuration/BepInExConfigFile.cs | 6 +++ .../API/Configuration/EmptyConfigFile.cs | 5 ++ .../API/Configuration/IConfigEntry.cs | 2 + .../API/Configuration/IConfigFile.cs | 3 ++ .../API/Configuration/IValueConstraint.cs | 9 ++++ .../API/Configuration/JsonConfigEntry.cs | 10 +++- .../API/Configuration/JsonConfigFile.cs | 28 +++++++---- .../API/Configuration/ListConstraint.cs | 47 +++++++++++++++++++ .../API/Configuration/RangeConstraint.cs | 30 ++++++++++++ .../API/Configuration/SaveConfigEntry.cs | 10 +++- .../API/Configuration/SaveConfigFile.cs | 7 ++- .../API/Configuration/ValueConstraint.cs | 19 ++++++++ 13 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 SpaceWarp.Core/API/Configuration/IValueConstraint.cs create mode 100644 SpaceWarp.Core/API/Configuration/ListConstraint.cs create mode 100644 SpaceWarp.Core/API/Configuration/RangeConstraint.cs create mode 100644 SpaceWarp.Core/API/Configuration/ValueConstraint.cs diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs index b84977f6..682aef50 100644 --- a/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs @@ -9,9 +9,10 @@ public class BepInExConfigEntry : IConfigEntry { public readonly ConfigEntryBase EntryBase; - public BepInExConfigEntry(ConfigEntryBase entryBase) + public BepInExConfigEntry(ConfigEntryBase entryBase, IValueConstraint constraint = null) { EntryBase = entryBase; + Constraint = constraint; } public object Value @@ -38,8 +39,13 @@ public void Set(T value) throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); } + if (Constraint != null) + { + if (!Constraint.IsConstrained(value)) return; + } EntryBase.BoxedValue = Convert.ChangeType(value, ValueType); } public string Description => EntryBase.Description.Description; + public IValueConstraint Constraint { get; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs index 87d32df0..feb20e8d 100644 --- a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs @@ -29,6 +29,12 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default return new BepInExConfigEntry(AdaptedConfigFile.Bind(section, key, defaultValue, description)); } + public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) + { + return new BepInExConfigEntry(AdaptedConfigFile.Bind(new ConfigDefinition(section, key), defaultValue, + new ConfigDescription(description, valueConstraint.ToAcceptableValueBase()))); + } + public IReadOnlyList Sections => AdaptedConfigFile.Keys.Select(x => x.Section).Distinct().ToList(); public IReadOnlyList this[string section] => AdaptedConfigFile.Keys.Where(x => x.Section == section) diff --git a/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs b/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs index 7be78914..503d8914 100644 --- a/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs @@ -17,6 +17,11 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default throw new System.NotImplementedException(); } + public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) + { + throw new System.NotImplementedException(); + } + public IReadOnlyList Sections => new List(); public IReadOnlyList this[string section] => throw new KeyNotFoundException($"{section}"); diff --git a/SpaceWarp.Core/API/Configuration/IConfigEntry.cs b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs index 36dba68d..72c67051 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs @@ -12,4 +12,6 @@ public interface IConfigEntry public void Set(T value); public string Description { get; } + + public IValueConstraint Constraint { get; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/IConfigFile.cs b/SpaceWarp.Core/API/Configuration/IConfigFile.cs index 87aedf23..6eae904e 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigFile.cs @@ -12,6 +12,9 @@ public interface IConfigFile public IConfigEntry this[string section, string key] { get; } public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = ""); + + public IConfigEntry Bind(string section, string key, T defaultValue, string description, + IValueConstraint valueConstraint); public IReadOnlyList Sections { get; } public IReadOnlyList this[string section] { get; } diff --git a/SpaceWarp.Core/API/Configuration/IValueConstraint.cs b/SpaceWarp.Core/API/Configuration/IValueConstraint.cs new file mode 100644 index 00000000..4256ceba --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/IValueConstraint.cs @@ -0,0 +1,9 @@ +using BepInEx.Configuration; + +namespace SpaceWarp.API.Configuration; + +public interface IValueConstraint +{ + public bool IsConstrained(object o); + public AcceptableValueBase ToAcceptableValueBase(); +} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs index 07453942..07433c28 100644 --- a/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs @@ -8,11 +8,13 @@ public class JsonConfigEntry : IConfigEntry { private readonly JsonConfigFile _configFile; private object _value; + - public JsonConfigEntry(JsonConfigFile configFile, Type type, string description, object value) + public JsonConfigEntry(JsonConfigFile configFile, Type type, string description, object value, IValueConstraint constraint = null) { _configFile = configFile; _value = value; + Constraint = constraint; Description = description; ValueType = type; } @@ -45,9 +47,13 @@ public void Set(T value) { throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); } - + if (Constraint != null) + { + if (!Constraint.IsConstrained(value)) return; + } Value = Convert.ChangeType(value, ValueType); } public string Description { get; } + public IValueConstraint Constraint { get; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs index 0f263a0c..a75f4f64 100644 --- a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs @@ -103,6 +103,11 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu } } + if (entry.Value.Constraint != null) + { + result.AppendLine($" // Accepts: {entry.Value.Constraint}"); + } + var serialized = JsonConvert.SerializeObject(entry.Value.Value,Formatting.Indented,DefaultConverters.ToArray()); var serializedLines = serialized.Split('\n').Select(x => x.TrimEnd()).ToArray(); if (serializedLines.Length > 1) @@ -131,6 +136,11 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") + { + return Bind(section, key, defaultValue, description, null); + } + + public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) { // So now we have to check if its already bound, and/or if the previous config object has it if (!CurrentEntries.TryGetValue(section, out var previousSection)) @@ -151,22 +161,22 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default if (sect is JObject obj && obj.TryGetValue(key, out var value)) { var previousValue = value.ToObject(typeof(T)); - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, previousValue); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, previousValue, valueConstraint); } else { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); } } catch { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); // ignored } } else { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); } Save(); @@ -174,7 +184,7 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default } public IConfigEntry Bind(Type type, string section, string key, object defaultValue = default, - string description = "") + string description = "", IValueConstraint constraint = null) { // So now we have to check if its already bound, and/or if the previous config object has it if (!CurrentEntries.TryGetValue(section, out var previousSection)) @@ -195,22 +205,22 @@ public IConfigEntry Bind(Type type, string section, string key, object defaultVa if (sect is JObject obj && obj.TryGetValue(key, out var value)) { var previousValue = value.ToObject(type); - previousSection[key] = new JsonConfigEntry(this, type, description, previousValue); + previousSection[key] = new JsonConfigEntry(this, type, description, previousValue,constraint); } else { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); } } catch { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); // ignored } } else { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue); + previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); } Save(); diff --git a/SpaceWarp.Core/API/Configuration/ListConstraint.cs b/SpaceWarp.Core/API/Configuration/ListConstraint.cs new file mode 100644 index 00000000..b9610ee4 --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/ListConstraint.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BepInEx.Configuration; +using JetBrains.Annotations; + +namespace SpaceWarp.API.Configuration; + +public class ListConstraint : ValueConstraint where T : IEquatable +{ + [PublicAPI] + public List AcceptableValues; + + public ListConstraint(IEnumerable acceptableValues) + { + AcceptableValues = acceptableValues.ToList(); + } + + public ListConstraint(params T[] acceptableValues) + { + AcceptableValues = acceptableValues.ToList(); + } + + public override bool IsValid(T o) => AcceptableValues.Any(x => x.Equals(o)); + public override AcceptableValueBase ToAcceptableValueBase() + { + return new AcceptableValueList(AcceptableValues.ToArray()); + } + + public override string ToString() + { + StringBuilder sb = new(); + sb.Append("["); + for (int i = 0; i < AcceptableValues.Count; i++) + { + if (i != 0) + { + sb.Append(", "); + } + + sb.Append(AcceptableValues[i]); + } + + sb.Append("]"); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/RangeConstraint.cs b/SpaceWarp.Core/API/Configuration/RangeConstraint.cs new file mode 100644 index 00000000..00fbc08d --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/RangeConstraint.cs @@ -0,0 +1,30 @@ +using System; +using BepInEx.Configuration; +using JetBrains.Annotations; + +namespace SpaceWarp.API.Configuration; + +public class RangeConstraint : ValueConstraint where T : IComparable, IComparable +{ + [PublicAPI] + public T Minimum; + [PublicAPI] + public T Maximum; + + public RangeConstraint(T minimum, T maximum) + { + Minimum = minimum; + Maximum = maximum; + } + + public override bool IsValid(T o) => Minimum.CompareTo(o) <= 0 && Maximum.CompareTo(o) >= 0; + public override AcceptableValueBase ToAcceptableValueBase() + { + return new AcceptableValueRange(Minimum, Maximum); + } + + public override string ToString() + { + return $"{Minimum} - {Maximum}"; + } +} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs index bf704cce..cf1b7fd2 100644 --- a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs @@ -10,13 +10,14 @@ public class SaveConfigEntry : IConfigEntry internal object InMemoryValue; internal object DefaultValue; - public SaveConfigEntry(string section, string key, string description, Type valueType, object defaultValue) + public SaveConfigEntry(string section, string key, string description, Type valueType, object defaultValue, IValueConstraint constraint) { Section = section; Key = key; Description = description; ValueType = valueType; DefaultValue = defaultValue; + Constraint = constraint; InMemoryValue = defaultValue; } @@ -57,6 +58,10 @@ public void Set(T value) { throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); } + if (Constraint != null) + { + if (!Constraint.IsConstrained(value)) return; + } Value = Convert.ChangeType(value, ValueType); } @@ -65,11 +70,12 @@ public void Set(T value) public string Key { get; } public string Description { get; } + public IValueConstraint Constraint { get; } // The moment we bind to a new config file, reset the defaults internal void Bind(JsonConfigFile newConfigFile) { - BoundEntry = newConfigFile.Bind(ValueType, Section, Key, InMemoryValue, Description); + BoundEntry = newConfigFile.Bind(ValueType, Section, Key, InMemoryValue, Description, Constraint); InMemoryValue = DefaultValue; } diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs b/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs index dd92d64b..1298373a 100644 --- a/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs @@ -41,6 +41,11 @@ public void Save() public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") + { + return Bind(section, key, defaultValue, description, null); + } + + public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) { if (!CurrentEntries.TryGetValue(section, out var previousSection)) { @@ -52,7 +57,7 @@ public IConfigEntry Bind(string section, string key, T defaultValue = default return result; } - var entry = new SaveConfigEntry(section, key, description, typeof(T), defaultValue); + var entry = new SaveConfigEntry(section, key, description, typeof(T), defaultValue, valueConstraint); if (_internalConfigFile != null) entry.Bind(_internalConfigFile); previousSection[key] = entry; return entry; diff --git a/SpaceWarp.Core/API/Configuration/ValueConstraint.cs b/SpaceWarp.Core/API/Configuration/ValueConstraint.cs new file mode 100644 index 00000000..ddabf763 --- /dev/null +++ b/SpaceWarp.Core/API/Configuration/ValueConstraint.cs @@ -0,0 +1,19 @@ +using BepInEx.Configuration; + +namespace SpaceWarp.API.Configuration; + +public abstract class ValueConstraint : IValueConstraint +{ + public abstract bool IsValid(T o); + public bool IsValid(object o) + { + return IsValid((T)o); + } + + public bool IsConstrained(object o) + { + return IsValid((T)o); + } + + public abstract AcceptableValueBase ToAcceptableValueBase(); +} \ No newline at end of file From 482e9800271a0769fbb815ba11b039470d622096 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 09:25:42 -0500 Subject: [PATCH 10/18] Add space warp config constraints to the SpaceWarp settings UI --- .../API/UI/Settings/ModsPropertyDrawers.cs | 106 +++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/SpaceWarp.UI/API/UI/Settings/ModsPropertyDrawers.cs b/SpaceWarp.UI/API/UI/Settings/ModsPropertyDrawers.cs index 74414e4c..75d3fb6f 100644 --- a/SpaceWarp.UI/API/UI/Settings/ModsPropertyDrawers.cs +++ b/SpaceWarp.UI/API/UI/Settings/ModsPropertyDrawers.cs @@ -335,15 +335,30 @@ private static Func GenerateGenericDrawerFor(Type e private static Func GenerateAbstractGenericDrawerFor(Type entrySettingType) { - var valueListMethod = typeof(ModsPropertyDrawers).GetMethod(nameof(CreateFromAcceptableValueList), + var valueListMethod = typeof(ModsPropertyDrawers).GetMethod(nameof(CreateFromListConstraint), BindingFlags.Static | BindingFlags.NonPublic) ?.MakeGenericMethod(entrySettingType); - var valueRangeMethod = typeof(ModsPropertyDrawers).GetMethod(nameof(CreateFromAcceptableValueRange), + var valueRangeMethod = typeof(ModsPropertyDrawers).GetMethod(nameof(CreateFromRangeConstraint), BindingFlags.Static | BindingFlags.NonPublic) ?.MakeGenericMethod(entrySettingType); return (name, entry) => { + var t = entry.Constraint?.GetType(); + if (t?.GetGenericTypeDefinition() == typeof(ListConstraint<>) && + t.GenericTypeArguments[0] == entrySettingType) + { + if (valueListMethod != null) + return (GameObject)valueListMethod.Invoke(null, new object[] { name, entry, entry.Constraint }); + } + if (t?.GetGenericTypeDefinition() == typeof(RangeConstraint<>) && + t.GenericTypeArguments[0] == entrySettingType) + { + if (valueRangeMethod != null) + { + return (GameObject)valueRangeMethod.Invoke(null, new object[] { name, entry, entry.Constraint }); + } + } var inputFieldCopy = UnityObject.Instantiate(InputFieldPrefab); var lab = inputFieldCopy.GetChild("Label"); lab.GetComponent().SetTerm(name); @@ -574,6 +589,93 @@ private static GameObject CreateFromAcceptableValueRange(ConfigEntry entry return slCopy; } + private static GameObject CreateFromListConstraint(string key, IConfigEntry entry, ListConstraint constraint) where T : IEquatable + { + var value = new ConfigValue(entry); + var ddCopy = UnityObject.Instantiate(DropdownPrefab); + var lab = ddCopy.GetChild("Label"); + lab.GetComponent().SetTerm(key); + lab.GetComponent().text = key; + var dropdown = ddCopy.GetChild("Setting").GetChild("BTN-Dropdown"); + var extended = dropdown.GetComponent(); + // Start by clearing the options data + extended.options.Clear(); + foreach (var option in constraint.AcceptableValues) + { + extended.options.Add(new TMP_Dropdown.OptionData(option as string ?? (option is Color color + ? (color.a < 1 ? ColorUtility.ToHtmlStringRGBA(color) : ColorUtility.ToHtmlStringRGB(color)) + : option.ToString()))); + } + + extended.value = constraint.AcceptableValues.IndexOf(value.Value); + extended.onValueChanged.AddListener(idx => { value.Value = constraint.AcceptableValues[idx]; }); + var sec = ddCopy.AddComponent(); + sec.description = entry.Description; + sec.isInputSettingElement = false; + ddCopy.SetActive(true); + ddCopy.name = key; + return ddCopy; + } + + private static GameObject CreateFromRangeConstraint(string key, IConfigEntry entry, + RangeConstraint constraint) where T : IComparable, IComparable + { + var value = new ConfigValue(entry); + // Now we have to have a "slider" prefab + var slCopy = UnityObject.Instantiate(SliderPrefab); + var lab = slCopy.GetChild("Label"); + lab.GetComponent().SetTerm(key); + lab.GetComponent().text = key; + var setting = slCopy.GetChild("Setting"); + var slider = setting.GetChild("KSP2SliderLinear").GetComponent(); + var amount = setting.GetChild("Amount display"); + var text = amount.GetComponentInChildren(); + text.text = entry.Value.ToString(); + Func toFloat = x => Convert.ToSingle(x); + // if (!typeof(T).IsIntegral()) + // { + // var convT = TypeDescriptor.GetConverter(typeof(T)) ?? + // throw new ArgumentNullException("TypeDescriptor.GetConverter(typeof(T))"); + // toT = x => (T)convT.ConvertFrom(x); + // } + Func toT = Type.GetTypeCode(typeof(T)) switch + { + TypeCode.Byte => x => (T)(object)Convert.ToByte(x), + TypeCode.SByte => x => (T)(object)Convert.ToSByte(x), + TypeCode.UInt16 => x => (T)(object)Convert.ToUInt16(x), + TypeCode.UInt32 => x => (T)(object)Convert.ToUInt32(x), + TypeCode.UInt64 => x => (T)(object)Convert.ToUInt64(x), + TypeCode.Int16 => x => (T)(object)Convert.ToInt16(x), + TypeCode.Int32 => x => (T)(object)Convert.ToInt32(x), + TypeCode.Int64 => x => (T)(object)Convert.ToInt64(x), + TypeCode.Decimal => x => (T)(object)Convert.ToDecimal(x), + TypeCode.Double => x => (T)(object)Convert.ToDouble(x), + TypeCode.Single => x => (T)(object)x, + _ => x => throw new NotImplementedException(typeof(T).ToString()) + }; + slider.minValue = toFloat(constraint.Minimum); + slider.maxValue = toFloat(constraint.Maximum); + slider.SetValueWithoutNotify(toFloat(value.Value)); + slider.onValueChanged.AddListener(val => + { + // var trueValue = (acceptableValues.MaxValue-acceptableValues.MinValue) * (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(value) + // var trueValue = (toFloat(acceptableValues.MaxValue) - toFloat(acceptableValues.MinValue)) * value + + // toFloat(acceptableValues.MinValue); + var trueValue = val; + + value.Value = toT(trueValue) ?? value.Value; + if (entry.Value != null) text.text = entry.Value.ToString(); + slider.SetWithoutCallback(toFloat(value.Value)); + }); + + var sec = slCopy.AddComponent(); + sec.description = entry.Description; + sec.isInputSettingElement = false; + slCopy.SetActive(true); + slCopy.name = key; + return slCopy; + } + private static GameObject CreateStringConfig(ConfigEntryBase entryBase) { var entry = (ConfigEntry)entryBase; From 087389da70186f8b42ec691384ae7075a4c837bf Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 7 Nov 2023 09:33:29 -0500 Subject: [PATCH 11/18] Add callbacks for changed config values --- .../API/Configuration/BepInExConfigEntry.cs | 14 ++++++++++++-- SpaceWarp.Core/API/Configuration/ConfigValue.cs | 17 ++++++++++++++++- .../API/Configuration/IConfigEntry.cs | 6 ++++++ SpaceWarp.Core/API/Configuration/IConfigFile.cs | 1 + .../API/Configuration/JsonConfigEntry.cs | 6 ++++++ .../API/Configuration/SaveConfigEntry.cs | 7 +++++++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs index 682aef50..1901d908 100644 --- a/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs @@ -8,7 +8,7 @@ namespace SpaceWarp.API.Configuration; public class BepInExConfigEntry : IConfigEntry { public readonly ConfigEntryBase EntryBase; - + public event Action Callbacks; public BepInExConfigEntry(ConfigEntryBase entryBase, IValueConstraint constraint = null) { EntryBase = entryBase; @@ -18,8 +18,13 @@ public BepInExConfigEntry(ConfigEntryBase entryBase, IValueConstraint constraint public object Value { get => EntryBase.BoxedValue; - set => EntryBase.BoxedValue = value; + set + { + Callbacks?.Invoke(EntryBase.BoxedValue, value); + EntryBase.BoxedValue = value; + } } + public Type ValueType => EntryBase.SettingType; public T Get() where T : class @@ -43,9 +48,14 @@ public void Set(T value) { if (!Constraint.IsConstrained(value)) return; } + Callbacks?.Invoke(EntryBase.BoxedValue, value); EntryBase.BoxedValue = Convert.ChangeType(value, ValueType); } public string Description => EntryBase.Description.Description; public IValueConstraint Constraint { get; } + public void RegisterCallback(Action valueChangedCallback) + { + Callbacks += valueChangedCallback; + } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/ConfigValue.cs b/SpaceWarp.Core/API/Configuration/ConfigValue.cs index 0eb6b192..b4a54278 100644 --- a/SpaceWarp.Core/API/Configuration/ConfigValue.cs +++ b/SpaceWarp.Core/API/Configuration/ConfigValue.cs @@ -20,6 +20,21 @@ public ConfigValue(IConfigEntry entry) public T Value { get => (T)Entry.Value; - set => Entry.Value = value; + set + { + Entry.Value = value; + } + } + + public void RegisterCallback(Action callback) + { + // Callbacks += callback; + Entry.RegisterCallback(NewCallback); + return; + + void NewCallback(object from, object to) + { + callback.Invoke((T)from, (T)to); + } } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/IConfigEntry.cs b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs index 72c67051..f5f062a9 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs @@ -14,4 +14,10 @@ public interface IConfigEntry public string Description { get; } public IValueConstraint Constraint { get; } + + /// + /// Called when setting the value on a config file + /// + /// An action that takes te old value and the new value and calls a callback + public void RegisterCallback(Action valueChangedCallback); } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/IConfigFile.cs b/SpaceWarp.Core/API/Configuration/IConfigFile.cs index 6eae904e..4b596796 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigFile.cs @@ -18,4 +18,5 @@ public IConfigEntry Bind(string section, string key, T defaultValue, string d public IReadOnlyList Sections { get; } public IReadOnlyList this[string section] { get; } + } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs index 07433c28..977f910c 100644 --- a/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs @@ -8,6 +8,7 @@ public class JsonConfigEntry : IConfigEntry { private readonly JsonConfigFile _configFile; private object _value; + public event Action Callbacks; public JsonConfigEntry(JsonConfigFile configFile, Type type, string description, object value, IValueConstraint constraint = null) @@ -25,6 +26,7 @@ public object Value get => _value; set { + Callbacks?.Invoke(_value, value); _value = value; _configFile.Save(); } @@ -56,4 +58,8 @@ public void Set(T value) public string Description { get; } public IValueConstraint Constraint { get; } + public void RegisterCallback(Action valueChangedCallback) + { + Callbacks += valueChangedCallback; + } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs index cf1b7fd2..de817747 100644 --- a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs +++ b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs @@ -9,6 +9,7 @@ public class SaveConfigEntry : IConfigEntry internal object InMemoryValue; internal object DefaultValue; + public event Action Callbacks; public SaveConfigEntry(string section, string key, string description, Type valueType, object defaultValue, IValueConstraint constraint) { @@ -31,10 +32,12 @@ public object Value { if (BoundEntry != null) { + Callbacks?.Invoke(BoundEntry.Value, value); BoundEntry.Value = value; } else { + Callbacks?.Invoke(InMemoryValue, value); InMemoryValue = value; } } @@ -71,6 +74,10 @@ public void Set(T value) public string Description { get; } public IValueConstraint Constraint { get; } + public void RegisterCallback(Action valueChangedCallback) + { + Callbacks += valueChangedCallback; + } // The moment we bind to a new config file, reset the defaults internal void Bind(JsonConfigFile newConfigFile) From 850f747efbda32867d5e0c9b2bb3f1bed2fbfd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bure=C5=A1?= Date: Wed, 8 Nov 2023 20:59:47 +0100 Subject: [PATCH 12/18] Only log an info message when a label doesn't exist in an AddressableAction --- .../Patching/LoadingActions/AddressableAction.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs b/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs index 47dac15c..cfb2dbce 100644 --- a/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs +++ b/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs @@ -21,8 +21,21 @@ public AddressableAction(string name, string label, Action action) : base(nam Action = action; } + private bool DoesLabelExist(object label) + { + return GameManager.Instance.Assets._registeredResourceLocators.Any(locator => locator.Keys.Contains(label)) + || Addressables.ResourceLocators.Any(locator => locator.Keys.Contains(label)); + } + public override void DoAction(Action resolve, Action reject) { + if (!DoesLabelExist(Label)) + { + Debug.Log($"[Space Warp] Skipping loading addressables for {Label} which does not exist."); + resolve(); + return; + } + try { GameManager.Instance.Assets.LoadByLabel(Label,Action,delegate(IList assetLocations) From 23c029cb560fd90675391c1f7c6da1d5edd671b0 Mon Sep 17 00:00:00 2001 From: Falki-git <72734856+Falki-git@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:17:35 +0100 Subject: [PATCH 13/18] Fix: support when savedata is a dictionary Previously plugin savedata could have only been a class with fields and properties. Now dictionaries are also supported. For example: ModSaves.RegisterSaveLoadGameData>( "My mod GUID", OnSave, OnLoad ); --- .../InternalUtilities/InternalExtensions.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs index 0e953df8..55accc73 100644 --- a/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs +++ b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs @@ -1,6 +1,7 @@ using System.Reflection; using System; using UnityEngine; +using System.Collections; namespace SpaceWarp.InternalUtilities; @@ -14,22 +15,36 @@ internal static void Persist(this UnityObject obj) internal static void CopyFieldAndPropertyDataFromSourceToTargetObject(object source, object target) { - foreach (FieldInfo field in source.GetType().GetFields()) + // check if it's a dictionary + if (source is IDictionary sourceDictionary && target is IDictionary targetDictionary) { - object value = field.GetValue(source); - - try + // copy dictionary items + foreach (DictionaryEntry entry in sourceDictionary) { - field.SetValue(target, value); + targetDictionary[entry.Key] = entry.Value; } - catch (FieldAccessException) - { /* some fields are constants */ } } - - foreach (PropertyInfo property in source.GetType().GetProperties()) + else { - object value = property.GetValue(source); - property.SetValue(target, value); + // copy fields + foreach (FieldInfo field in source.GetType().GetFields()) + { + object value = field.GetValue(source); + + try + { + field.SetValue(target, value); + } + catch (FieldAccessException) + { /* some fields are constants */ } + } + + // copy properties + foreach (PropertyInfo property in source.GetType().GetProperties()) + { + object value = property.GetValue(source); + property.SetValue(target, value); + } } } } \ No newline at end of file From efe64f510b9564afcfa7b7d21d7ec2729ea1e513 Mon Sep 17 00:00:00 2001 From: Lexi Date: Thu, 30 Nov 2023 17:19:47 -0500 Subject: [PATCH 14/18] Revert "Reset bindings for campaign configuration when returning to main menu" This reverts commit 6338e33eba7ef8f74412f9cf3555710c68574855. --- SpaceWarp.Core/SpaceWarpPlugin.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/SpaceWarp.Core/SpaceWarpPlugin.cs b/SpaceWarp.Core/SpaceWarpPlugin.cs index 966feceb..985c9aed 100644 --- a/SpaceWarp.Core/SpaceWarpPlugin.cs +++ b/SpaceWarp.Core/SpaceWarpPlugin.cs @@ -9,9 +9,7 @@ using HarmonyLib; using I2.Loc; using JetBrains.Annotations; -using KSP.Game; using KSP.IO; -using KSP.Messages; using KSP.ScriptInterop.impl.moonsharp; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; @@ -42,8 +40,7 @@ public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin internal ScriptEnvironment GlobalLuaState; internal new static ManualLogSource Logger; - - + public SpaceWarpPlugin() { // Load the type forwarders @@ -114,20 +111,6 @@ public override void OnInitialized() { ModuleManager.InitializeAllModules(); SetupLuaState(); - Game.Messages.Subscribe(typeof(GameStateEnteredMessage), ResetToDefaults, false, true); - } - - private void ResetToDefaults(MessageCenterMessage msg) - { - var stateChange = msg as GameStateEnteredMessage; - if (stateChange!.StateBeingEntered != GameState.MainMenu) return; - foreach (var plugin in PluginList.AllEnabledAndActivePlugins) - { - if (plugin.SaveConfigFile != null && plugin.SaveConfigFile.Sections.Count > 0) - { - plugin.SaveConfigFile.ResetToDefaults(); - } - } } public override void OnPostInitialized() From ea94f18251a9cd4c995de7fb0beec97461179fa1 Mon Sep 17 00:00:00 2001 From: Lexi Date: Thu, 30 Nov 2023 17:27:09 -0500 Subject: [PATCH 15/18] Revert "Allow for per campaign configuration with `CampaignConfiguration.Bind` in the mod base plugin" This reverts commit a80cedd7 --- .../API/Configuration/BepInExConfigFile.cs | 4 +- .../API/Configuration/IConfigFile.cs | 4 - .../API/Configuration/JsonConfigFile.cs | 62 +----------- .../API/Configuration/SaveConfigEntry.cs | 94 ------------------- .../API/Configuration/SaveConfigFile.cs | 74 --------------- .../API/Mods/BaseKspLoaderSpaceWarpMod.cs | 2 - .../API/Mods/BaseSpaceWarpPlugin.cs | 2 - SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs | 2 - .../API/Mods/SpaceWarpPluginDescriptor.cs | 5 +- .../Backend/Modding/AssetOnlyMod.cs | 3 - .../Backend/Modding/BepInExModAdapter.cs | 3 - .../Backend/Modding/KspModAdapter.cs | 3 - .../Backend/Modding/PluginRegister.cs | 21 ++--- .../LoadCampaignConfiguration.cs | 31 ------ SpaceWarp.Core/SpaceWarpPlugin.cs | 6 +- 15 files changed, 14 insertions(+), 302 deletions(-) delete mode 100644 SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs delete mode 100644 SpaceWarp.Core/API/Configuration/SaveConfigFile.cs delete mode 100644 SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs index feb20e8d..a1a5c60f 100644 --- a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Reflection; +using System.Collections.Generic; using BepInEx.Configuration; using JetBrains.Annotations; diff --git a/SpaceWarp.Core/API/Configuration/IConfigFile.cs b/SpaceWarp.Core/API/Configuration/IConfigFile.cs index 4b596796..87aedf23 100644 --- a/SpaceWarp.Core/API/Configuration/IConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/IConfigFile.cs @@ -12,11 +12,7 @@ public interface IConfigFile public IConfigEntry this[string section, string key] { get; } public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = ""); - - public IConfigEntry Bind(string section, string key, T defaultValue, string description, - IValueConstraint valueConstraint); public IReadOnlyList Sections { get; } public IReadOnlyList this[string section] { get; } - } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs index a75f4f64..aafbfc8b 100644 --- a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs +++ b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs @@ -103,11 +103,6 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu } } - if (entry.Value.Constraint != null) - { - result.AppendLine($" // Accepts: {entry.Value.Constraint}"); - } - var serialized = JsonConvert.SerializeObject(entry.Value.Value,Formatting.Indented,DefaultConverters.ToArray()); var serializedLines = serialized.Split('\n').Select(x => x.TrimEnd()).ToArray(); if (serializedLines.Length > 1) @@ -136,11 +131,6 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") - { - return Bind(section, key, defaultValue, description, null); - } - - public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) { // So now we have to check if its already bound, and/or if the previous config object has it if (!CurrentEntries.TryGetValue(section, out var previousSection)) @@ -161,66 +151,22 @@ public IConfigEntry Bind(string section, string key, T defaultValue, string d if (sect is JObject obj && obj.TryGetValue(key, out var value)) { var previousValue = value.ToObject(typeof(T)); - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, previousValue, valueConstraint); - } - else - { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); - } - } - catch - { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); - // ignored - } - } - else - { - previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue, valueConstraint); - } - - Save(); - return previousSection[key]; - } - - public IConfigEntry Bind(Type type, string section, string key, object defaultValue = default, - string description = "", IValueConstraint constraint = null) - { - // So now we have to check if its already bound, and/or if the previous config object has it - if (!CurrentEntries.TryGetValue(section, out var previousSection)) - { - previousSection = new Dictionary(); - CurrentEntries.Add(section,previousSection); - } - - if (previousSection.TryGetValue(key, out var result)) - { - return result; - } - - if (_previousConfigObject != null && _previousConfigObject.TryGetValue(section, out var sect)) - { - try - { - if (sect is JObject obj && obj.TryGetValue(key, out var value)) - { - var previousValue = value.ToObject(type); - previousSection[key] = new JsonConfigEntry(this, type, description, previousValue,constraint); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, previousValue); } else { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); } } catch { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); // ignored } } else { - previousSection[key] = new JsonConfigEntry(this, type, description, defaultValue, constraint); + previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue); } Save(); diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs b/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs deleted file mode 100644 index de817747..00000000 --- a/SpaceWarp.Core/API/Configuration/SaveConfigEntry.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace SpaceWarp.API.Configuration; - -public class SaveConfigEntry : IConfigEntry -{ - public IConfigEntry BoundEntry; - - internal object InMemoryValue; - internal object DefaultValue; - public event Action Callbacks; - - public SaveConfigEntry(string section, string key, string description, Type valueType, object defaultValue, IValueConstraint constraint) - { - Section = section; - Key = key; - Description = description; - ValueType = valueType; - DefaultValue = defaultValue; - Constraint = constraint; - InMemoryValue = defaultValue; - } - - public object Value - { - get - { - return BoundEntry != null ? BoundEntry.Value : InMemoryValue; - } - set - { - if (BoundEntry != null) - { - Callbacks?.Invoke(BoundEntry.Value, value); - BoundEntry.Value = value; - } - else - { - Callbacks?.Invoke(InMemoryValue, value); - InMemoryValue = value; - } - } - } - - public Type ValueType { get; } - public T Get() where T : class - { - - if (!typeof(T).IsAssignableFrom(ValueType)) - { - throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); - } - - return Value as T; - } - - public void Set(T value) - { - if (!ValueType.IsAssignableFrom(typeof(T))) - { - throw new InvalidCastException($"Cannot cast {ValueType} to {typeof(T)}"); - } - if (Constraint != null) - { - if (!Constraint.IsConstrained(value)) return; - } - - Value = Convert.ChangeType(value, ValueType); - } - - public string Section { get; } - public string Key { get; } - - public string Description { get; } - public IValueConstraint Constraint { get; } - public void RegisterCallback(Action valueChangedCallback) - { - Callbacks += valueChangedCallback; - } - - // The moment we bind to a new config file, reset the defaults - internal void Bind(JsonConfigFile newConfigFile) - { - BoundEntry = newConfigFile.Bind(ValueType, Section, Key, InMemoryValue, Description, Constraint); - InMemoryValue = DefaultValue; - } - - internal void Reset() - { - BoundEntry = null; - InMemoryValue = DefaultValue; - } -} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs b/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs deleted file mode 100644 index 1298373a..00000000 --- a/SpaceWarp.Core/API/Configuration/SaveConfigFile.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; - -namespace SpaceWarp.API.Configuration; - -public class SaveConfigFile : IConfigFile -{ - [CanBeNull] private JsonConfigFile _internalConfigFile; - internal Dictionary> CurrentEntries = new(); - - public JsonConfigFile CurrentConfigFile - { - get => _internalConfigFile; - set - { - if (_internalConfigFile == value) return; - Save(); - _internalConfigFile = value; - RebindConfigFile(); - } - } - - private void RebindConfigFile() - { - foreach (var subEntry in CurrentEntries.SelectMany(entry => entry.Value)) - { - if (_internalConfigFile != null) - subEntry.Value.Bind(_internalConfigFile); - else - subEntry.Value.Reset(); - } - } - public void Save() - { - if (CurrentConfigFile != null) - { - CurrentConfigFile.Save(); - } - } - - public IConfigEntry this[string section, string key] => CurrentEntries[section][key]; - - public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "") - { - return Bind(section, key, defaultValue, description, null); - } - - public IConfigEntry Bind(string section, string key, T defaultValue, string description, IValueConstraint valueConstraint) - { - if (!CurrentEntries.TryGetValue(section, out var previousSection)) - { - previousSection = new Dictionary(); - CurrentEntries.Add(section,previousSection); - } - if (previousSection.TryGetValue(key, out var result)) - { - return result; - } - - var entry = new SaveConfigEntry(section, key, description, typeof(T), defaultValue, valueConstraint); - if (_internalConfigFile != null) entry.Bind(_internalConfigFile); - previousSection[key] = entry; - return entry; - } - - public IReadOnlyList Sections => CurrentEntries.Keys.ToList(); - - public IReadOnlyList this[string section] => CurrentEntries[section].Keys.ToList(); - - public void ResetToDefaults() - { - CurrentConfigFile = null; - } -} \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs index 422df16b..0127632e 100644 --- a/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs +++ b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs @@ -32,6 +32,4 @@ public IConfigFile SWConfiguration { } public SpaceWarpPluginDescriptor SWMetadata { get; set; } - private SaveConfigFile _saveConfig; - public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs index e9e7f5d0..2f7551f8 100644 --- a/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs +++ b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs @@ -64,8 +64,6 @@ public virtual void OnPostInitialized() public ILogger SWLogger => _logger ??= new BepInExLogger(Logger); private BepInExConfigFile _configFile; public IConfigFile SWConfiguration => _configFile ??= new BepInExConfigFile(Config); - private SaveConfigFile _saveConfig; - public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } internal static string GetGuidBySpec(PluginInfo pluginInfo, ModInfo modInfo) diff --git a/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs index 36bb39f6..c2f9d446 100644 --- a/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs +++ b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs @@ -17,7 +17,5 @@ public interface ISpaceWarpMod public IConfigFile SWConfiguration { get; } - public SaveConfigFile CampaignConfiguration { get; } - public SpaceWarpPluginDescriptor SWMetadata { get; set; } } \ No newline at end of file diff --git a/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs index 207cbb73..fdf7edfb 100644 --- a/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs +++ b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs @@ -15,8 +15,7 @@ public SpaceWarpPluginDescriptor( ModInfo swInfo, DirectoryInfo folder, bool doLoadingActions = true, - [CanBeNull] IConfigFile configFile = null, - [CanBeNull] SaveConfigFile saveConfigFile = null + [CanBeNull] IConfigFile configFile = null ) { Plugin = plugin; @@ -26,7 +25,6 @@ public SpaceWarpPluginDescriptor( Folder = folder; DoLoadingActions = doLoadingActions; ConfigFile = configFile; - SaveConfigFile = saveConfigFile; } [CanBeNull] public ISpaceWarpMod Plugin; @@ -36,7 +34,6 @@ public SpaceWarpPluginDescriptor( public readonly DirectoryInfo Folder; public bool DoLoadingActions; [CanBeNull] public IConfigFile ConfigFile; - [CanBeNull] public SaveConfigFile SaveConfigFile; // Set by the version checking system public bool Outdated; diff --git a/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs index de6041f0..cd34c581 100644 --- a/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs +++ b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs @@ -27,7 +27,4 @@ public void OnPostInitialized() public ILogger SWLogger { get; } public IConfigFile SWConfiguration => new EmptyConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } - - private SaveConfigFile _saveConfig; - public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs index 641fa4a2..84f61bda 100644 --- a/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs +++ b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs @@ -29,7 +29,4 @@ public BepInExModAdapter(BaseUnityPlugin plugin) { Plugin = plugin; } - - private SaveConfigFile _saveConfig; - public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs index 2cae25b3..2e85d23c 100644 --- a/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs +++ b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs @@ -33,7 +33,4 @@ public void Update() public ILogger SWLogger { get; private set; } public IConfigFile SWConfiguration => new EmptyConfigFile(); public SpaceWarpPluginDescriptor SWMetadata { get; set; } - - private SaveConfigFile _saveConfig; - public SaveConfigFile CampaignConfiguration => _saveConfig ??= new SaveConfigFile(); } \ No newline at end of file diff --git a/SpaceWarp.Core/Backend/Modding/PluginRegister.cs b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs index 82c3e94c..88d24c1c 100644 --- a/SpaceWarp.Core/Backend/Modding/PluginRegister.cs +++ b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs @@ -83,8 +83,7 @@ private static bool AssertFolderPath(BaseSpaceWarpPlugin plugin, string folderPa BepInExToSWInfo(plugin.Info), new DirectoryInfo(folderPath), false, - new BepInExConfigFile(plugin.Config), - plugin.CampaignConfiguration + new BepInExConfigFile(plugin.Config) ); PluginList.NoteBadDirectoryError(descriptor); return false; @@ -244,8 +243,7 @@ private static void RegisterSingleSpaceWarpPlugin(BaseSpaceWarpPlugin plugin) metadata, directoryInfo, true, - plugin.SWConfiguration, - plugin.CampaignConfiguration + new BepInExConfigFile(plugin.Config) ); descriptor.Plugin!.SWMetadata = descriptor; if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath)) return; @@ -266,16 +264,14 @@ private static void RegisterSingleBepInExPlugin(BaseUnityPlugin plugin) if (File.Exists(modInfoPath)) { if (!TryReadModInfo(plugin, modInfoPath, folderPath, out var metadata)) return; - var adapter = new BepInExModAdapter(plugin); var descriptor = new SpaceWarpPluginDescriptor( - adapter, + new BepInExModAdapter(plugin), metadata.Spec != SpecVersion.V1_2 ? metadata.ModID : plugin.Info.Metadata.GUID, metadata.Name, metadata, directoryInfo, false, - adapter.SWConfiguration, - adapter.CampaignConfiguration + new BepInExConfigFile(plugin.Config) ); descriptor.Plugin!.SWMetadata = descriptor; if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath)) @@ -298,8 +294,7 @@ private static SpaceWarpPluginDescriptor GetBepInExDescriptor(BaseUnityPlugin pl BepInExToSWInfo(plugin.Info), new DirectoryInfo(Path.GetDirectoryName(plugin.Info.Location)!), false, - pluginAdapter.SWConfiguration, - pluginAdapter.CampaignConfiguration + new BepInExConfigFile(plugin.Config) ); pluginAdapter.SWMetadata = descriptor; return descriptor; @@ -355,15 +350,13 @@ private static void RegisterAllCodelessMods() continue; } - var assetMod = new AssetOnlyMod(swinfoData.Name); var descriptor = new SpaceWarpPluginDescriptor( - assetMod, + new AssetOnlyMod(swinfoData.Name), swinfoData.ModID, swinfoData.Name, swinfoData, swinfo.Directory, true, - new BepInExConfigFile(FindOrCreateConfigFile(swinfoData.ModID)), - assetMod.CampaignConfiguration + new BepInExConfigFile(FindOrCreateConfigFile(swinfoData.ModID)) ); descriptor.Plugin!.SWMetadata = descriptor; diff --git a/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs deleted file mode 100644 index 486032c7..00000000 --- a/SpaceWarp.Core/Patching/LoadingActions/LoadCampaignConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.IO; -using KSP.Game; -using KSP.Game.Flow; -using KSP.Game.Load; -using SpaceWarp.API.Configuration; -using SpaceWarp.API.Mods; - -namespace SpaceWarp.Patching.LoadingActions; - -public class LoadCampaignConfiguration : FlowAction -{ - public LoadCampaignConfiguration() : base("Loading per campaign mod configuration") - { - } - - public override void DoAction(Action resolve, Action reject) - { - var path = - $"{GameManager.Instance.Game.SaveLoadManager.ActiveCampaignFolderPath}{Path.DirectorySeparatorChar}mod_data"; - if (!Directory.Exists(path)) Directory.CreateDirectory(path); - foreach (var mod in PluginList.AllEnabledAndActivePlugins) - { - if (mod.SaveConfigFile == null || mod.SaveConfigFile.Sections.Count == 0) continue; - var newConfigFile = new JsonConfigFile($"{path}{Path.DirectorySeparatorChar}{mod.Guid}.cfg"); - mod.SaveConfigFile.CurrentConfigFile = newConfigFile; - } - - resolve(); - } -} \ No newline at end of file diff --git a/SpaceWarp.Core/SpaceWarpPlugin.cs b/SpaceWarp.Core/SpaceWarpPlugin.cs index 985c9aed..3aede271 100644 --- a/SpaceWarp.Core/SpaceWarpPlugin.cs +++ b/SpaceWarp.Core/SpaceWarpPlugin.cs @@ -4,7 +4,6 @@ using System.IO; using System.Reflection; using BepInEx; -using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using I2.Loc; @@ -13,7 +12,6 @@ using KSP.ScriptInterop.impl.moonsharp; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; -using SpaceWarp.API.Configuration; using SpaceWarp.API.Loading; using SpaceWarp.API.Lua; using SpaceWarp.API.Mods; @@ -40,7 +38,7 @@ public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin internal ScriptEnvironment GlobalLuaState; internal new static ManualLogSource Logger; - + public SpaceWarpPlugin() { // Load the type forwarders @@ -103,8 +101,6 @@ public override void OnPreInitialized() { // Persist all game objects so I don't need to stomp on config ModuleManager.PreInitializeAllModules(); - // Start loading campaign configuration data - SaveLoad.AddFlowActionToCampaignLoadAfter("Deserializing Save File Contents"); } public override void OnInitialized() From a1af172a85b7efc521ca2c3f4489484c5ae0d5cb Mon Sep 17 00:00:00 2001 From: cheese3660 Date: Fri, 1 Dec 2023 11:41:13 -0500 Subject: [PATCH 16/18] Update swinfo.json --- SpaceWarpBuildTemplate/swinfo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpaceWarpBuildTemplate/swinfo.json b/SpaceWarpBuildTemplate/swinfo.json index 5b45cfd9..683d2a97 100644 --- a/SpaceWarpBuildTemplate/swinfo.json +++ b/SpaceWarpBuildTemplate/swinfo.json @@ -6,7 +6,7 @@ "description": "Space-Warp is an API for KSP2 mod developers.", "source": "https://github.com/SpaceWarpDev/SpaceWarp", "version_check": "https://raw.githubusercontent.com/SpaceWarpDev/SpaceWarp/main/SpaceWarpBuildTemplate/swinfo.json", - "version": "1.5.4", + "version": "1.6.0", "dependencies": [ { "id": "UitkForKsp2", From 70d10445ba5b5cd0e703a2d7b7539c0c510a6022 Mon Sep 17 00:00:00 2001 From: cheese3660 Date: Fri, 1 Dec 2023 11:41:25 -0500 Subject: [PATCH 17/18] Update Directory.Build.props --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index a2be5c89..e2aa9f15 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.5.4 + 1.6.0 netstandard2.1 SpaceWarp 11 From 65cf427c0388b373454420ee6e754e2cdecdc32a Mon Sep 17 00:00:00 2001 From: cheese3660 Date: Fri, 1 Dec 2023 11:41:34 -0500 Subject: [PATCH 18/18] Update SpaceWarp.nuspec --- SpaceWarp.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpaceWarp.nuspec b/SpaceWarp.nuspec index 8b7451fd..4907f98b 100644 --- a/SpaceWarp.nuspec +++ b/SpaceWarp.nuspec @@ -2,7 +2,7 @@ SpaceWarp - 1.5.4 + 1.6.0 SpaceWarp contributors false https://raw.githubusercontent.com/SpaceWarp/SpaceWarp/main/LICENSE