diff --git a/plugin_template/BepInEx/plugins/SpaceWarp/localizations/space_warp_localizations.csv b/plugin_template/BepInEx/plugins/SpaceWarp/localizations/space_warp_localizations.csv
index e335e0d..101fa84 100644
--- a/plugin_template/BepInEx/plugins/SpaceWarp/localizations/space_warp_localizations.csv
+++ b/plugin_template/BepInEx/plugins/SpaceWarp/localizations/space_warp_localizations.csv
@@ -1,48 +1,49 @@
-Key,Type,Description,English,Portuguese BR,French
-SpaceWarp/ModList/EnableAll,Text,,Enable All,Ativar todos,Activer tous
-SpaceWarp/ModList/DisableAll,Text,,Disable All,Desativar todos,Désactiver tous
-SpaceWarp/ModList/RevertChanges,Text,,Revert Changes,Reverter alterações,Rétablir les changements
-SpaceWarp/ModList/ApplyChanges,Text,,Apply and restart,Aplicar e reiniciar,Appliquer et redémarrer
-SpaceWarp/ModList/multipleChangesDetected,Text,,"{0} changes detected, please restart to apply them","{0} alterações detetadas, porfavor reinicie o jogo","{0} changements détectés, veuillez redémarrer pour les appliquer"
-SpaceWarp/ModList/singleChangeDetected,Text,,"1 change detected, please restart to apply it","1 alteração detectada, reinicie para aplicá-la","1 changement détecté, veuillez redémarrer pour l'appliquer"
-SpaceWarp/ModList/Header,Text,,spacewarp.modlist,spacewarp.modlist,spacewarp.modlist
-SpaceWarp/ModList/CoreMods,Text,,Core Mods,Mods do jogo,Mods du jeu
-SpaceWarp/ModList/EnabledMods,Text,,Enabled Mods,Mods ativos,Mods activés
-SpaceWarp/ModList/ErroredMods,Text,,Errored Mods,Mods com erros,Mods avec des erreurs
-SpaceWarp/ModList/DisabledMods,Text,,Disabled Mods,Mods desativados,Mods désactivés
-SpaceWarp/ModList/Version,Text,,Version,Versão,Version
-SpaceWarp/ModList/Author,Text,,Author,Autor,Auteur
-SpaceWarp/ModList/Source,Text,,Source,Fonte,Source
-SpaceWarp/ModList/Description,Text,,Description,Descrição,Description
-SpaceWarp/ModList/KSP2Version,Text,,KSP2 Version,Versão do jogo,Version de KSP2
-SpaceWarp/ModList/Dependencies,Text,,Dependencies,Dependências,Dépendances
-SpaceWarp/ModList/outdated,Text,,(outdated),(desatualizado),(obsolète)
-SpaceWarp/ModList/unsupported,Text,,(unsupported),(não suportado),(non pris en charge)
-SpaceWarp/ModList/disabled,Text,,(disabled),(desativado),(désactivé)
-SpaceWarp/ModList/mismatched,Text,,(mismatched),(não corresponde com o swinfo),(non concordant)
-SpaceWarp/ModList/baddirectory,Text,,(bad directory),(problemas no diretorio),(mauvais dossier)
-SpaceWarp/ModList/missingswinfo,Text,,(missing SW Info),(não possui um swinfo),(SW Info manquant)
-SpaceWarp/ModList/OpenModSettings,Text,,Mod Settings,Configurações dos mods,Paramètres des mods
-SpaceWarp/ModList/OpenModsFolder,Text,,Open Mods Folder,Abrir pasta de mods,Ouvrir le dossier des mods
-SpaceWarp/ModList/MissingDependency,Text,,Dependency is missing,Esta dependência não está presente,Cette dépendance est manquante
-SpaceWarp/ModList/ErroredDependency,Text,,Dependency had an error while loading,Esta dependência teve um erro durante o loading,Une erreur s'est produite lors du chargement de cette dépendance
-SpaceWarp/ModList/DisabledDependency,Text,,Dependency is disabled,Esta dependência esta desativada,Cette dépendance est manquante
-SpaceWarp/ModList/UnspecifiedDependency,Text,,Dependency was not specified in SW info,Esta dependência não está presente no swinfo,Cette dépendance n'a pas été spécifiée dans SW Info
-SpaceWarp/ModList/UnsupportedDependency,Text,,Dependency is of an unsupported version,Esta dependência é de uma versão nao suportada,Cette dépendance est d'une version non supportée
-SpaceWarp/ModList/Conflicts,Text,,Conflicts,Incompatibilidades,Conflits
-SpaceWarp/ModList/Details,Text,,Details,Detalhes,Détails
-SpaceWarp/Mods,Text,,Mods,Mods,Mods
-SpaceWarp/Mods/Outdated,Text,,Mods ⚠,Mods ⚠,Mods ⚠
-SpaceWarp/Mods/Errored,Text,,Mods ⚠,Mods ⚠,Mods ⚠
-SpaceWarp,Text,,Space Warp,Space Warp,Space Warp
-SpaceWarp/VersionChecking,Text,,Allow Space Warp to check versions for mods over the network?,Deixar SpaceWarp verificar a versão dos mods na internet?,Autoriser Space Warp à vérifier les versions des mods sur le réseau ?
-SpaceWarp/Yes,Text,,Yes,Sim,Oui
-SpaceWarp/No,Text,,No,Não,Non
-SpaceWarp/Console/Header,Text,,SPACE WARP - Console,SPACE WARP - Consola,SPACE WARP - Console
-SpaceWarp/Console/Clear,Text,,Clear,Limpar,Effacer
-SpaceWarp/Console/AutoScroll,Text,,Auto Scroll,Scroll automatico,Défilement automatique
-SpaceWarp/Console/On,Text,,On,On,On
-SpaceWarp/Console/Off,Text,,Off,Off,Off
-SpaceWarp/AvcDialog/Title,Text,,spacewarp.avc,spacewarp.avc,spacewarp.avc
-SpaceWarp/AvcDialog/MainText,Text,,Allow SpaceWarp to automatically check for mod updates online?,Permitir que o SpaceWarp verifique automaticamente se há atualizações de mods online?,Autoriser SpaceWarp à vérifier automatiquement les mises à jour de mods en ligne ?
-SpaceWarp/AvcDialog/MinorText,Text,,*You can change this later in the settings menu.,*Pode alterá-lo mais tarde no menu de definições.,*Vous pouvez modifier plus tard dans le menu des réglages.
\ No newline at end of file
+Key,Type,Description,English,Portuguese BR,French,Italiano,Chinese-Simplified [zh-CN]
+SpaceWarp/ModList/EnableAll,Text,,Enable All,Ativar todos,Activer tous,Abilita tutto,全部启用
+SpaceWarp/ModList/DisableAll,Text,,Disable All,Desativar todos,Désactiver tous,Disabilita tutto,全部禁用
+SpaceWarp/ModList/RevertChanges,Text,,Revert Changes,Reverter alterações,Rétablir les changements,Annulla modifiche,还原更改
+SpaceWarp/ModList/ApplyChanges,Text,,Apply and restart,Aplicar e reiniciar,Appliquer et redémarrer,Applica e riavvia,应用并重新启动
+SpaceWarp/ModList/multipleChangesDetected,Text,,"{0} changes detected, please restart to apply them","{0} alterações detetadas, porfavor reinicie o jogo","{0} changements détectés, veuillez redémarrer pour les appliquer","{0} modifiche rilevate, si prega di riavviare per applicarle","{0}检测到更改,请重新启动以应用它们"
+SpaceWarp/ModList/singleChangeDetected,Text,,"1 change detected, please restart to apply it","1 alteração detectada, reinicie para aplicá-la","1 changement détecté, veuillez redémarrer pour l'appliquer","1 modifica rilevata, si prega di riavviare per applicarla","检测到 1 个更改,请重新启动以应用它"
+SpaceWarp/ModList/Header,Text,,spacewarp.modlist,spacewarp.modlist,spacewarp.modlist,spacewarp.modlist,模组列表
+SpaceWarp/ModList/CoreMods,Text,,Core Mods,Mods do jogo,Mods du jeu,Mod Principali,核心模组
+SpaceWarp/ModList/EnabledMods,Text,,Enabled Mods,Mods ativos,Mods activés,Mod Abilitate,启用的模组
+SpaceWarp/ModList/ErroredMods,Text,,Errored Mods,Mods com erros,Mods avec des erreurs,Mod con Errori,错误的模组
+SpaceWarp/ModList/DisabledMods,Text,,Disabled Mods,Mods desativados,Mods désactivés,Mod Disabilitate,禁用的模组
+SpaceWarp/ModList/Version,Text,,Version,Versão,Version,Versione,版本
+SpaceWarp/ModList/Author,Text,,Author,Autor,Auteur,Autore,作者
+SpaceWarp/ModList/Source,Text,,Source,Fonte,Source,Fonte,源
+SpaceWarp/ModList/Description,Text,,Description,Descrição,Description,Descrizione,描述
+SpaceWarp/ModList/KSP2Version,Text,,KSP2 Version,Versão do jogo,Version de KSP2,Versione KSP2,KSP2版本
+SpaceWarp/ModList/Dependencies,Text,,Dependencies,Dependências,Dépendances,Dipendenze,依赖
+SpaceWarp/ModList/outdated,Text,,(outdated),(desatualizado),(obsolète),(obsoleto),(已过时)
+SpaceWarp/ModList/unsupported,Text,,(unsupported),(não suportado),(non pris en charge),(non supportato),(不支持)
+SpaceWarp/ModList/disabled,Text,,(disabled),(desativado),(désactivé),(disabilitata),(已禁用)
+SpaceWarp/ModList/mismatched,Text,,(mismatched),(não corresponde com o swinfo),(non concordant),(non corrispondente),(不匹配)
+SpaceWarp/ModList/baddirectory,Text,,(bad directory),(problemas no diretorio),(mauvais dossier),(cartella errata),(目录错误)
+SpaceWarp/ModList/missingswinfo,Text,,(missing SW Info),(não possui um swinfo),(SW Info manquant),(mancanza di informazioni SW),(缺少SW信息)
+SpaceWarp/ModList/OpenModSettings,Text,,Mod Settings,Configurações dos mods,Paramètres des mods,Impostazioni Mod,模组设置
+SpaceWarp/ModList/OpenModsFolder,Text,,Open Mods Folder,Abrir pasta de mods,Ouvrir le dossier des mods,Apri Cartella Mod,打开Mods文件夹
+SpaceWarp/ModList/MissingDependency,Text,,Dependency is missing,Esta dependência não está presente,Cette dépendance est manquante,Mancanza di una dipendenza,缺少依赖项
+SpaceWarp/ModList/ErroredDependency,Text,,Dependency had an error while loading,Esta dependência teve um erro durante o loading,Une erreur s'est produite lors du chargement de cette dépendance,Errore nel caricamento di una dipendenza,依赖项在加载时出错
+SpaceWarp/ModList/DisabledDependency,Text,,Dependency is disabled,Esta dependência esta desativada,Cette dépendance est manquante,Dipendenza disabilitata,依赖项已禁用
+SpaceWarp/ModList/UnspecifiedDependency,Text,,Dependency was not specified in SW info,Esta dependência não está presente no swinfo,Cette dépendance n'a pas été spécifiée dans SW Info,Dipendenza non specificata nelle info SW,未在软件信息中指定依赖项
+SpaceWarp/ModList/UnsupportedDependency,Text,,Dependency is of an unsupported version,Esta dependência é de uma versão nao suportada,Cette dépendance est d'une version non supportée,Dipendenza di una versione non supportata,依赖项的版本不受支持
+SpaceWarp/ModList/Conflicts,Text,,Conflicts,Incompatibilidades,Conflits,Conflitti,冲突
+SpaceWarp/ModList/Details,Text,,Details,Detalhes,Détails,Dettagli,详细信息
+SpaceWarp/Mods,Text,,Mods,Mods,Mods,Mod,模组
+SpaceWarp/Mods/Outdated,Text,,Mods ⚠,Mods ⚠,Mods ⚠,Mod ⚠,模组 ⚠/color>
+SpaceWarp/Mods/Errored,Text,,Mods ⚠,Mods ⚠,Mods ⚠,Mod ⚠,模组 ⚠/color>
+SpaceWarp,Text,,Space Warp,Space Warp,Space Warp,Space Warp,空间扭曲
+SpaceWarp/VersionChecking,Text,,Allow Space Warp to check versions for mods over the network?,Deixar Space Warp verificar a versão dos mods na internet?,Autoriser Space Warp à vérifier les versions des mods sur le réseau ?,Autorizzare Space Warp a controllare le versioni delle mod su internet?,是否允许 Space Warp 通过网络检查模组的版本?
+SpaceWarp/Yes,Text,,Yes,Sim,Oui,Sì,是
+SpaceWarp/No,Text,,No,Não,Non,No,否
+SpaceWarp/Console/Header,Text,,SPACE WARP - Console,SPACE WARP - Consola,SPACE WARP - Console,SPACE WARP - Console,控制台
+SpaceWarp/Console/Clear,Text,,Clear,Limpar,Effacer,Pulisci,清理
+SpaceWarp/Console/AutoScroll,Text,,Auto Scroll,Scroll automatico,Défilement automatique,Scorrimento automatico,自动滚动
+SpaceWarp/Console/On,Text,,On,On,On,On,开
+SpaceWarp/Console/Off,Text,,Off,Off,Off,Off,关
+SpaceWarp/AvcDialog/Title,Text,,spacewarp.avc,spacewarp.avc,spacewarp.avc,spacewarp.avc,spacewarp.avc
+SpaceWarp/AvcDialog/MainText,Text,,Allow Space Warp to automatically check for mod updates online?,Permitir que o Space Warp verifique automaticamente se há atualizações de mods online?,Autoriser Space Warp à vérifier automatiquement les mises à jour de mods en ligne ?,Autorizzare Space Warp a controllare automaticamente gli aggiornamenti delle mod online?,允许 Space Warp 自动在线检查模组更新?
+SpaceWarp/AvcDialog/MinorText,Text,,*You can change this later in the settings menu.,*Pode alterá-lo mais tarde no menu de definições.,*Vous pouvez modifier plus tard dans le menu des réglages.,*Puoi cambiare questa impostazione più tardi nel menu delle impostazioni.,*您可以稍后在设置菜单中更改此设置。
+SpaceWarp/Menu/Apps,Text,,Apps,Aplicativos,Applications,Applicazioni,应用
diff --git a/plugin_template/BepInEx/plugins/SpaceWarp/swinfo.json b/plugin_template/BepInEx/plugins/SpaceWarp/swinfo.json
index 14722a3..3d99f0f 100644
--- a/plugin_template/BepInEx/plugins/SpaceWarp/swinfo.json
+++ b/plugin_template/BepInEx/plugins/SpaceWarp/swinfo.json
@@ -5,7 +5,7 @@
"name": "Space Warp",
"description": "Space Warp is an API for KSP 2 mod developers.",
"source": "https://github.com/SpaceWarpDev/SpaceWarp",
- "version": "1.8.1",
+ "version": "1.8.2",
"version_check": "https://raw.githubusercontent.com/SpaceWarpDev/SpaceWarp/main/plugin_template/BepInEx/plugins/SpaceWarp/swinfo.json",
"ksp2_version": {
"min": "0.2.0",
diff --git a/src/SpaceWarp.Core/API/Loading/Loading.cs b/src/SpaceWarp.Core/API/Loading/Loading.cs
index d85b3b0..90d9d31 100644
--- a/src/SpaceWarp.Core/API/Loading/Loading.cs
+++ b/src/SpaceWarp.Core/API/Loading/Loading.cs
@@ -80,6 +80,21 @@ public static void AddAddressablesLoadingAction(string name, string label, Ac
AddGeneralLoadingAction(() => new AddressableAction(name, label, action));
}
+ ///
+ /// Registers an action to be done on addressables after addressables have been loaded. Should be added either on Awake() or Start().
+ /// Allows to keep asset in memory after loading them. This is useful for textures or UXML templates, for example.
+ ///
+ /// The name of the action
+ /// The addressables label to hook into
+ /// Indicates if assets should be kept in memory after loading them.
+ /// The action to be done on each addressables asset
+ /// The type of asset that this action is done upon
+ public static void AddAddressablesLoadingAction(string name, string label, bool keepAssets, Action action)
+ where T : UnityObject
+ {
+ AddGeneralLoadingAction(() => new AddressableAction(name, label, keepAssets, action));
+ }
+
private static Action CreateAssetLoadingActionWithExtensions(string subfolder,
Func> importFunction, string[] extensions)
diff --git a/src/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs b/src/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
index 6f1b6cf..2fc6fe7 100644
--- a/src/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
+++ b/src/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
@@ -18,6 +18,7 @@ public class AddressableAction : FlowAction where T : UnityObject
{
private string _label;
private Action _action;
+ private bool _keepAssets;
///
/// Creates a new addressable loading action.
@@ -30,6 +31,19 @@ public AddressableAction(string name, string label, Action action) : base(nam
_label = label;
_action = action;
}
+
+ ///
+ /// Creates a new addressable loading action, with the option to keep the asset in memory after loading.
+ /// This is useful for textures or UXML templates, for example.
+ ///
+ /// Name of the action.
+ /// Label of the asset to load.
+ /// Action to perform on the loaded asset.
+ /// Allows to keep asset in memory after loading them.
+ public AddressableAction(string name, string label, bool keepAssets, Action action) : this(name, label, action)
+ {
+ _keepAssets = keepAssets;
+ }
private bool DoesLabelExist(object label)
{
@@ -55,7 +69,7 @@ public override void DoAction(Action resolve, Action reject)
{
GameManager.Instance.Assets.LoadByLabel(_label,_action,delegate(IList assetLocations)
{
- if (assetLocations != null)
+ if (assetLocations != null && !_keepAssets)
{
Addressables.Release(assetLocations);
}
diff --git a/src/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs b/src/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs
index a77deae..1d3b206 100644
--- a/src/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs
+++ b/src/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs
@@ -1,6 +1,8 @@
using HarmonyLib;
+using KSP.Game;
using KSP.Game.Load;
using KSP.IO;
+using Newtonsoft.Json;
using SpaceWarp.API.SaveGameManager;
using SpaceWarp.Backend.SaveGameManager;
using SpaceWarp.InternalUtilities;
@@ -11,6 +13,24 @@ namespace SpaceWarp.Patching.SaveGameManager;
internal class SaveLoadPatches
{
#region Saving
+
+ ///
+ /// Common method used before serialization to save plugin data, if any.
+ ///
+ private static void SavePluginData(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;
+ data.SavedGame = modSaveData;
+
+ // Initiate save callback for plugins that specified a callback function
+ foreach (var plugin in ModSaves.InternalPluginSaveData)
+ {
+ plugin.SaveEventCallback(plugin.SaveData);
+ }
+ }
[HarmonyPatch(typeof(SerializeGameDataFlowAction), MethodType.Constructor, [typeof(string), typeof(LoadGameData)])]
[HarmonyPostfix]
@@ -27,26 +47,122 @@ SerializeGameDataFlowAction __instance
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)
+ SavePluginData(data);
+ }
+
+ ///
+ /// Handles save game serialization in memory, like when launching from VAB. Current
+ /// game is serialized to a buffer and kept in memory.
+ ///
+ [HarmonyPatch(typeof(SerializeGameToMemoryFlowAction), MethodType.Constructor, [typeof(LoadOrSaveCampaignTicket)])]
+ [HarmonyPostfix]
+ private static void InjectToMemoryPluginSaveGameData(
+ LoadOrSaveCampaignTicket loadOrSaveCampaignTicket,
+ // ReSharper disable once InconsistentNaming
+ SerializeGameToMemoryFlowAction __instance
+ )
+ {
+ // Skip plugin data injection if there are no mods that have registered for save/load actions
+ if (ModSaves.InternalPluginSaveData.Count == 0)
{
- plugin.SaveEventCallback(plugin.SaveData);
+ return;
}
+
+ SavePluginData(loadOrSaveCampaignTicket.LoadGameData);
}
#endregion
#region Loading
+ ///
+ /// Common method used after deserialization to load plugin data, if any.
+ ///
+ private static void LoadPluginSaveData(SpaceWarpSerializedSavedGame serializedSavedGame)
+ {
+ // Perform plugin load data if plugin data is found in the save file
+ if (serializedSavedGame.serializedPluginSaveData.Count <= 0) return;
+
+ // 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)
+ {
+ SpaceWarpPlugin.Instance.SWLogger.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
+ );
+ }
+ }
+
+ ///
+ /// DeserializeBufferFlowAction is used when reverting to VAB / Launch from flight
+ ///
+ [HarmonyPatch(typeof(DeserializeBufferFlowAction), "DoAction")]
+ [HarmonyPrefix]
+ private static bool DeserializeBufferLoadedPluginData(
+ Action resolve,
+ Action reject,
+ // ReSharper disable once InconsistentNaming
+ DeserializeBufferFlowAction __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
+ {
+ if (DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings == null)
+ {
+ DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings = IOProvider.CloneSerializerSettings(IOProvider.GetDefaultSerializerSettings());
+ DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings.NullValueHandling = NullValueHandling.Ignore;
+ }
+
+ // Deserialize save buffer to our own class that extends game's SerializedSavedGame
+ var serializedSavedGame = IOProvider.FromBuffer(__instance._savedGameBuffer, DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings);
+ __instance._data.SavedGame = serializedSavedGame;
+ __instance._data.DataLength = (long) __instance._savedGameBuffer.Length;
+
+ // Perform plugin load data if plugin data is found in the save file
+ LoadPluginSaveData(serializedSavedGame);
+ }
+ catch (Exception ex)
+ {
+ UnityEngine.Debug.LogException(ex);
+ reject(ex.Message);
+ }
+
+ resolve();
+
+ return false;
+ }
+
+ ///
+ /// DeserializeContentsFlowAction is used when loading a save file
+ ///
[HarmonyPatch(typeof(DeserializeContentsFlowAction), "DoAction")]
[HarmonyPrefix]
- private static bool DeserializeLoadedPluginData(
+ private static bool DeserializeContentsLoadedPluginData(
Action resolve,
Action reject,
// ReSharper disable once InconsistentNaming
@@ -68,35 +184,7 @@ DeserializeContentsFlowAction __instance
__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)
- {
- SpaceWarpPlugin.Instance.SWLogger.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
- );
- }
- }
+ LoadPluginSaveData(serializedSavedGame);
}
catch (Exception ex)
{
diff --git a/src/SpaceWarp.Game/API/Game/Waypoints/SerializedWaypoint.cs b/src/SpaceWarp.Game/API/Game/Waypoints/SerializedWaypoint.cs
new file mode 100644
index 0000000..ea136fe
--- /dev/null
+++ b/src/SpaceWarp.Game/API/Game/Waypoints/SerializedWaypoint.cs
@@ -0,0 +1,49 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+
+namespace SpaceWarp.API.Game.Waypoints;
+
+///
+/// This contains the serialized information for a waypoint, used for saving/loading waypoints
+///
+[Serializable]
+[method: JsonConstructor]
+[PublicAPI]
+public class SerializedWaypoint(string name, string bodyName, double latitude, double longitude, double altitude, WaypointState state)
+{
+ ///
+ /// The name of the waypoint
+ ///
+ public string Name => name;
+
+ ///
+ /// The body the waypoint is on
+ ///
+ public string BodyName => bodyName;
+
+ ///
+ /// The latitude of the waypoint
+ ///
+ public double Latitude => latitude;
+
+ ///
+ /// The longitude of the waypoint
+ ///
+ public double Longitude => longitude;
+
+ ///
+ /// The altitude of the waypoint
+ ///
+ public double Altitude => altitude;
+
+ ///
+ /// The current state of the waypoint
+ ///
+ public WaypointState State => state;
+
+ ///
+ /// Deserializes the waypoint, creating an actual waypoint from it
+ ///
+ /// A newly created waypoint from the serialized waypoint's parameters
+ public virtual Waypoint Deserialize() => new(latitude, longitude, altitude, bodyName, name, state);
+}
\ No newline at end of file
diff --git a/src/SpaceWarp.Game/API/Game/Waypoints/Waypoint.cs b/src/SpaceWarp.Game/API/Game/Waypoints/Waypoint.cs
new file mode 100644
index 0000000..502143d
--- /dev/null
+++ b/src/SpaceWarp.Game/API/Game/Waypoints/Waypoint.cs
@@ -0,0 +1,241 @@
+using JetBrains.Annotations;
+using KSP.Game;
+using KSP.Sim;
+using KSP.Sim.Definitions;
+using KSP.Sim.impl;
+using Newtonsoft.Json;
+
+namespace SpaceWarp.API.Game.Waypoints;
+
+///
+/// A handle for a waypoint in the flight/map view for KSP2
+///
+[PublicAPI]
+public class Waypoint
+{
+
+
+ private SimulationObjectModel _waypointObject;
+
+ ///
+ /// The current name of the waypoint
+ ///
+ public string Name => State == WaypointState.Visible ? _waypointObject.Name : _hiddenRename;
+
+ ///
+ /// The current body that the waypoint is placed on
+ ///
+ public string BodyName { get; private set; }
+
+ ///
+ /// The current latitude of the waypoint
+ ///
+ public double Latitude { get; private set; }
+
+ ///
+ /// The current longitude of the waypoint
+ ///
+ public double Longitude { get; private set; }
+
+ ///
+ /// The current altitude of the waypoint
+ ///
+ public double AltitudeFromRadius { get; private set; }
+
+ [CanBeNull] private string _hiddenRename;
+
+ private WaypointState _state = WaypointState.Visible;
+
+ ///
+ /// Set the state of the waypoint to either being hidden/shown
+ ///
+ /// Thrown when trying to set the state of a destroyed waypoint
+ public WaypointState State
+ {
+ get => _state;
+ set
+ {
+ if (_isDestroyed)
+ {
+ throw new Exception("Waypoint was already destroyed");
+ }
+
+ if (value == _state) return;
+ _state = value;
+ if (value == WaypointState.Hidden)
+ {
+ _hiddenRename = Name;
+ _waypointObject.Destroy();
+ _waypointObject = null;
+ }
+ else
+ {
+ var spaceSimulation = GameManager.Instance.Game.SpaceSimulation;
+ var celestialBodies = GameManager.Instance.Game.UniverseModel.GetAllCelestialBodies();
+ var body = celestialBodies.Find(c => c.Name == BodyName);
+ if (body == null)
+ throw new Exception($"Could not create waypoint as there is no body with the name of {BodyName}");
+ var waypointComponentDefinition = new WaypointComponentDefinition { Name = _hiddenRename };
+ _waypointObject = spaceSimulation.CreateWaypointSimObject(
+ waypointComponentDefinition, body, Latitude, Longitude, AltitudeFromRadius);
+ _hiddenRename = null;
+ }
+ }
+ }
+
+ private bool _isDestroyed;
+
+ ///
+ /// Whether or not this waypoint has been destroyed
+ ///
+ public bool IsDestroyed => _isDestroyed || _waypointObject is { _isDestroyed: true };
+
+ private static long _nextID;
+
+ ///
+ /// Creates a waypoint handle from a preexisting waypoint
+ ///
+ /// The preexisting waypoint
+ public Waypoint(WaypointComponent preexistingWaypoint)
+ {
+ _waypointObject = preexistingWaypoint.SimulationObject;
+ var body = _waypointObject.transform.parent.transform.objectModel.CelestialBody;
+ BodyName = body.Name;
+ body.GetLatLonAltFromRadius(_waypointObject.transform.Position, out var latitude, out var longitude,
+ out var altitudeFromRadius);
+ Latitude = latitude;
+ Longitude = longitude;
+ AltitudeFromRadius = altitudeFromRadius;
+ }
+
+ ///
+ /// Create a new waypoint at the specified location
+ ///
+ /// The latitude of the waypoint
+ /// The longitude of the waypoint
+ /// The altitude of the waypoint, if null it defaults to the height of the terrain at the specified latitude/longitude
+ /// The body that the waypoint is around, if null it defaults to the current active vehicles body
+ /// The name of the waypoint, if null defaults to Waypoint-{sequential_number}
+ /// The initial state of the waypoint, whether or not it is visible
+ /// Thrown if there is no body with the name bodyName
+ public Waypoint(double latitude, double longitude, double? altitudeFromRadius = null,
+ [CanBeNull] string bodyName = null, [CanBeNull] string name = null,
+ WaypointState waypointState = WaypointState.Visible)
+ {
+ BodyName = bodyName ??= Vehicle.ActiveSimVessel.mainBody.Name;
+ Latitude = latitude;
+ Longitude = longitude;
+ var spaceSimulation = GameManager.Instance.Game.SpaceSimulation;
+ var celestialBodies = GameManager.Instance.Game.UniverseModel.GetAllCelestialBodies();
+ var body = celestialBodies.Find(c => c.Name == bodyName);
+ if (body == null)
+ throw new Exception($"Could not create waypoint as there is no body with the name of {bodyName}");
+ altitudeFromRadius ??= body.SurfaceProvider.GetTerrainAltitudeFromCenter(latitude, longitude) - body.radius;
+ AltitudeFromRadius = altitudeFromRadius.Value;
+ _state = waypointState;
+ if (_state == WaypointState.Visible)
+ {
+ var waypointComponentDefinition = new WaypointComponentDefinition
+ { Name = name ?? $"Waypoint-{_nextID++}" };
+ _waypointObject = spaceSimulation.CreateWaypointSimObject(
+ waypointComponentDefinition, body, latitude, longitude, altitudeFromRadius.Value);
+ }
+ else
+ {
+ _hiddenRename = name ?? $"Waypoint-{_nextID++}";
+ }
+ }
+
+ ///
+ /// Destroys this waypoint
+ ///
+ /// Thrown if the waypoint was already destroyed
+ public void Destroy()
+ {
+ if (IsDestroyed)
+ {
+ throw new Exception("Waypoint was already destroyed");
+ }
+
+ if (State == WaypointState.Visible)
+ {
+ _waypointObject.Destroy();
+ _waypointObject = null;
+ }
+
+ _isDestroyed = true;
+ }
+
+ ///
+ /// Moves a waypoint to another position
+ ///
+ /// The new latitude of the waypoint
+ /// The new longitude of the waypoint
+ /// The altitude of the waypoint, if null it defaults to the height of the terrain at the specified latitude/longitude
+ /// The body that the waypoint is around, if null it defaults to the waypoints current body
+ /// Thrown if the waypoint is destroyed, or if there is no waypoint with the name bodyName
+ public void Move(double latitude, double longitude, double? altitudeFromRadius = null,
+ [CanBeNull] string bodyName = null)
+ {
+ if (IsDestroyed)
+ {
+ throw new Exception("Waypoint was already destroyed");
+ }
+
+ bodyName ??= BodyName;
+ var celestialBodies = GameManager.Instance.Game.UniverseModel.GetAllCelestialBodies();
+ var body = celestialBodies.Find(c => c.Name == bodyName);
+ if (body == null)
+ throw new Exception($"Could not create waypoint as there is no body with the name of {bodyName}");
+ altitudeFromRadius ??= body.SurfaceProvider.GetTerrainAltitudeFromCenter(latitude, longitude) - body.radius;
+ BodyName = bodyName;
+ Latitude = latitude;
+ Longitude = longitude;
+ AltitudeFromRadius = altitudeFromRadius.Value;
+ if (_state != WaypointState.Visible) return;
+ var bodyFrame = body.transform.bodyFrame;
+ var relSurfacePosition = body.GetRelSurfacePosition(latitude, longitude, altitudeFromRadius.Value);
+ _waypointObject.transform.parent = bodyFrame;
+ _waypointObject.transform.Position = new Position(bodyFrame, relSurfacePosition);
+ }
+
+ ///
+ /// Renames a waypoint
+ ///
+ /// The new name for the waypoint, if null defaults to Waypoint-{sequential_number}
+ /// Thrown if the waypoint is destroyed
+ public void Rename([CanBeNull] string name = null)
+ {
+ if (IsDestroyed)
+ {
+ throw new Exception("Waypoint was already destroyed");
+ }
+
+ if (State == WaypointState.Visible)
+ {
+ _waypointObject.Name = name ?? $"Waypoint-{_nextID++}";
+ }
+ else
+ {
+ _hiddenRename = _waypointObject.Name;
+ }
+ }
+
+
+ ///
+ /// Hides the waypoint
+ ///
+ public void Hide() => State = WaypointState.Hidden;
+
+ ///
+ /// Shows the waypoint
+ ///
+ public void Show() => State = WaypointState.Visible;
+
+ ///
+ /// Serializes the waypoint to be saved in save data
+ ///
+ /// The waypoint as a serialized record
+ public virtual SerializedWaypoint Serialize() =>
+ new(Name, BodyName, Latitude, Longitude, AltitudeFromRadius, State);
+}
\ No newline at end of file
diff --git a/src/SpaceWarp.Game/API/Game/Waypoints/WaypointState.cs b/src/SpaceWarp.Game/API/Game/Waypoints/WaypointState.cs
new file mode 100644
index 0000000..56c8fc1
--- /dev/null
+++ b/src/SpaceWarp.Game/API/Game/Waypoints/WaypointState.cs
@@ -0,0 +1,17 @@
+namespace SpaceWarp.API.Game.Waypoints;
+
+///
+/// This contains the state for a waypoint
+///
+public enum WaypointState
+{
+ ///
+ /// The waypoint is shown in the flight/map view
+ ///
+ Visible,
+
+ ///
+ /// The waypoint is hidden in the flight/map view
+ ///
+ Hidden
+}
\ No newline at end of file
diff --git a/src/SpaceWarp.Game/Modules/Game.cs b/src/SpaceWarp.Game/Modules/Game.cs
index c9db243..b6a3944 100644
--- a/src/SpaceWarp.Game/Modules/Game.cs
+++ b/src/SpaceWarp.Game/Modules/Game.cs
@@ -1,7 +1,14 @@
-using JetBrains.Annotations;
+using System.Reflection;
+using HarmonyLib;
+using JetBrains.Annotations;
using KSP.Game;
using KSP.Messages;
+using SpaceWarp.API.Assets;
using SpaceWarp.API.Game.Messages;
+using SpaceWarp.API.Game.Waypoints;
+using UnityEngine;
+using ILogger = SpaceWarp.API.Logging.ILogger;
+using Object = UnityEngine.Object;
namespace SpaceWarp.Modules;
@@ -11,9 +18,18 @@ namespace SpaceWarp.Modules;
[UsedImplicitly]
public class Game : SpaceWarpModule
{
+
+ internal static ILogger Logger;
///
public override string Name => "SpaceWarp.Game";
+ ///
+ public override void PreInitializeModule()
+ {
+ Logger = ModuleLogger;
+ Harmony.CreateAndPatchAll(typeof(Patches.MapPatches));
+ }
+
///
public override void InitializeModule()
{
diff --git a/src/SpaceWarp.Game/Patches/MapPatches.cs b/src/SpaceWarp.Game/Patches/MapPatches.cs
new file mode 100644
index 0000000..0d0c3e5
--- /dev/null
+++ b/src/SpaceWarp.Game/Patches/MapPatches.cs
@@ -0,0 +1,18 @@
+using HarmonyLib;
+using KSP.Map;
+using KSP.Sim;
+using KSP.Sim.impl;
+using UnityEngine;
+
+namespace SpaceWarp.Patches;
+
+[HarmonyPatch]
+internal static class MapPatches
+{
+ [HarmonyPatch(typeof(Map3DView), nameof(Map3DView.ProcessSingleMapItem))]
+ [HarmonyPrefix]
+ // ReSharper disable once InconsistentNaming
+ public static bool SkipBadItems(Map3DView __instance, MapItem item) =>
+ item.MapItemType != MapItemType.Waypoint ||
+ ((ISimulationModelMap)__instance.Game.UniverseModel).FromGlobalId(item.SimGUID) != null;
+}
\ No newline at end of file
diff --git a/src/SpaceWarp.Game/SpaceWarp.Game.csproj b/src/SpaceWarp.Game/SpaceWarp.Game.csproj
index d90289f..0959ba0 100644
--- a/src/SpaceWarp.Game/SpaceWarp.Game.csproj
+++ b/src/SpaceWarp.Game/SpaceWarp.Game.csproj
@@ -1,10 +1,18 @@
+
+ SpaceWarp
+
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/src/SpaceWarp.UI/Backend/UI/Appbar/AppbarBackend.cs b/src/SpaceWarp.UI/Backend/UI/Appbar/AppbarBackend.cs
index 1169473..ed55555 100644
--- a/src/SpaceWarp.UI/Backend/UI/Appbar/AppbarBackend.cs
+++ b/src/SpaceWarp.UI/Backend/UI/Appbar/AppbarBackend.cs
@@ -95,7 +95,7 @@ public static GameObject AddButton(string buttonText, Sprite buttonIcon, string
var localizer = text.gameObject.GetComponent();
if (localizer)
{
- UnityObject.Destroy(localizer);
+ localizer.Term = buttonText;
}
// Change the icon.
@@ -260,7 +260,7 @@ public static void AddOABButton(string buttonText, Sprite buttonIcon, string but
var localizer = text.gameObject.GetComponent();
if (localizer)
{
- UnityObject.Destroy(localizer);
+ localizer.Term = buttonText;
}
// Change the icon.
@@ -303,21 +303,28 @@ private static void SetOABTrayState(bool state)
#region KSC App Bar
private static GameObject _kscTray;
-
+
// ReSharper disable once InconsistentNaming
private static GameObject KSCTray
{
get
{
if (_kscTray == null)
- {
- return _kscTray = CreateKSCTray();
+ {
+ return _kscTray = CreateKSCTray();
}
return _kscTray;
}
}
+ private const string KscMenuPath =
+ "GameManager/Default Game Instance(Clone)/UI Manager(Clone)/Main Canvas/KSCMenu(Clone)/Panel/Window-FacilityMenu/GRP-Body/Content/Menu";
+
+ private const string FlyoutName = "LaunchLocationFlyoutHeaderToggle";
+
+ private const string TargetName = "LaunchLocationsFlyoutTarget";
+
// ReSharper disable once InconsistentNaming
private static GameObject CreateKSCTray()
{
@@ -326,8 +333,8 @@ private static GameObject CreateKSCTray()
// Find the KSC launch locations menu item; it will be used for cloning the app tray
// Get the Launch Pads menu item
- var kscMenu = GameObject.Find("GameManager/Default Game Instance(Clone)/UI Manager(Clone)/Main Canvas/KSCMenu(Clone)/LandingPanel/InteriorWindow/MenuButtons/Content/Menu");
- var launchLocationsButton = kscMenu != null ? kscMenu.GetChild("LaunchLocationFlyoutHeaderToggle") : null;
+ var kscMenu = GameObject.Find(KscMenuPath);
+ var launchLocationsButton = kscMenu != null ? kscMenu.GetChild(FlyoutName) : null;
if (kscMenu == null || launchLocationsButton == null)
{
@@ -340,38 +347,57 @@ private static GameObject CreateKSCTray()
kscAppTrayButton.name = "KSC-AppTrayButton";
// Set the button icon (use OAB app tray icon)
- var image = kscAppTrayButton.GetChild("Header").GetChild("Content").GetChild("Icon Panel").GetChild("icon").GetComponent();
+ var image = kscAppTrayButton.GetChild("ICO-Launchpad").GetComponent();
var tex = AssetManager.GetAsset($"{SpaceWarpPlugin.ModGuid}/images/oabTrayButton.png");
tex.filterMode = FilterMode.Point;
image.sprite = Sprite.Create(tex, new Rect(0, 0, 32, 32), new Vector2(0.5f, 0.5f));
// Change the text to APPS
- var title = kscAppTrayButton.GetChild("Header").GetChild("Content").GetChild("Title");
+ var title = kscAppTrayButton.GetChild("TXT-Launchpad");
{
// Suppress renaming of the button to Launchpad
+ title.GetComponent().text = "Apps"; // Set the text in case term does not exist
var localizer = title.GetComponent();
- if (localizer)
- {
- UnityObject.Destroy(localizer);
- }
- var text = title.GetComponent();
- text.text = "Apps";
+ localizer.Term = "SpaceWarp/Menu/Apps";
}
// Get the popup tray and rename it
- var kscAppTray = kscAppTrayButton.GetChild("LaunchLocationsFlyoutTarget");
+ var originalFlyout = kscMenu.GetChild(TargetName);
+ var originalToggle = launchLocationsButton.GetComponent();
+ var kscAppTray = UnityObject.Instantiate(originalFlyout, kscMenu.transform);
kscAppTray.name = "KSC-AppTray";
+
// Delete existing buttons and separators in the tray
for (var i = 0; i < kscAppTray.transform.childCount; i++)
{
var child = kscAppTray.transform.GetChild(i);
-
+
// Destroy all objects inside the tray, but keep the arrow ("thingy") that points to the menu button
- if (!child.name.ToLowerInvariant().Contains("thingy"))
+ if (!child.name.ToLowerInvariant().Contains("bg-panel"))
UnityObject.Destroy(child.gameObject);
}
+ var toggleFlyout = kscAppTrayButton.GetComponent();
+ toggleFlyout.OnDisable();
+ toggleFlyout._target = kscAppTray;
+ toggleFlyout._toggle = kscAppTrayButton.GetComponent();
+ toggleFlyout._toggleCanvas = kscAppTray.GetComponent