diff --git a/Directory.Build.props b/Directory.Build.props
index abe159c5..582628b6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,8 @@
- 1.4.3
+ 1.5.0
netstandard2.0
+ SpaceWarp
11
true
false
@@ -28,4 +29,23 @@
System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SpaceWarp/API/Assets/AssetManager.cs b/SpaceWarp.Core/API/Assets/AssetManager.cs
similarity index 99%
rename from SpaceWarp/API/Assets/AssetManager.cs
rename to SpaceWarp.Core/API/Assets/AssetManager.cs
index efc50150..d1d956b2 100644
--- a/SpaceWarp/API/Assets/AssetManager.cs
+++ b/SpaceWarp.Core/API/Assets/AssetManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using SpaceWarp.API.Lua;
using UnityEngine;
using Logger = BepInEx.Logging.Logger;
@@ -8,6 +9,7 @@
namespace SpaceWarp.API.Assets;
[SpaceWarpLuaAPI("Assets")]
+[PublicAPI]
public static class AssetManager
{
private static readonly Dictionary AllAssets = new();
@@ -135,7 +137,7 @@ public static bool TryGetAsset(string path, out T asset) where T : UnityObjec
return true;
}
-
+
///
/// Gets an asset from the specified asset path
///
diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs
new file mode 100644
index 00000000..b84977f6
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/BepInExConfigEntry.cs
@@ -0,0 +1,45 @@
+using System;
+using BepInEx.Configuration;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class BepInExConfigEntry : IConfigEntry
+{
+ public readonly ConfigEntryBase EntryBase;
+
+ public BepInExConfigEntry(ConfigEntryBase entryBase)
+ {
+ EntryBase = entryBase;
+ }
+
+ public object Value
+ {
+ get => EntryBase.BoxedValue;
+ set => EntryBase.BoxedValue = value;
+ }
+ public Type ValueType => EntryBase.SettingType;
+
+ 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)}");
+ }
+
+ EntryBase.BoxedValue = Convert.ChangeType(value, ValueType);
+ }
+
+ public string Description => EntryBase.Description.Description;
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs
new file mode 100644
index 00000000..720a2ff3
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/BepInExConfigFile.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using BepInEx.Configuration;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class BepInExConfigFile : IConfigFile
+{
+
+ public readonly ConfigFile AdaptedConfigFile;
+
+ public BepInExConfigFile(ConfigFile adaptedConfigFile)
+ {
+ AdaptedConfigFile = adaptedConfigFile;
+ }
+
+ public void Save()
+ {
+ AdaptedConfigFile.Save();
+ }
+
+ public IConfigEntry this[string section, string key] => new BepInExConfigEntry(AdaptedConfigFile[section, key]);
+
+ public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "")
+ {
+ return new BepInExConfigEntry(AdaptedConfigFile.Bind(section, key, defaultValue, description));
+ }
+
+ public IReadOnlyList Sections => AdaptedConfigFile.Keys.Select(x => x.Section).Distinct().ToList();
+
+ public IReadOnlyList this[string section] => AdaptedConfigFile.Keys.Where(x => x.Section == section)
+ .Select(x => x.Key).ToList();
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/ConfigValue.cs b/SpaceWarp.Core/API/Configuration/ConfigValue.cs
new file mode 100644
index 00000000..0eb6b192
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/ConfigValue.cs
@@ -0,0 +1,25 @@
+using System;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class ConfigValue
+{
+ public IConfigEntry Entry;
+
+ public ConfigValue(IConfigEntry entry)
+ {
+ Entry = entry;
+ if (typeof(T) != entry.ValueType)
+ {
+ throw new ArgumentException(nameof(entry));
+ }
+ }
+
+ public T Value
+ {
+ get => (T)Entry.Value;
+ set => Entry.Value = value;
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs b/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs
new file mode 100644
index 00000000..7be78914
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/EmptyConfigFile.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class EmptyConfigFile : IConfigFile
+{
+ public void Save()
+ {
+ }
+
+ public IConfigEntry this[string section, string key] => throw new KeyNotFoundException($"{section}/{key}");
+
+ public IConfigEntry Bind(string section, string key, T defaultValue = default, string description = "")
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public IReadOnlyList Sections => new List();
+
+ public IReadOnlyList this[string section] => throw new KeyNotFoundException($"{section}");
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/IConfigEntry.cs b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs
new file mode 100644
index 00000000..36dba68d
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/IConfigEntry.cs
@@ -0,0 +1,15 @@
+using System;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public interface IConfigEntry
+{
+ public object Value { get; set; }
+ public Type ValueType { get; }
+ public T Get() where T : class;
+ public void Set(T value);
+
+ public string Description { get; }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/IConfigFile.cs b/SpaceWarp.Core/API/Configuration/IConfigFile.cs
new file mode 100644
index 00000000..7247c455
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/IConfigFile.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public interface IConfigFile
+{
+ public void Save();
+
+ 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/JsonConfigEntry.cs b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs
new file mode 100644
index 00000000..07453942
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/JsonConfigEntry.cs
@@ -0,0 +1,53 @@
+using System;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class JsonConfigEntry : IConfigEntry
+{
+ private readonly JsonConfigFile _configFile;
+ private object _value;
+
+ public JsonConfigEntry(JsonConfigFile configFile, Type type, string description, object value)
+ {
+ _configFile = configFile;
+ _value = value;
+ Description = description;
+ ValueType = type;
+ }
+
+
+ public object Value
+ {
+ get => _value;
+ set
+ {
+ _value = value;
+ _configFile.Save();
+ }
+ }
+
+ 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 Description { get; }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs
new file mode 100644
index 00000000..34f5c959
--- /dev/null
+++ b/SpaceWarp.Core/API/Configuration/JsonConfigFile.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using JetBrains.Annotations;
+using KSP.IO;
+using Newtonsoft.Json.Linq;
+using UnityEngine;
+
+namespace SpaceWarp.API.Configuration;
+
+[PublicAPI]
+public class JsonConfigFile : IConfigFile
+{
+ [CanBeNull] private JObject _previousConfigObject;
+
+ private Dictionary> _currentEntries = new();
+ private readonly string _file;
+
+ public JsonConfigFile(string file)
+ {
+ // Use .cfg as this is going to have comments and that will be an issue
+ if (File.Exists(file))
+ {
+ try
+ {
+ _previousConfigObject = JObject.Parse(File.ReadAllText(file));
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"Error in attempting to load previous config file at '{file}': {e}");
+ // ignored
+ }
+ }
+
+ _file = file;
+ }
+
+ public void Save()
+ {
+ 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))
+ {
+ hadPreviousSection = DumpSection(hadPreviousSection, result, section);
+ }
+ result.AppendLine("\n}");
+ File.WriteAllText(_file, result.ToString());
+ }
+
+ private static bool DumpSection(bool hadPreviousSection, StringBuilder result, KeyValuePair> section)
+ {
+ if (hadPreviousSection)
+ {
+ result.AppendLine(",");
+ }
+
+ result.AppendLine($" \"{section.Key.Replace("\"", "\\\"").Replace("\n", "\\\n")}\": {{");
+ var hadPreviousKey = false;
+ foreach (var entry in section.Value)
+ {
+ hadPreviousKey = DumpEntry(result, hadPreviousKey, entry);
+ }
+
+ result.Append("\n }");
+ return true;
+ }
+
+ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValuePair entry)
+ {
+ if (hadPreviousKey)
+ {
+ result.AppendLine(",");
+ }
+
+ // result.AppendLine($" // {entry.Value.Description}");
+ if (entry.Value.Description != "")
+ {
+ var descriptionLines = entry.Value.Description.Split('\n').Select(x => x.TrimEnd());
+ foreach (var line in descriptionLines)
+ {
+ result.AppendLine($" // {line}");
+ }
+ }
+
+ var serialized = IOProvider.ToJson(entry.Value.Value);
+ var serializedLines = serialized.Split('\n').Select(x => x.TrimEnd()).ToArray();
+ if (serializedLines.Length > 1)
+ {
+ result.AppendLine($" \"{entry.Key.Replace("\"", "\\\"").Replace("\n", "\\\n")}\": ");
+ for (var i = 0; i < serializedLines.Length; i++)
+ {
+ if (i != serializedLines.Length - 1)
+ {
+ result.AppendLine($" {serializedLines[i]}");
+ }
+ else
+ {
+ result.Append($" {serializedLines[i]}");
+ }
+ }
+ }
+ else
+ {
+ result.Append($" \"{entry.Key.Replace("\"", "\\\"").Replace("\n", "\\\n")}\": {serializedLines[0]}");
+ }
+
+ return true;
+ }
+
+ 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))
+ {
+ 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(typeof(T));
+ previousSection[key] = new JsonConfigEntry(this, typeof(T), description, previousValue);
+ }
+ else
+ {
+ previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue);
+ }
+ }
+ catch
+ {
+ previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue);
+ // ignored
+ }
+ }
+ else
+ {
+ previousSection[key] = new JsonConfigEntry(this, typeof(T), description, defaultValue);
+ }
+
+ Save();
+ return previousSection[key];
+ }
+
+ public IReadOnlyList Sections => _currentEntries.Keys.ToList();
+
+ public IReadOnlyList this[string section] => _currentEntries[section].Keys.ToList();
+}
\ No newline at end of file
diff --git a/SpaceWarp/API/Loading/Loading.cs b/SpaceWarp.Core/API/Loading/Loading.cs
similarity index 94%
rename from SpaceWarp/API/Loading/Loading.cs
rename to SpaceWarp.Core/API/Loading/Loading.cs
index 538c6b39..e9a0a82d 100644
--- a/SpaceWarp/API/Loading/Loading.cs
+++ b/SpaceWarp.Core/API/Loading/Loading.cs
@@ -5,21 +5,17 @@
using KSP.Game.Flow;
using SpaceWarp.API.Assets;
using SpaceWarp.API.Mods;
-using SpaceWarp.API.Mods.JSON;
using SpaceWarp.InternalUtilities;
using SpaceWarp.Patching.LoadingActions;
namespace SpaceWarp.API.Loading;
+[PublicAPI]
public static class Loading
{
internal static List> DescriptorLoadingActionGenerators =
new();
- internal static List>
- FallbackDescriptorLoadingActionGenerators =
- new();
-
internal static List> LoadingActionGenerators = new();
internal static List> GeneralLoadingActions = new();
@@ -33,11 +29,7 @@ internal static List>
public static void AddAssetLoadingAction(string subfolder, string name,
Func> importFunction, params string[] extensions)
{
- AddModLoadingAction(name,
- extensions.Length == 0
- ? CreateAssetLoadingActionWithoutExtension(subfolder, importFunction)
- : CreateAssetLoadingActionWithExtensions(subfolder, importFunction, extensions));
- FallbackDescriptorLoadingActionGenerators.Add(d => new DescriptorLoadingAction(name,
+ DescriptorLoadingActionGenerators.Add(d => new DescriptorLoadingAction(name,
extensions.Length == 0
? CreateAssetLoadingActionWithoutExtensionDescriptor(subfolder, importFunction)
: CreateAssetLoadingActionWithExtensionsDescriptor(subfolder, importFunction, extensions), d));
@@ -67,7 +59,7 @@ public static void AddDescriptorLoadingAction(string name, Action
/// Registers a general loading action. Should be added either on Awake() or Start().
///
- /// The action generator
+ /// The action generator
public static void AddGeneralLoadingAction(Func actionGenerator)
{
GeneralLoadingActions.Add(actionGenerator);
diff --git a/SpaceWarp/API/Loading/SaveLoad.cs b/SpaceWarp.Core/API/Loading/SaveLoad.cs
similarity index 96%
rename from SpaceWarp/API/Loading/SaveLoad.cs
rename to SpaceWarp.Core/API/Loading/SaveLoad.cs
index aec8e10c..cd617ef5 100644
--- a/SpaceWarp/API/Loading/SaveLoad.cs
+++ b/SpaceWarp.Core/API/Loading/SaveLoad.cs
@@ -1,20 +1,22 @@
using System;
+using JetBrains.Annotations;
using KSP.Game.Flow;
using SpaceWarp.Patching;
namespace SpaceWarp.API.Loading;
+[PublicAPI]
public static class SaveLoad
{
///
/// Construct and add a FlowAction to the Game's load sequence.
- ///
+ ///
/// FlowActionType must have a public constructor that takes either no arguments,
/// or a single GameManager.
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -37,15 +39,15 @@ public static class SaveLoad
/// Thrown if FlowActionType does not have a valid Constructor
public static void AddFlowActionToGameLoadAfter(string referenceAction) where FlowActionType : FlowAction
{
- SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FLOW_METHOD_STARTGAME);
+ SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FlowMethodStartgame);
}
///
/// Add a FlowAction to the Game's load sequence.
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -67,12 +69,12 @@ public static void AddFlowActionToGameLoadAfter(string reference
/// The name of the action to insert a FlowActionType after. Use null to insert it at the start.
public static void AddFlowActionToGameLoadAfter(FlowAction flowAction, string referenceAction)
{
- SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FLOW_METHOD_STARTGAME);
+ SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FlowMethodStartgame);
}
///
/// Add a FlowAction to the save file loading sequence. A new FlowActionType is constructed every load.
- ///
+ ///
///
/// FlowActionType must have a public constructor that at most one of each of the following types:
///
@@ -81,10 +83,10 @@ public static void AddFlowActionToGameLoadAfter(FlowAction flowAction, string re
/// - LoadGameData
///
///
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -148,15 +150,15 @@ public static void AddFlowActionToGameLoadAfter(FlowAction flowAction, string re
/// Thrown if FlowActionType does not have a valid Constructor
public static void AddFlowActionToCampaignLoadAfter(string referenceAction) where FlowActionType : FlowAction
{
- SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FLOW_METHOD_PRIVATELOADCOMMON);
+ SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FlowMethodPrivateloadcommon);
}
///
/// Add a FlowAction to the save file loading sequence. The same object is used for every load.
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -219,12 +221,12 @@ public static void AddFlowActionToCampaignLoadAfter(string refer
/// The name of the action to insert a FlowActionType after. Use null to insert it at the start.
public static void AddFlowActionToCampaignLoadAfter(FlowAction flowAction, string referenceAction)
{
- SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FLOW_METHOD_PRIVATELOADCOMMON);
+ SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FlowMethodPrivateloadcommon);
}
///
/// Add a FlowAction to the save file writing sequence. A new FlowActionType is constructed every load.
- ///
+ ///
///
/// FlowActionType must have a public constructor that at most one of each of the following types:
///
@@ -232,10 +234,10 @@ public static void AddFlowActionToCampaignLoadAfter(FlowAction flowAction, strin
/// - LoadOrSaveCampaignTicket
///
///
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -261,15 +263,15 @@ public static void AddFlowActionToCampaignLoadAfter(FlowAction flowAction, strin
/// Thrown if FlowActionType does not have a valid Constructor
public static void AddFlowActionToCampaignSaveAfter(string referenceAction) where FlowActionType : FlowAction
{
- SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FLOW_METHOD_PRIVATESAVECOMMON);
+ SequentialFlowLoadersPatcher.AddConstructor(referenceAction, typeof(FlowActionType), SequentialFlowLoadersPatcher.FlowMethodPrivatesavecommon);
}
///
/// Add a FlowAction to the save file writing sequence. The same object is used for every load.
- ///
+ ///
/// The action will be run after the first FlowAction with a name equal to referenceAction.
/// If referenceAction is null, the action will be the first action run.
- ///
+ ///
///
/// The FlowActions that occur in this sequence by default are: (Updated for KSP 0.1.1.0)
///
@@ -294,6 +296,6 @@ public static void AddFlowActionToCampaignSaveAfter(string refer
/// The name of the action to insert a FlowActionType after. Use null to insert it at the start.
public static void AddFlowActionToCampaignSaveAfter(FlowAction flowAction, string referenceAction)
{
- SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FLOW_METHOD_PRIVATESAVECOMMON);
+ SequentialFlowLoadersPatcher.AddFlowAction(referenceAction, flowAction, SequentialFlowLoadersPatcher.FlowMethodPrivatesavecommon);
}
}
diff --git a/SpaceWarp/API/Logging/BaseLogger.cs b/SpaceWarp.Core/API/Logging/BaseLogger.cs
similarity index 88%
rename from SpaceWarp/API/Logging/BaseLogger.cs
rename to SpaceWarp.Core/API/Logging/BaseLogger.cs
index 1da7e7c1..7835d5b4 100644
--- a/SpaceWarp/API/Logging/BaseLogger.cs
+++ b/SpaceWarp.Core/API/Logging/BaseLogger.cs
@@ -1,5 +1,8 @@
-namespace SpaceWarp.API.Logging;
+using JetBrains.Annotations;
+namespace SpaceWarp.API.Logging;
+
+[PublicAPI]
public abstract class BaseLogger : ILogger
{
public abstract void Log(LogLevel level, object x);
diff --git a/SpaceWarp/API/Logging/BepInExLogger.cs b/SpaceWarp.Core/API/Logging/BepInExLogger.cs
similarity index 72%
rename from SpaceWarp/API/Logging/BepInExLogger.cs
rename to SpaceWarp.Core/API/Logging/BepInExLogger.cs
index 29e93966..662f73a3 100644
--- a/SpaceWarp/API/Logging/BepInExLogger.cs
+++ b/SpaceWarp.Core/API/Logging/BepInExLogger.cs
@@ -1,7 +1,9 @@
using BepInEx.Logging;
+using JetBrains.Annotations;
namespace SpaceWarp.API.Logging;
+[PublicAPI]
public class BepInExLogger : BaseLogger
{
private ManualLogSource _log;
@@ -15,4 +17,6 @@ public override void Log(LogLevel level, object x)
{
_log.Log((BepInEx.Logging.LogLevel)level, x);
}
+
+ public static implicit operator BepInExLogger(ManualLogSource log) => new(log);
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Logging/ILogger.cs b/SpaceWarp.Core/API/Logging/ILogger.cs
similarity index 82%
rename from SpaceWarp/API/Logging/ILogger.cs
rename to SpaceWarp.Core/API/Logging/ILogger.cs
index 41837980..a77e76e3 100644
--- a/SpaceWarp/API/Logging/ILogger.cs
+++ b/SpaceWarp.Core/API/Logging/ILogger.cs
@@ -1,5 +1,8 @@
-namespace SpaceWarp.API.Logging;
+using JetBrains.Annotations;
+namespace SpaceWarp.API.Logging;
+
+[PublicAPI]
public interface ILogger
{
public void Log(LogLevel level, object x);
@@ -12,5 +15,4 @@ public interface ILogger
public void LogInfo(object x);
public void LogDebug(object x);
public void LogAll(object x);
-
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Logging/LogLevel.cs b/SpaceWarp.Core/API/Logging/LogLevel.cs
similarity index 71%
rename from SpaceWarp/API/Logging/LogLevel.cs
rename to SpaceWarp.Core/API/Logging/LogLevel.cs
index 3b4462e5..2ef7977f 100644
--- a/SpaceWarp/API/Logging/LogLevel.cs
+++ b/SpaceWarp.Core/API/Logging/LogLevel.cs
@@ -1,6 +1,8 @@
-namespace SpaceWarp.API.Logging;
+using JetBrains.Annotations;
+namespace SpaceWarp.API.Logging;
+[PublicAPI]
public enum LogLevel
{
None = 0,
diff --git a/SpaceWarp/API/Logging/UnityLogSource.cs b/SpaceWarp.Core/API/Logging/UnityLogSource.cs
similarity index 92%
rename from SpaceWarp/API/Logging/UnityLogSource.cs
rename to SpaceWarp.Core/API/Logging/UnityLogSource.cs
index fc89eddc..654cb060 100644
--- a/SpaceWarp/API/Logging/UnityLogSource.cs
+++ b/SpaceWarp.Core/API/Logging/UnityLogSource.cs
@@ -1,7 +1,9 @@
-using UnityEngine;
+using JetBrains.Annotations;
+using UnityEngine;
namespace SpaceWarp.API.Logging;
+[PublicAPI]
public class UnityLogSource : BaseLogger
{
public string Name;
diff --git a/SpaceWarp/API/Lua/LuaMod.cs b/SpaceWarp.Core/API/Lua/LuaMod.cs
similarity index 87%
rename from SpaceWarp/API/Lua/LuaMod.cs
rename to SpaceWarp.Core/API/Lua/LuaMod.cs
index 583a46c8..14960196 100644
--- a/SpaceWarp/API/Lua/LuaMod.cs
+++ b/SpaceWarp.Core/API/Lua/LuaMod.cs
@@ -1,41 +1,38 @@
-
-using System;
+using System;
using BepInEx.Logging;
+using JetBrains.Annotations;
using KSP.Game;
using MoonSharp.Interpreter;
-using UnityEngine;
-using Logger = BepInEx.Logging.Logger;
namespace SpaceWarp.API.Lua;
-
-
[MoonSharpUserData]
+[PublicAPI]
public class LuaMod : KerbalMonoBehaviour
{
public Table ModTable;
// TODO: Add more than just this to the behaviour but for now
-
+
#region Message Definitions
// Start
- private Closure _start = null;
+ private Closure _start;
// Update Functions
- private Closure _update = null;
- private Closure _fixedUpdate = null;
- private Closure _lateUpdate = null;
-
+ private Closure _update;
+ private Closure _fixedUpdate;
+ private Closure _lateUpdate;
+
// Enable/Disable
- private Closure _onEnable = null;
- private Closure _onDisable = null;
-
+ private Closure _onEnable;
+ private Closure _onDisable;
+
// Destruction
- private Closure _onDestroy = null;
-
+ private Closure _onDestroy;
+
// Reset
- private Closure _reset = null;
+ private Closure _reset;
#endregion
-
+
public ManualLogSource Logger;
// First a pass through to the wrapped table
@@ -67,7 +64,7 @@ private void TryRegister(string name, out Closure method)
}
method = null;
}
-
+
public void Awake()
{
if (ModTable.Get("Awake") != null && ModTable.Get("Awake").Type == DataType.Function)
@@ -91,7 +88,7 @@ public void Awake()
TryRegister(nameof(OnDestroy), out _onDestroy);
TryRegister(nameof(Reset), out _reset);
-
+
}
// Start
@@ -128,7 +125,7 @@ public void LateUpdate()
TryCallMethod(_lateUpdate, this);
}
}
-
+
// Enable/Disable
public void OnEnable()
@@ -146,7 +143,7 @@ public void OnDisable()
TryCallMethod(_onDisable, this);
}
}
-
+
// Destruction
public void OnDestroy()
diff --git a/SpaceWarp/API/Lua/SpaceWarpInterop.cs b/SpaceWarp.Core/API/Lua/SpaceWarpInterop.cs
similarity index 65%
rename from SpaceWarp/API/Lua/SpaceWarpInterop.cs
rename to SpaceWarp.Core/API/Lua/SpaceWarpInterop.cs
index 03856d33..96b762aa 100644
--- a/SpaceWarp/API/Lua/SpaceWarpInterop.cs
+++ b/SpaceWarp.Core/API/Lua/SpaceWarpInterop.cs
@@ -1,26 +1,19 @@
-using System;
-using BepInEx.Bootstrap;
+using BepInEx.Bootstrap;
using JetBrains.Annotations;
-using KSP.UI.Flight;
using MoonSharp.Interpreter;
-using SpaceWarp.API.Assets;
-using SpaceWarp.API.UI.Appbar;
-using SpaceWarp.Backend.UI.Appbar;
using SpaceWarp.InternalUtilities;
using UnityEngine;
-using UnityEngine.UIElements;
using Logger = BepInEx.Logging.Logger;
-// ReSharper disable UnusedMember.Global
namespace SpaceWarp.API.Lua;
[SpaceWarpLuaAPI("SpaceWarp")]
-// ReSharper disable once UnusedType.Global
+[PublicAPI]
public static class SpaceWarpInterop
{
public static LuaMod RegisterMod(string name, Table modTable)
{
- GameObject go = new GameObject(name);
+ var go = new GameObject(name);
go.Persist();
go.transform.SetParent(Chainloader.ManagerObject.transform);
go.SetActive(false);
@@ -30,6 +23,4 @@ public static LuaMod RegisterMod(string name, Table modTable)
go.SetActive(true);
return mod;
}
-
-
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Lua/SpaceWarpLuaAPIAttribute.cs b/SpaceWarp.Core/API/Lua/SpaceWarpLuaAPIAttribute.cs
similarity index 84%
rename from SpaceWarp/API/Lua/SpaceWarpLuaAPIAttribute.cs
rename to SpaceWarp.Core/API/Lua/SpaceWarpLuaAPIAttribute.cs
index 3d3f27b9..91fc288f 100644
--- a/SpaceWarp/API/Lua/SpaceWarpLuaAPIAttribute.cs
+++ b/SpaceWarp.Core/API/Lua/SpaceWarpLuaAPIAttribute.cs
@@ -1,8 +1,10 @@
using System;
+using JetBrains.Annotations;
namespace SpaceWarp.API.Lua;
[AttributeUsage(AttributeTargets.Class)]
+[MeansImplicitUse]
public class SpaceWarpLuaAPIAttribute : Attribute
{
public string LuaName;
diff --git a/SpaceWarp.Core/API/Lua/UI/LuaUITK.cs b/SpaceWarp.Core/API/Lua/UI/LuaUITK.cs
new file mode 100644
index 00000000..34ae01f6
--- /dev/null
+++ b/SpaceWarp.Core/API/Lua/UI/LuaUITK.cs
@@ -0,0 +1,470 @@
+using JetBrains.Annotations;
+using MoonSharp.Interpreter;
+using SpaceWarp.API.Assets;
+using UnityEngine.UIElements;
+
+namespace SpaceWarp.API.Lua.UI;
+
+[SpaceWarpLuaAPI("UI")]
+[PublicAPI]
+public static class LuaUITK
+{
+ #region Creation
+
+ public static UIDocument Window(LuaMod mod, string id, string documentPath)
+ {
+ return Window(mod, id, AssetManager.GetAsset(documentPath));
+ }
+
+ public static UIDocument Window(LuaMod mod, string id, VisualTreeAsset uxml)
+ {
+ var parent = mod.transform;
+ return UitkForKsp2.API.Window.CreateFromUxml(uxml, id, parent, true);
+ }
+
+ public static UIDocument Window(LuaMod mod, string id)
+ {
+ var parent = mod.transform;
+ return UitkForKsp2.API.Window.Create(out _, id, parent, true);
+ }
+
+ #region Element Creation
+
+ public static VisualElement VisualElement()
+ {
+ return new VisualElement();
+ }
+
+ public static ScrollView ScrollView()
+ {
+ return new ScrollView();
+ }
+
+ public static ListView ListView()
+ {
+ return new ListView();
+ }
+
+ public static Toggle Toggle(string text = "")
+ {
+ return new Toggle
+ {
+ text = text
+ };
+ }
+
+ public static Label Label(string text = "")
+ {
+ return new Label
+ {
+ text = text
+ };
+ }
+
+ public static Button Button(string text = "")
+ {
+ return new Button
+ {
+ text = text
+ };
+ }
+
+ public static Scroller Scroller()
+ {
+ return new Scroller();
+ }
+
+ public static TextField TextField(string text = "")
+ {
+ return new TextField
+ {
+ text = text
+ };
+ }
+
+ public static Foldout Foldout()
+ {
+ return new Foldout();
+ }
+
+ public static Slider Slider(float value = 0.0f, float minValue = 0.0f, float maxValue = 1.0f)
+ {
+ return new Slider
+ {
+ lowValue = minValue,
+ highValue = maxValue,
+ value = value
+ };
+ }
+
+ public static SliderInt SliderInt(int value = 0, int minValue = 0, int maxValue = 100)
+ {
+ return new SliderInt
+ {
+ lowValue = minValue,
+ highValue = maxValue,
+ value = value
+ };
+ }
+
+ public static MinMaxSlider MinMaxSlider(float minValue = 0.0f, float maxValue = 1.0f, float minLimit = 0.0f,
+ float maxLimit = 1.0f)
+ {
+ return new MinMaxSlider
+ {
+ minValue = minValue,
+ maxValue = maxValue,
+ lowLimit = minLimit,
+ highLimit = maxLimit
+ };
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Callbacks
+
+ public static void AddCallback(Button button, Closure callback, [CanBeNull] DynValue self = null)
+ {
+ if (self != null)
+ {
+ button.clicked += () => callback.Call(self);
+ }
+ else
+ {
+ button.clicked += () => callback.Call();
+ }
+ }
+
+ ///
+ /// Registers a value changed callback from lua
+ /// The lua functions parameters should be like function(self?,previous,new)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void RegisterValueChangedCallback(
+ INotifyValueChanged element,
+ Closure callback,
+ [CanBeNull] DynValue self = null
+ )
+ {
+ if (self != null)
+ {
+ element.RegisterValueChangedCallback(evt => callback.Call(self, evt.previousValue, evt.newValue));
+ }
+ else
+ {
+ element.RegisterValueChangedCallback(evt => callback.Call(evt.previousValue, evt.newValue));
+ }
+ }
+
+ private static void RegisterGenericCallback(
+ VisualElement element,
+ Closure callback,
+ DynValue self,
+ bool trickleDown = false
+ ) where T : EventBase, new()
+ {
+ if (self != null)
+ {
+ element.RegisterCallback(evt => callback.Call(self, evt),
+ trickleDown ? TrickleDown.TrickleDown : TrickleDown.NoTrickleDown);
+ }
+ else
+ {
+ element.RegisterCallback(evt => callback.Call(evt),
+ trickleDown ? TrickleDown.TrickleDown : TrickleDown.NoTrickleDown);
+ }
+ }
+
+
+ #region Capture Events
+
+ public static void RegisterMouseCaptureCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseCaptureOutCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerCaptureCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerCaptureOutCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Change Events
+
+ public static void RegisterChangeBoolCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback>(element, callback, self, trickleDown);
+
+ public static void RegisterChangeIntCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback>(element, callback, self, trickleDown);
+
+ public static void RegisterChangeFloatCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback>(element, callback, self, trickleDown);
+
+ public static void RegisterChangeStringCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback>(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Click Events
+
+ public static void RegisterClickCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Focus Events
+
+ public static void RegisterFocusOutCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterFocusInCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterBlurCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterFocusCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Input Events
+
+ public static void RegisterInputCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Layout Events
+
+ public static void RegisterGeometryChangedCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Mouse Events
+
+ public static void RegisterMouseDownCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseUpCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseMoveCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterWheelCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseEnterWindowCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseLeaveWindowCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseEnterCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseLeaveCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseOverCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterMouseOutCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Pointer Events
+
+ public static void RegisterPointerDownCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerUpCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerMoveCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerEnterCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerLeaveCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerOverCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterPointerOutCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Panel Events
+
+ public static void RegisterAttachToPanelCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ public static void RegisterDetachFromPanelCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #region Tooltip Events
+
+ public static void RegisterTooltipCallback(
+ VisualElement element,
+ Closure callback,
+ [CanBeNull] DynValue self = null,
+ bool trickleDown = false
+ ) => RegisterGenericCallback(element, callback, self, trickleDown);
+
+ #endregion
+
+ #endregion
+}
\ No newline at end of file
diff --git a/SpaceWarp/API/Mods/BaseKspLoaderSpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs
similarity index 57%
rename from SpaceWarp/API/Mods/BaseKspLoaderSpaceWarpMod.cs
rename to SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs
index f62fdeef..0127632e 100644
--- a/SpaceWarp/API/Mods/BaseKspLoaderSpaceWarpMod.cs
+++ b/SpaceWarp.Core/API/Mods/BaseKspLoaderSpaceWarpMod.cs
@@ -1,12 +1,16 @@
-using KSP.Modding;
+using JetBrains.Annotations;
+using KSP.Modding;
+using SpaceWarp.API.Configuration;
using SpaceWarp.API.Logging;
namespace SpaceWarp.API.Mods;
+[PublicAPI]
public abstract class BaseKspLoaderSpaceWarpMod : Mod, ISpaceWarpMod
{
public virtual void OnPreInitialized()
{
+
}
public virtual void OnInitialized()
@@ -16,9 +20,16 @@ public virtual void OnInitialized()
public virtual void OnPostInitialized()
{
}
-
+
///
/// Gets set automatically, before awake is called
///
- public ILogger SWLogger { get; internal set; }
+ public ILogger SWLogger { get; set; }
+
+ public IConfigFile SWConfiguration {
+ get;
+ internal set;
+ }
+
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Mods/BaseSpaceWarpPlugin.cs b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs
similarity index 86%
rename from SpaceWarp/API/Mods/BaseSpaceWarpPlugin.cs
rename to SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs
index 69027956..2f7551f8 100644
--- a/SpaceWarp/API/Mods/BaseSpaceWarpPlugin.cs
+++ b/SpaceWarp.Core/API/Mods/BaseSpaceWarpPlugin.cs
@@ -1,17 +1,19 @@
using BepInEx;
using BepInEx.Logging;
+using JetBrains.Annotations;
using KSP.Game;
using KSP.Messages;
using KSP.VFX;
using SpaceWarp.API.Mods.JSON;
-using JetBrains.Annotations;
+using SpaceWarp.API.Configuration;
using SpaceWarp.API.Logging;
namespace SpaceWarp.API.Mods;
///
-/// Represents a KSP2 Mod, you should inherit from this and do your manager processing.
+/// Represents a KSP2 Mod, you should inherit from this and do your manager processing.
///
+[PublicAPI]
public abstract class BaseSpaceWarpPlugin : BaseUnityPlugin, ISpaceWarpMod
{
#region KspBehaviour things
@@ -60,6 +62,10 @@ public virtual void OnPostInitialized()
}
private BepInExLogger _logger;
public ILogger SWLogger => _logger ??= new BepInExLogger(Logger);
+ private BepInExConfigFile _configFile;
+ public IConfigFile SWConfiguration => _configFile ??= new BepInExConfigFile(Config);
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
+
internal static string GetGuidBySpec(PluginInfo pluginInfo, ModInfo modInfo)
{
return modInfo.Spec >= SpecVersion.V1_2
diff --git a/SpaceWarp/API/Mods/GlobalModDefines.cs b/SpaceWarp.Core/API/Mods/GlobalModDefines.cs
similarity index 86%
rename from SpaceWarp/API/Mods/GlobalModDefines.cs
rename to SpaceWarp.Core/API/Mods/GlobalModDefines.cs
index d93043e8..4c36ad8e 100644
--- a/SpaceWarp/API/Mods/GlobalModDefines.cs
+++ b/SpaceWarp.Core/API/Mods/GlobalModDefines.cs
@@ -1,7 +1,9 @@
using System.IO;
+using JetBrains.Annotations;
namespace SpaceWarp.API.Mods;
+[PublicAPI]
public static class GlobalModDefines
{
public static readonly string AssetBundlesFolder = Path.Combine("assets", "bundles");
diff --git a/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs
new file mode 100644
index 00000000..c2f9d446
--- /dev/null
+++ b/SpaceWarp.Core/API/Mods/ISpaceWarpMod.cs
@@ -0,0 +1,21 @@
+using JetBrains.Annotations;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+
+namespace SpaceWarp.API.Mods;
+
+[PublicAPI]
+public interface ISpaceWarpMod
+{
+ public void OnPreInitialized();
+
+ public void OnInitialized();
+
+ public void OnPostInitialized();
+
+ public ILogger SWLogger { get; }
+
+ public IConfigFile SWConfiguration { get; }
+
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
+}
\ No newline at end of file
diff --git a/SpaceWarp/API/Mods/JSON/Converters/SpecVersionConverter.cs b/SpaceWarp.Core/API/Mods/JSON/Converters/SpecVersionConverter.cs
similarity index 100%
rename from SpaceWarp/API/Mods/JSON/Converters/SpecVersionConverter.cs
rename to SpaceWarp.Core/API/Mods/JSON/Converters/SpecVersionConverter.cs
diff --git a/SpaceWarp/API/Mods/JSON/DependencyInfo.cs b/SpaceWarp.Core/API/Mods/JSON/DependencyInfo.cs
similarity index 59%
rename from SpaceWarp/API/Mods/JSON/DependencyInfo.cs
rename to SpaceWarp.Core/API/Mods/JSON/DependencyInfo.cs
index f40cbad9..f862fcd1 100644
--- a/SpaceWarp/API/Mods/JSON/DependencyInfo.cs
+++ b/SpaceWarp.Core/API/Mods/JSON/DependencyInfo.cs
@@ -1,12 +1,14 @@
-using Newtonsoft.Json;
+using JetBrains.Annotations;
+using Newtonsoft.Json;
namespace SpaceWarp.API.Mods.JSON;
///
-/// Represents the json property info. Properties have to use the same name as in the JSON file, that's why they break
-/// convention.
+/// Represents the json property info. Properties have to use the same name as in the JSON file, that's why they break
+/// convention.
///
[JsonObject(MemberSerialization.OptIn)]
+[PublicAPI]
public sealed class DependencyInfo
{
[JsonProperty("id")] public string ID { get; internal set; }
diff --git a/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs b/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs
new file mode 100644
index 00000000..9a971929
--- /dev/null
+++ b/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+
+namespace SpaceWarp.API.Mods.JSON;
+
+///
+/// Representation of the mod info JSON file.
+///
+[JsonObject(MemberSerialization.OptIn)]
+[PublicAPI]
+public sealed class ModInfo
+{
+ [JsonProperty("spec", Required = Required.DisallowNull)] public SpecVersion Spec { get; internal set; } = new();
+
+ [JsonProperty("mod_id", Required = Required.DisallowNull)] private string _modID = "";
+
+ public string ModID
+ {
+ get
+ {
+ if (Spec == SpecVersion.V1_2)
+ {
+ throw new DeprecatedSwinfoPropertyException(nameof(ModID), SpecVersion.V1_2);
+ }
+ return _modID;
+ }
+ internal set => _modID = value;
+ }
+
+
+ [JsonProperty("name")]
+ private string _name;
+ public string Name
+ {
+ get => _name ?? _modID;
+ internal set => _name = value;
+ }
+
+ [JsonProperty("author", Required = Required.DisallowNull)]
+ public string Author { get; internal set; } = "";
+
+ [JsonProperty("description", Required = Required.DisallowNull)] public string Description { get; internal set; } = "";
+
+ [JsonProperty("source", Required = Required.DisallowNull)] public string Source { get; internal set; } = "";
+
+ [JsonProperty("version", Required = Required.Always)] public string Version { get; internal set; }
+
+ [JsonProperty("dependencies", Required = Required.DisallowNull)] public List Dependencies { get; internal set; } = new();
+
+ [JsonProperty("ksp2_version", Required = Required.DisallowNull)]
+ public SupportedVersionsInfo SupportedKsp2Versions { get; internal set; } = new()
+ {
+ Min = "*",
+ Max = "*"
+ };
+
+ [JsonProperty("version_check")]
+ [CanBeNull]
+ public string VersionCheck { get; internal set; }
+
+ [JsonProperty("version_check_type")]
+ [Obsolete("Only swinfo.json version checking will be allowed in 2.0.0.")]
+ public VersionCheckType VersionCheckType { get; internal set; } = VersionCheckType.SwInfo;
+
+ [JsonProperty("conflicts", Required = Required.DisallowNull)]
+ public List Conflicts { get; internal set; } = new();
+}
\ No newline at end of file
diff --git a/SpaceWarp/API/Mods/JSON/SpecVersion.cs b/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs
similarity index 72%
rename from SpaceWarp/API/Mods/JSON/SpecVersion.cs
rename to SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs
index 3d64594c..6d527c15 100644
--- a/SpaceWarp/API/Mods/JSON/SpecVersion.cs
+++ b/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs
@@ -1,4 +1,5 @@
using System;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using SpaceWarp.API.Mods.JSON.Converters;
@@ -8,6 +9,7 @@ namespace SpaceWarp.API.Mods.JSON;
/// Represents the version of the API specification from the swinfo.json file.
///
[JsonConverter(typeof(SpecVersionConverter))]
+[PublicAPI]
public sealed record SpecVersion
{
private const int DefaultMajor = 1;
@@ -19,22 +21,29 @@ public sealed record SpecVersion
// ReSharper disable InconsistentNaming
///
- /// Specification version 1.0 (SpaceWarp < 1.2), used if "spec" is not specified in the swinfo.json file.
+ /// Specification version 1.0 (SpaceWarp < 1.2) - used if "spec" is not specified in the swinfo.json file.
///
public static SpecVersion Default { get; } = new();
///
- /// Specification version 1.2 (SpaceWarp 1.2.x), replaces SpaceWarp's proprietary ModID with BepInEx plugin GUID.
+ /// Specification version 1.2 (SpaceWarp 1.2.x) - replaces SpaceWarp's proprietary ModID with BepInEx plugin GUID.
///
public static SpecVersion V1_2 { get; } = new(1, 2);
///
- /// Specification version 1.3 (SpaceWarp 1.3.x), adds back the ModID field, but enforces that it is the same as the BepInEx plugin GUID if there is a BepInEx plugin attached to the swinfo
- /// Which w/ version 1.3 there does not have to be a plugin
- /// Also enforces that all dependencies use BepInEx GUID, and that they are loaded
+ /// Specification version 1.3 (SpaceWarp 1.3.x) - adds back the ModID field, but enforces that it is the same
+ /// as the BepInEx plugin GUID if there is a BepInEx plugin attached to the swinfo, since w/ version 1.3 there
+ /// does not have to be a plugin.
+ /// Also enforces that all dependencies use BepInEx GUID, and that they are loaded.
///
public static SpecVersion V1_3 { get; } = new(1, 3);
+ ///
+ /// Specification version 2.0 (SpaceWarp 1.5.x and 2.0.x) - removes support for version checking from .csproj files,
+ ///
+ ///
+ public static SpecVersion V2_0 { get; } = new(2, 0);
+
// ReSharper restore InconsistentNaming
///
@@ -91,10 +100,11 @@ private static int Compare(SpecVersion a, SpecVersion b)
///
/// Thrown if the specification version string is invalid.
///
+[PublicAPI]
public sealed class InvalidSpecVersionException : Exception
{
- public InvalidSpecVersionException(string version) : base(
- $"Invalid spec version: {version}. The correct format is \"major.minor\".")
+ public InvalidSpecVersionException(string version) :
+ base($"Invalid spec version: {version}. The correct format is \"major.minor\".")
{
}
}
@@ -102,11 +112,11 @@ public InvalidSpecVersionException(string version) : base(
///
/// Thrown if a property is deprecated in the current specification version.
///
+[PublicAPI]
public sealed class DeprecatedSwinfoPropertyException : Exception
{
- public DeprecatedSwinfoPropertyException(string property, SpecVersion deprecationVersion) : base(
- $"The swinfo.json property \"{property}\" is deprecated in the spec version {deprecationVersion} and will be removed completely in the future."
- )
+ public DeprecatedSwinfoPropertyException(string property, SpecVersion deprecationVersion) :
+ base($"The swinfo.json property \"{property}\" is deprecated in the spec version {deprecationVersion} and will be removed completely in the future.")
{
}
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Mods/JSON/SupportedVersionsInfo.cs b/SpaceWarp.Core/API/Mods/JSON/SupportedVersionsInfo.cs
similarity index 70%
rename from SpaceWarp/API/Mods/JSON/SupportedVersionsInfo.cs
rename to SpaceWarp.Core/API/Mods/JSON/SupportedVersionsInfo.cs
index 4b700e15..fb4e3118 100644
--- a/SpaceWarp/API/Mods/JSON/SupportedVersionsInfo.cs
+++ b/SpaceWarp.Core/API/Mods/JSON/SupportedVersionsInfo.cs
@@ -1,16 +1,18 @@
-using Newtonsoft.Json;
+using JetBrains.Annotations;
+using Newtonsoft.Json;
using SpaceWarp.API.Versions;
namespace SpaceWarp.API.Mods.JSON;
///
-/// Representation of the supported version info of a mod from a JSON file.
+/// Representation of the supported version info of a mod from a JSON file.
///
[JsonObject(MemberSerialization.OptIn)]
+[PublicAPI]
public sealed class SupportedVersionsInfo
{
- internal const string DefaultMin = "0.0.0";
- internal const string DefaultMax = "*";
+ public const string DefaultMin = "0.0.0";
+ public const string DefaultMax = "*";
[JsonProperty("min")] public string Min { get; internal set; } = DefaultMin;
diff --git a/SpaceWarp/API/Mods/JSON/VersionCheckType.cs b/SpaceWarp.Core/API/Mods/JSON/VersionCheckType.cs
similarity index 61%
rename from SpaceWarp/API/Mods/JSON/VersionCheckType.cs
rename to SpaceWarp.Core/API/Mods/JSON/VersionCheckType.cs
index 46d2c003..bfa1b6db 100644
--- a/SpaceWarp/API/Mods/JSON/VersionCheckType.cs
+++ b/SpaceWarp.Core/API/Mods/JSON/VersionCheckType.cs
@@ -1,10 +1,14 @@
-using System.Runtime.Serialization;
+using System;
+using System.Runtime.Serialization;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace SpaceWarp.API.Mods.JSON;
[JsonConverter(typeof(StringEnumConverter))]
+[Obsolete("Only swinfo.json version checking will be allowed in 2.0.0.")]
+[PublicAPI]
public enum VersionCheckType
{
[EnumMember(Value = "swinfo")]
diff --git a/SpaceWarp.Core/API/Mods/PluginList.cs b/SpaceWarp.Core/API/Mods/PluginList.cs
new file mode 100644
index 00000000..97ea2079
--- /dev/null
+++ b/SpaceWarp.Core/API/Mods/PluginList.cs
@@ -0,0 +1,351 @@
+using System;
+using System.Collections.Generic;
+using BepInEx;
+using BepInEx.Bootstrap;
+using JetBrains.Annotations;
+using SpaceWarp.API.Mods.JSON;
+using SpaceWarp.API.Versions;
+using SpaceWarpPatcher;
+using UnityEngine.InputSystem;
+using UnityEngine.InputSystem.Switch.LowLevel;
+
+// Disable obsolete warning for Chainloader.Plugins
+#pragma warning disable CS0618
+
+namespace SpaceWarp.API.Mods;
+
+///
+/// API for accessing information about currently loaded and disabled plugins.
+///
+[PublicAPI]
+public static class PluginList
+{
+ #region Reading Plugins
+
+ ///
+ /// Set if the plugin list is different in any way since last run (version differences, new mods, mods removed,
+ /// mods disabled, description differences, any different in any swinfo file and the disabled mod list).
+ ///
+ public static bool ModListChangedSinceLastRun => ChainloaderPatch.ModListChangedSinceLastRun;
+
+ ///
+ /// Contains information about all currently loaded plugins. The key is the BepInEx GUID of the plugin.
+ ///
+ public static Dictionary LoadedPluginInfos { get; } = Chainloader.PluginInfos;
+
+ ///
+ /// Contains information about all currently disabled plugins. The key is the BepInEx GUID of the plugin.
+ ///
+ public static Dictionary DisabledPluginInfos { get; } = ChainloaderPatch
+ .DisabledPluginGuids.Zip(ChainloaderPatch.DisabledPlugins, (guid, info) => new { guid, info })
+ .ToDictionary(item => item.guid, item => item.info);
+
+ ///
+ /// Returns whether the plugin with the specified GUID is currently loaded.
+ ///
+ /// GUID of the plugin
+ /// Returns true if the plugin is loaded, false otherwise
+ public static bool IsLoaded(string guid)
+ {
+ return LoadedPluginInfos.ContainsKey(guid);
+ }
+
+ ///
+ /// Compares the version of the specified plugin with the given version.
+ ///
+ /// GUID of the plugin
+ /// Version to compare the plugin's version to
+ /// Returns -1 if the plugin is older than the given version, 0 if it's the same version and 1 if it's newer
+ /// Thrown if the plugin with the specified GUID is not loaded
+ public static int CompareVersion(string guid, Version version)
+ {
+ if (!IsLoaded(guid))
+ {
+ throw new ArgumentException($"Plugin with GUID {guid} is not loaded");
+ }
+
+ return LoadedPluginInfos[guid].Metadata.Version.CompareTo(version);
+ }
+
+ ///
+ /// Retrieves the of the specified plugin. Returns null if the specified plugin guid doesn't
+ /// have an associated .
+ ///
+ /// GUID of the plugin
+ /// of the plugin or null if not found
+ public static ModInfo TryGetSwinfo(string guid)
+ {
+ var swModInfo = AllEnabledAndActivePlugins
+ .FirstOrDefault(item => item.Guid == guid);
+
+ if (swModInfo != null)
+ {
+ return swModInfo.SWInfo;
+ }
+
+ var disabledModInfo = AllDisabledPlugins
+ .Where(item => item.Guid == guid)
+ .Select(item => item.SWInfo)
+ .FirstOrDefault();
+
+ return disabledModInfo;
+ }
+
+ ///
+ /// Retrieves the of the specified plugin. Returns null if the specified plugin guid doesn't
+ /// have an associated .
+ ///
+ /// GUID of the plugin
+ /// of the plugin or null if not found
+ public static SpaceWarpPluginDescriptor TryGetDescriptor(string guid)
+ {
+ return AllEnabledAndActivePlugins
+ .FirstOrDefault(item => item.Guid == guid);
+ }
+
+ ///
+ /// Retrieves the instance of the specified plugin class.
+ ///
+ /// The type of the plugin class
+ /// Plugin instance or null if not found
+ public static T TryGetPlugin() where T : BaseUnityPlugin => Chainloader.Plugins.OfType().FirstOrDefault();
+
+ ///
+ /// Retrieves the instance of a plugin class with the given BepInEx GUID.
+ ///
+ /// BepInEx GUID of the plugin
+ /// The type of the plugin class
+ /// Plugin instance or null if not found
+ public static T TryGetPlugin(string guid) where T : BaseUnityPlugin =>
+ Chainloader.Plugins.Find(plugin => plugin.Info.Metadata.GUID == guid) as T;
+
+ #endregion
+
+ #region Registering Plugins
+
+ private static List _allEnabledAndActivePlugins = new();
+
+ ///
+ /// All plugins that are enabled, and active (not errored)
+ ///
+ public static IReadOnlyList AllEnabledAndActivePlugins => _allEnabledAndActivePlugins;
+
+ private static List _allDisabledPlugins = new();
+
+ ///
+ /// All disabled plugins
+ ///
+ public static IReadOnlyList AllDisabledPlugins => _allDisabledPlugins;
+
+ private static List _allErroredPlugins = new();
+ public static IReadOnlyList AllErroredPlugins => _allErroredPlugins;
+
+ public static IEnumerable AllPlugins => _allEnabledAndActivePlugins
+ .Concat(_allDisabledPlugins).Concat(_allErroredPlugins.Select(x => x.Plugin));
+
+ public static void RegisterPlugin(SpaceWarpPluginDescriptor plugin)
+ {
+ if (AllPlugins.Any(x => x.Guid == plugin.Guid))
+ {
+ SpaceWarpPlugin.Logger.LogError($"Attempting to register a mod with a duplicate GUID: {plugin.Guid}");
+ }
+
+ SpaceWarpPlugin.Logger.LogInfo($"Registered plugin: {plugin.Guid}");
+ _allEnabledAndActivePlugins.Add(plugin);
+ }
+
+ public static void Disable(string guid)
+ {
+ var descriptor = _allEnabledAndActivePlugins.FirstOrDefault(x =>
+ string.Equals(x.Guid, guid, StringComparison.InvariantCultureIgnoreCase));
+ if (descriptor != null)
+ {
+ _allEnabledAndActivePlugins.Remove(descriptor);
+ _allDisabledPlugins.Add(descriptor);
+ }
+ }
+
+ public static SpaceWarpErrorDescription GetErrorDescriptor(SpaceWarpPluginDescriptor plugin)
+ {
+ if (_allErroredPlugins.Any(x => x.Plugin == plugin))
+ {
+ return _allErroredPlugins.First(x => x.Plugin == plugin);
+ }
+
+ if (_allEnabledAndActivePlugins.Any(x => x == plugin))
+ {
+ _allEnabledAndActivePlugins.Remove(plugin);
+ }
+
+ var newError = new SpaceWarpErrorDescription(plugin);
+ _allErroredPlugins.Add(newError);
+ return newError;
+ }
+
+ public static void NoteMissingSwinfoError(SpaceWarpPluginDescriptor plugin)
+ {
+ var errorDescriptor = GetErrorDescriptor(plugin);
+ errorDescriptor.MissingSwinfo = true;
+ }
+
+ public static void NoteBadDirectoryError(SpaceWarpPluginDescriptor plugin)
+ {
+ var errorDescriptor = GetErrorDescriptor(plugin);
+ errorDescriptor.BadDirectory = true;
+ }
+
+ public static void NoteBadIDError(SpaceWarpPluginDescriptor plugin)
+ {
+ var errorDescriptor = GetErrorDescriptor(plugin);
+ errorDescriptor.BadID = true;
+ }
+
+ public static void NoteMismatchedVersionError(SpaceWarpPluginDescriptor plugin)
+ {
+ var errorDescriptor = GetErrorDescriptor(plugin);
+ errorDescriptor.MismatchedVersion = true;
+ }
+
+ public static void NoteUnspecifiedDependencyError(SpaceWarpPluginDescriptor plugin, string dependency)
+ {
+ var errorDescriptor = GetErrorDescriptor(plugin);
+ errorDescriptor.UnspecifiedDependencies.Add(dependency);
+ }
+
+ private static SemanticVersion PadVersion(string version)
+ {
+ var length = version.Split('.').Length;
+ for (var i = 0; i < 3-length; i++)
+ {
+ version += ".0";
+ }
+
+ return new SemanticVersion(version);
+ }
+
+ private static bool IsSupportedSemver(string version, string min, string max)
+ {
+ var basicVersion = PadVersion(version);
+ var minVersion = PadVersion(min.Replace("*", "0"));
+ var maxVersion = PadVersion(max.Replace("*", $"{int.MaxValue}"));
+ return basicVersion >= minVersion && basicVersion <= maxVersion;
+ }
+
+ private static bool DependencyResolved(
+ SpaceWarpPluginDescriptor descriptor,
+ List resolvedPlugins
+ )
+ {
+ if (descriptor.SWInfo.Spec < SpecVersion.V1_3) return true;
+ return !(from dependency in descriptor.SWInfo.Dependencies
+ let info = resolvedPlugins.FirstOrDefault(x => string.Equals(
+ x.Guid,
+ dependency.ID,
+ StringComparison.InvariantCultureIgnoreCase)
+ )
+ where info == null || !IsSupportedSemver(
+ info.SWInfo.Version,
+ dependency.Version.Min,
+ dependency.Version.Max
+ )
+ select dependency).Any();
+ }
+
+ private static void GetLoadOrder()
+ {
+ var changed = true;
+ List newOrder = new();
+ while (changed)
+ {
+ changed = false;
+ for (var i = _allEnabledAndActivePlugins.Count - 1; i >= 0; i--)
+ {
+ if (!DependencyResolved(_allEnabledAndActivePlugins[i], newOrder)) continue;
+ newOrder.Add(_allEnabledAndActivePlugins[i]);
+ _allEnabledAndActivePlugins.RemoveAt(i);
+ changed = true;
+ }
+ }
+
+ for (var i = _allEnabledAndActivePlugins.Count - 1; i >= 0; i--)
+ {
+ var info = _allEnabledAndActivePlugins[i];
+ SpaceWarpPlugin.Logger.LogError($"Missing dependency for mod: {info.Name}, this mod will not be loaded");
+ var error = GetErrorDescriptor(info);
+ error.MissingDependencies = info.SWInfo.Dependencies.Select(x => x.ID).Where(x =>
+ !newOrder.Any(y => string.Equals(x, y.Guid, StringComparison.InvariantCultureIgnoreCase))).ToList();
+ }
+
+ _allEnabledAndActivePlugins = newOrder;
+ }
+
+ private static void GetDependencyErrors()
+ {
+ foreach (var erroredPlugin in _allErroredPlugins.Where(erroredPlugin =>
+ erroredPlugin.MissingDependencies.Count != 0))
+ {
+ for (var i = erroredPlugin.MissingDependencies.Count - 1; i >= 0; i--)
+ {
+ var dep = erroredPlugin.MissingDependencies[i];
+ if (AllEnabledAndActivePlugins.Any(x =>
+ string.Equals(x.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ erroredPlugin.UnsupportedDependencies.Add(dep);
+ erroredPlugin.MissingDependencies.RemoveAt(i);
+ }
+ else if (AllErroredPlugins.Any(x =>
+ string.Equals(x.Plugin.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ erroredPlugin.ErroredDependencies.Add(dep);
+ erroredPlugin.MissingDependencies.RemoveAt(i);
+ }
+ else if (AllDisabledPlugins.Any(x =>
+ string.Equals(x.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ erroredPlugin.DisabledDependencies.Add(dep);
+ erroredPlugin.MissingDependencies.RemoveAt(i);
+ }
+ }
+ }
+ }
+
+ private static void CheckCompatibility()
+ {
+ var incompatibilities = _allEnabledAndActivePlugins.Select(x => (Key: x.Guid, Value: new HashSet()))
+ .ToDictionary(x => x.Key, x => x.Value);
+ var versionLookup = _allEnabledAndActivePlugins.Select(x => (Key: x.Guid, Value: x.SWInfo.Version))
+ .ToDictionary(x => x.Key, x => x.Value);
+ var pluginDictionary = _allEnabledAndActivePlugins.ToDictionary(x => x.Guid, x => x);
+ foreach (var mod in _allEnabledAndActivePlugins)
+ {
+ var swinfo = mod.SWInfo;
+ if (swinfo.Spec < SpecVersion.V2_0) continue;
+ foreach (var conflict in swinfo.Conflicts)
+ {
+ if (!versionLookup.TryGetValue(conflict.ID, out var conflictingVersion) ||
+ !IsSupportedSemver(conflictingVersion, conflict.Version.Min, conflict.Version.Max)) continue;
+ incompatibilities[mod.Guid].Add(conflict.ID);
+ incompatibilities[conflict.ID].Add(mod.Guid);
+ }
+ }
+
+ foreach (var incompatibility in incompatibilities)
+ {
+ if (incompatibility.Value.Count <= 0) continue;
+ var descriptor = GetErrorDescriptor(pluginDictionary[incompatibility.Key]);
+ descriptor.Incompatibilities.AddRange(incompatibility.Value);
+ }
+ }
+
+ ///
+ /// This is done after Awake/LoadModule(), so that everything else can use it
+ ///
+ internal static void ResolveDependenciesAndLoadOrder()
+ {
+ GetLoadOrder();
+ GetDependencyErrors();
+ CheckCompatibility();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Mods/SpaceWarpErrorDescription.cs b/SpaceWarp.Core/API/Mods/SpaceWarpErrorDescription.cs
new file mode 100644
index 00000000..9753a4e3
--- /dev/null
+++ b/SpaceWarp.Core/API/Mods/SpaceWarpErrorDescription.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using BepInEx;
+using JetBrains.Annotations;
+using SpaceWarp.Patching;
+using UnityEngine;
+
+namespace SpaceWarp.API.Mods;
+
+[PublicAPI]
+public class SpaceWarpErrorDescription
+{
+ public SpaceWarpPluginDescriptor Plugin;
+ public bool MissingSwinfo;
+ public bool BadDirectory;
+ public bool BadID;
+ public bool MismatchedVersion;
+
+ public List DisabledDependencies = new();
+ public List ErroredDependencies = new();
+ public List MissingDependencies = new();
+ public List UnsupportedDependencies = new();
+ public List UnspecifiedDependencies = new();
+ public List Incompatibilities = new();
+
+ public SpaceWarpErrorDescription(SpaceWarpPluginDescriptor plugin)
+ {
+ Plugin = plugin;
+ // Essentially if we have an errored plugin, we delete the plugin code
+ if (plugin.Plugin != null)
+ {
+ switch (plugin.Plugin)
+ {
+ // If and only if this is space warp we don't destroy it
+ // Then we inform our loading patcher to do space warp specially
+ case SpaceWarpPlugin:
+ BootstrapPatch.ForceSpaceWarpLoadDueToError = true;
+ BootstrapPatch.ErroredSWPluginDescriptor = plugin;
+ return;
+ case BaseUnityPlugin unityPlugin:
+ Object.Destroy(unityPlugin);
+ break;
+ }
+ }
+ plugin.Plugin = null;
+ }
+
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs
new file mode 100644
index 00000000..fdf7edfb
--- /dev/null
+++ b/SpaceWarp.Core/API/Mods/SpaceWarpPluginDescriptor.cs
@@ -0,0 +1,45 @@
+using System.IO;
+using JetBrains.Annotations;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Mods.JSON;
+
+namespace SpaceWarp.API.Mods;
+
+[PublicAPI]
+public class SpaceWarpPluginDescriptor
+{
+ public SpaceWarpPluginDescriptor(
+ [CanBeNull] ISpaceWarpMod plugin,
+ string guid,
+ string name,
+ ModInfo swInfo,
+ DirectoryInfo folder,
+ bool doLoadingActions = true,
+ [CanBeNull] IConfigFile configFile = null
+ )
+ {
+ Plugin = plugin;
+ Guid = guid;
+ Name = name;
+ SWInfo = swInfo;
+ Folder = folder;
+ DoLoadingActions = doLoadingActions;
+ ConfigFile = configFile;
+ }
+
+ [CanBeNull] public ISpaceWarpMod Plugin;
+ public readonly string Guid;
+ public readonly string Name;
+ public readonly ModInfo SWInfo;
+ public readonly DirectoryInfo Folder;
+ public bool DoLoadingActions;
+ [CanBeNull] public IConfigFile ConfigFile;
+
+ // Set by the version checking system
+ public bool Outdated;
+
+ public bool Unsupported = false;
+
+ // Used to check for mods that have not been pre-initialized
+ public bool LatePreInitialize;
+}
\ No newline at end of file
diff --git a/SpaceWarp/API/Parts/Colors.cs b/SpaceWarp.Core/API/Parts/Colors.cs
similarity index 94%
rename from SpaceWarp/API/Parts/Colors.cs
rename to SpaceWarp.Core/API/Parts/Colors.cs
index 2cf1842c..752bb847 100644
--- a/SpaceWarp/API/Parts/Colors.cs
+++ b/SpaceWarp.Core/API/Parts/Colors.cs
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
-using BepInEx.Logging;
+using JetBrains.Annotations;
using SpaceWarp.API.Lua;
using SpaceWarp.Patching;
+using UnityEngine;
namespace SpaceWarp.API.Parts;
[SpaceWarpLuaAPI("Colors")]
+[PublicAPI]
public static class Colors
{
///
@@ -54,5 +56,5 @@ public static void DeclareParts(string modGuid, IEnumerable partNameList
/// Name of the part as described in the .json.
///
[Obsolete("Use the shader \"KSP2/Parts/Paintable\" or \"Parts Replace\" instead")]
- public static UnityEngine.Texture[] GetTextures(string partName) => new UnityEngine.Texture[0];
+ public static Texture[] GetTextures(string partName) => Array.Empty();
}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Versions/SemanticVersion.cs b/SpaceWarp.Core/API/Versions/SemanticVersion.cs
new file mode 100644
index 00000000..ea45be74
--- /dev/null
+++ b/SpaceWarp.Core/API/Versions/SemanticVersion.cs
@@ -0,0 +1,346 @@
+using System;
+using System.Collections.Generic;
+
+// Disable warnings for missing Equals and GetHashCode implementations
+#pragma warning disable CS0660, CS0661
+
+namespace SpaceWarp.API.Versions;
+
+///
+/// Extended version of semantic versioning (see https://semver.org/) that supports an unlimited amount of
+/// version numbers.
+///
+public class SemanticVersion : IComparable
+{
+ #region Properties
+
+ ///
+ /// The major (first) version number.
+ ///
+ public int Major => VersionNumbers[0];
+
+ ///
+ /// The minor (second) version number.
+ ///
+ public int Minor => VersionNumbers[1];
+
+ ///
+ /// The patch (third) version number.
+ ///
+ public int Patch => VersionNumbers[2];
+
+ ///
+ /// List of all version numbers.
+ ///
+ public List VersionNumbers { get; } = new();
+
+ ///
+ /// Prerelease identifiers.
+ ///
+ public string Prerelease { get; }
+
+ ///
+ /// Build metadata.
+ ///
+ public string Build { get; }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Creates a new SemanticVersion object.
+ ///
+ /// Major version number
+ /// Minor version number
+ /// Patch version number
+ /// Prerelease identifiers
+ /// Build metadata
+ public SemanticVersion(int major, int minor, int patch, string prerelease, string build)
+ {
+ VersionNumbers.Add(major);
+ VersionNumbers.Add(minor);
+ VersionNumbers.Add(patch);
+ Prerelease = prerelease ?? string.Empty;
+ Build = build ?? string.Empty;
+ }
+
+ ///
+ /// Creates a new SemanticVersion object.
+ ///
+ /// Major version number
+ /// Minor version number
+ /// Patch version number
+ /// Prerelease identifiers
+ public SemanticVersion(int major, int minor, int patch, string prerelease)
+ : this(major, minor, patch, prerelease, string.Empty)
+ {
+ }
+
+ ///
+ /// Creates a new SemanticVersion object.
+ ///
+ /// Major version number
+ /// Minor version number
+ /// Patch version number
+ public SemanticVersion(int major, int minor, int patch)
+ : this(major, minor, patch, string.Empty)
+ {
+ }
+
+ ///
+ /// Creates a new SemanticVersion object from a version string.
+ ///
+ /// Version string in the format "major.minor.patch[-prerelease][+build]"
+ /// Thrown if the version string is invalid
+ public SemanticVersion(string version)
+ {
+ if (string.IsNullOrEmpty(version))
+ {
+ throw new ArgumentException("Version string must not be null or empty.");
+ }
+
+ var originalVersion = version;
+
+ var buildIndex = version.IndexOf('+');
+ if (buildIndex != -1)
+ {
+ Build = version[(buildIndex + 1)..];
+ version = version[..buildIndex];
+ }
+
+ var prereleaseIndex = version.IndexOf('-');
+ if (prereleaseIndex != -1)
+ {
+ Prerelease = version[(prereleaseIndex + 1)..];
+ version = version[..prereleaseIndex];
+ }
+
+ var versionParts = version.Split('.');
+ if (versionParts.Length < 3)
+ {
+ throw new ArgumentException(
+ $"Version string \"{originalVersion}\" must have at least 3 version numbers (major.minor.patch)."
+ );
+ }
+
+ foreach (var part in versionParts)
+ {
+ if (int.TryParse(part, out var versionNumber))
+ {
+ VersionNumbers.Add(versionNumber);
+ }
+ else
+ {
+ throw new ArgumentException(
+ $"Invalid version number: \"{part}\" in version string \"{originalVersion}\"."
+ );
+ }
+ }
+ }
+
+ #endregion
+
+ #region Comparisons
+
+ ///
+ /// Compares this version to another version.
+ ///
+ /// The other version to compare to.
+ ///
+ /// Negative if this version is less than the other version, 0 if they are equal, positive if this version is
+ /// larger than the other version.
+ ///
+ public int CompareTo(SemanticVersion other)
+ {
+ /*
+ * Precedence MUST be calculated by separating the version into major, minor, patch and pre-release identifiers
+ * in that order (Build metadata does not figure into precedence).
+ *
+ * Precedence is determined by the first difference when comparing each of these identifiers from left to right
+ * as follows: Major, minor, and patch versions are always compared numerically.
+ *
+ * Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
+ *
+ * When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version:
+ *
+ * Example: 1.0.0-alpha < 1.0.0.
+ *
+ * Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by
+ * comparing each dot separated identifier from left to right until a difference is found as follows:
+ *
+ * Identifiers consisting of only digits are compared numerically.
+ *
+ * Identifiers with letters or hyphens are compared lexically in ASCII sort order.
+ *
+ * Numeric identifiers always have lower precedence than non-numeric identifiers.
+ *
+ * A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding
+ * identifiers are equal.
+ *
+ * Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11
+ * < 1.0.0-rc.1 < 1.0.0.
+ */
+
+ // Compare the individual version numbers
+ var maxLength = Math.Max(VersionNumbers.Count, other.VersionNumbers.Count);
+ for (var i = 0; i < maxLength; i++)
+ {
+ var version1Part = i < VersionNumbers.Count ? VersionNumbers[i] : 0;
+ var version2Part = i < other.VersionNumbers.Count ? other.VersionNumbers[i] : 0;
+
+ if (version1Part > version2Part)
+ {
+ return 1;
+ }
+
+ if (version1Part < version2Part)
+ {
+ return -1;
+ }
+ }
+
+ // If the version numbers are equal, compare the prerelease identifiers if at least one of them is not null,
+ // otherwise the versions are considered equal
+ if (Prerelease == null && other.Prerelease == null)
+ {
+ return 0;
+ }
+
+ if (Prerelease == null)
+ {
+ return 1;
+ }
+
+ if (other.Prerelease == null)
+ {
+ return -1;
+ }
+
+ // Both prerelease strings are not null, compare them
+ var prereleaseParts1 = Prerelease.Split('.');
+ var prereleaseParts2 = other.Prerelease.Split('.');
+ var maxPrereleaseLength = Math.Max(prereleaseParts1.Length, prereleaseParts2.Length);
+ for (var i = 0; i < maxPrereleaseLength; i++)
+ {
+ var prerelease1Part = i < prereleaseParts1.Length ? prereleaseParts1[i] : string.Empty;
+ var prerelease2Part = i < prereleaseParts2.Length ? prereleaseParts2[i] : string.Empty;
+
+ if (prerelease1Part == prerelease2Part)
+ {
+ continue;
+ }
+
+ var prerelease1PartIsNumeric = int.TryParse(prerelease1Part, out var prerelease1PartNumber);
+ var prerelease2PartIsNumeric = int.TryParse(prerelease2Part, out var prerelease2PartNumber);
+
+ if (prerelease1PartIsNumeric && prerelease2PartIsNumeric)
+ {
+ return prerelease1PartNumber - prerelease2PartNumber;
+ }
+
+ if (prerelease1PartIsNumeric)
+ {
+ return -1;
+ }
+
+ if (prerelease2PartIsNumeric)
+ {
+ return 1;
+ }
+
+ return string.Compare(prerelease1Part, prerelease2Part, StringComparison.Ordinal);
+ }
+
+ return 0;
+ }
+
+ ///
+ /// Compares whether one version is less than another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is less than b, false otherwise.
+ public static bool operator <(SemanticVersion a, SemanticVersion b) => a.CompareTo(b) < 0;
+
+ ///
+ /// Compares whether one version is greater than another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is greater than b, false otherwise.
+ public static bool operator >(SemanticVersion a, SemanticVersion b) => a.CompareTo(b) > 0;
+
+ ///
+ /// Compares whether one version is less than or equal to another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is less than or equal to b, false otherwise.
+ public static bool operator <=(SemanticVersion a, SemanticVersion b) => a.CompareTo(b) <= 0;
+
+ ///
+ /// Compares whether one version is greater than or equal to another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is greater than or equal to b, false otherwise.
+ public static bool operator >=(SemanticVersion a, SemanticVersion b) => a.CompareTo(b) >= 0;
+
+ ///
+ /// Compares whether one version is equal to another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is equal to b, false otherwise.
+ public static bool operator ==(SemanticVersion a, SemanticVersion b) =>
+ (a is null && b is null) || (a is not null && b is not null && a.CompareTo(b) == 0);
+
+ ///
+ /// Compares whether one version is not equal to another version.
+ ///
+ /// First version to compare.
+ /// Second version to compare.
+ /// True if a is not equal to b, false otherwise.
+ public static bool operator !=(SemanticVersion a, SemanticVersion b) => !(a == b);
+
+ #endregion
+
+ #region String conversion
+
+ ///
+ /// Returns the version as a string.
+ ///
+ /// Version string.
+ public override string ToString()
+ {
+ var versionString = $"{Major}.{Minor}.{Patch}";
+ if (!string.IsNullOrEmpty(Prerelease))
+ {
+ versionString += $"-{Prerelease}";
+ }
+
+ if (!string.IsNullOrEmpty(Build))
+ {
+ versionString += $"+{Build}";
+ }
+
+ return versionString;
+ }
+
+ ///
+ /// Implicitly converts a SemanticVersion object to a string.
+ ///
+ /// SemanticVersion object to convert.
+ /// Version string.
+ public static implicit operator string(SemanticVersion version) => version.ToString();
+
+ ///
+ /// Explicitly converts a string to a SemanticVersion object.
+ ///
+ /// Version string to convert.
+ /// SemanticVersion object.
+ public static explicit operator SemanticVersion(string version) => new(version);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/API/Versions/VersionUtility.cs b/SpaceWarp.Core/API/Versions/VersionUtility.cs
new file mode 100644
index 00000000..b6f9b2a3
--- /dev/null
+++ b/SpaceWarp.Core/API/Versions/VersionUtility.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Text.RegularExpressions;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Versions;
+
+[PublicAPI]
+public static class VersionUtility
+{
+ ///
+ /// Checks if one semantic version is newer than another
+ ///
+ /// The first version
+ /// The second version
+ /// version1 is newer than version2
+ public static bool IsNewerThan(string version1, string version2)
+ {
+ return CompareSemanticVersionStrings(version1, version2) > 0;
+ }
+
+ ///
+ /// Checks if one semantic version is older than another
+ ///
+ /// The first version
+ /// The second version
+ /// version1 is older than version2
+ public static bool IsOlderThan(string version1, string version2)
+ {
+ return CompareSemanticVersionStrings(version1, version2) < 0;
+ }
+
+ public static bool IsSupported(string version, string min, string max)
+ {
+ return !IsOlderThan(version, min) && !IsNewerThan(version, max);
+ }
+
+ private static Regex _toClear = new("[^0-9.*]");
+ private static string PreprocessSemanticVersion(string semver) => _toClear.Replace(semver, "");
+
+
+ ///
+ /// Compares 2 semantic versions
+ ///
+ /// The first version
+ /// The second version
+ ///
+ /// 0 if version1 equals version2, -1 if version1 is less than version2, 1 if version1 is greater than version2
+ ///
+ public static int CompareSemanticVersionStrings(string version1, string version2)
+ {
+ var semanticVersion1 = PreprocessSemanticVersion(version1);
+ var semanticVersion2 = PreprocessSemanticVersion(version2);
+ Console.WriteLine($"version 1: {version1} -> {semanticVersion1}");
+ Console.WriteLine($"version 2: {version2} -> {semanticVersion2}");
+ var version1Parts = semanticVersion1.Split('.');
+ var version2Parts = semanticVersion2.Split('.');
+
+ var maxLength = Math.Max(version1Parts.Length, version2Parts.Length);
+
+ for (var i = 0; i < maxLength; i++)
+ {
+ var version1PartString = i < version1Parts.Length ? version1Parts[i] : "0";
+ var version2PartString = i < version2Parts.Length ? version2Parts[i] : "0";
+
+ if (version1PartString == "*" || version2PartString == "*")
+ {
+ break;
+ }
+
+ var version1Part = int.Parse(version1PartString);
+ var version2Part = int.Parse(version2PartString);
+
+ if (version1Part > version2Part)
+ {
+ return 1;
+ }
+
+ if (version1Part < version2Part)
+ {
+ return -1;
+ }
+ }
+
+ return ComparePrereleaseSemanticVersions(version1, version2);
+ }
+
+ private static Regex _prereleaseVersion = new(@"(\D+)(\d+)");
+
+ private static int ComparePrereleaseSemanticVersions(string version1, string version2)
+ {
+ var dash1 = version1.IndexOf("-", StringComparison.Ordinal);
+ var dash2 = version2.IndexOf("-", StringComparison.Ordinal);
+ if (dash1 == -1 && dash2 == -1)
+ {
+ return 0;
+ }
+
+ if (dash1 == -1)
+ {
+ return 1;
+ }
+
+ if (dash2 == -1)
+ {
+ return -1;
+ }
+
+ var alphaVersion1 = version1[(dash1 + 1)..];
+ var alphaVersion2 = version2[(dash2 + 1)..];
+ // So now we get the numerics from the end of here
+ int alphaVersionNumber1 = 0;
+ int alphaVersionNumber2 = 0;
+ var alphaVersionName1 = alphaVersion1;
+ var alphaVersionName2 = alphaVersion2;
+ if (_prereleaseVersion.IsMatch(alphaVersion1))
+ {
+ var match = _prereleaseVersion.Match(alphaVersion1);
+ var name = match.Groups[1];
+ var number = match.Groups[2];
+ alphaVersionNumber1 = int.Parse(number.Value);
+ alphaVersionName1 = name.Value;
+ }
+
+ if (_prereleaseVersion.IsMatch(alphaVersion2))
+ {
+ var match = _prereleaseVersion.Match(alphaVersion2);
+ var name = match.Groups[1];
+ var number = match.Groups[2];
+ alphaVersionNumber2 = int.Parse(number.Value);
+ alphaVersionName2 = name.Value;
+ }
+
+ var comparison = string.CompareOrdinal(alphaVersionName1, alphaVersionName2);
+ if (comparison == 0)
+ {
+ if (alphaVersionNumber1 > alphaVersionNumber2)
+ {
+ return 1;
+ } else if (alphaVersionNumber1 == alphaVersionNumber2)
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ return comparison;
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/AssemblyInfo.cs b/SpaceWarp.Core/AssemblyInfo.cs
new file mode 100644
index 00000000..7387ab6a
--- /dev/null
+++ b/SpaceWarp.Core/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("SpaceWarp.UI")]
+[assembly: InternalsVisibleTo("SpaceWarp.VersionChecking")]
\ No newline at end of file
diff --git a/SpaceWarp/Backend/Modding/AssetOnlyMod.cs b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs
similarity index 73%
rename from SpaceWarp/Backend/Modding/AssetOnlyMod.cs
rename to SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs
index 470a1f7d..cd34c581 100644
--- a/SpaceWarp/Backend/Modding/AssetOnlyMod.cs
+++ b/SpaceWarp.Core/Backend/Modding/AssetOnlyMod.cs
@@ -1,4 +1,5 @@
using BepInEx.Logging;
+using SpaceWarp.API.Configuration;
using SpaceWarp.API.Logging;
using SpaceWarp.API.Mods;
@@ -10,7 +11,7 @@ public AssetOnlyMod(string name)
{
SWLogger = new BepInExLogger(new ManualLogSource(name));
}
-
+
public void OnPreInitialized()
{
}
@@ -24,4 +25,6 @@ public void OnPostInitialized()
}
public ILogger SWLogger { get; }
+ public IConfigFile SWConfiguration => new EmptyConfigFile();
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs
new file mode 100644
index 00000000..84f61bda
--- /dev/null
+++ b/SpaceWarp.Core/Backend/Modding/BepInExModAdapter.cs
@@ -0,0 +1,32 @@
+using BepInEx;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+using SpaceWarp.API.Mods;
+
+namespace SpaceWarp.Backend.Modding;
+
+public class BepInExModAdapter : ISpaceWarpMod
+{
+ public readonly BaseUnityPlugin Plugin;
+
+ public void OnPreInitialized()
+ {
+ }
+
+ public void OnInitialized()
+ {
+ }
+
+ public void OnPostInitialized()
+ {
+ }
+
+ public ILogger SWLogger => new BepInExLogger(Plugin.Logger);
+ public IConfigFile SWConfiguration => new BepInExConfigFile(Plugin.Config);
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
+
+ public BepInExModAdapter(BaseUnityPlugin plugin)
+ {
+ Plugin = plugin;
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Backend/Modding/Ksp2ModInfo.cs b/SpaceWarp.Core/Backend/Modding/Ksp2ModInfo.cs
new file mode 100644
index 00000000..d8615dd9
--- /dev/null
+++ b/SpaceWarp.Core/Backend/Modding/Ksp2ModInfo.cs
@@ -0,0 +1,43 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace SpaceWarp.Backend.Modding;
+
+public class Ksp2ModInfo
+{
+ [JsonConverter(typeof(VersionConverter))]
+ [JsonProperty]
+ public Version APIVersion { get; private set; }
+
+ // Token: 0x17001C93 RID: 7315
+ // (get) Token: 0x06008070 RID: 32880 RVA: 0x001EE0B9 File Offset: 0x001EC2B9
+ // (set) Token: 0x06008071 RID: 32881 RVA: 0x001EE0C1 File Offset: 0x001EC2C1
+ [JsonConverter(typeof(VersionConverter))]
+ [JsonProperty]
+ public Version ModVersion { get; private set; }
+
+ // Token: 0x17001C94 RID: 7316
+ // (get) Token: 0x06008072 RID: 32882 RVA: 0x001EE0CA File Offset: 0x001EC2CA
+ // (set) Token: 0x06008073 RID: 32883 RVA: 0x001EE0D2 File Offset: 0x001EC2D2
+ [JsonProperty]
+ public string ModName { get; private set; }
+
+ // Token: 0x17001C95 RID: 7317
+ // (get) Token: 0x06008074 RID: 32884 RVA: 0x001EE0DB File Offset: 0x001EC2DB
+ // (set) Token: 0x06008075 RID: 32885 RVA: 0x001EE0E3 File Offset: 0x001EC2E3
+ [JsonProperty]
+ public string ModAuthor { get; private set; }
+
+ // Token: 0x17001C96 RID: 7318
+ // (get) Token: 0x06008076 RID: 32886 RVA: 0x001EE0EC File Offset: 0x001EC2EC
+ // (set) Token: 0x06008077 RID: 32887 RVA: 0x001EE0F4 File Offset: 0x001EC2F4
+ [JsonProperty]
+ public string ModDescription { get; private set; }
+
+ // Token: 0x17001C97 RID: 7319
+ // (get) Token: 0x06008078 RID: 32888 RVA: 0x001EE0FD File Offset: 0x001EC2FD
+ // (set) Token: 0x06008079 RID: 32889 RVA: 0x001EE105 File Offset: 0x001EC305
+ [JsonProperty]
+ public string Catalog { get; private set; }
+}
\ No newline at end of file
diff --git a/SpaceWarp/Backend/Modding/KspModAdapter.cs b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs
similarity index 63%
rename from SpaceWarp/Backend/Modding/KspModAdapter.cs
rename to SpaceWarp.Core/Backend/Modding/KspModAdapter.cs
index 7600fc96..2e85d23c 100644
--- a/SpaceWarp/Backend/Modding/KspModAdapter.cs
+++ b/SpaceWarp.Core/Backend/Modding/KspModAdapter.cs
@@ -1,6 +1,5 @@
-using System;
-using JetBrains.Annotations;
-using KSP.Modding;
+using KSP.Modding;
+using SpaceWarp.API.Configuration;
using SpaceWarp.API.Logging;
using SpaceWarp.API.Mods;
using UnityEngine;
@@ -15,6 +14,7 @@ internal class KspModAdapter : MonoBehaviour, ISpaceWarpMod
public void OnPreInitialized()
{
AdaptedMod.modCore?.ModStart();
+ SWLogger = new UnityLogSource(AdaptedMod.ModName);
}
public void OnInitialized()
@@ -30,5 +30,7 @@ public void Update()
AdaptedMod.modCore?.ModUpdate();
}
- public ILogger SWLogger { get; }
+ public ILogger SWLogger { get; private set; }
+ public IConfigFile SWConfiguration => new EmptyConfigFile();
+ public SpaceWarpPluginDescriptor SWMetadata { get; set; }
}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Backend/Modding/PluginRegister.cs b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs
new file mode 100644
index 00000000..82cbe719
--- /dev/null
+++ b/SpaceWarp.Core/Backend/Modding/PluginRegister.cs
@@ -0,0 +1,530 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using BepInEx;
+using BepInEx.Bootstrap;
+using BepInEx.Configuration;
+using Newtonsoft.Json;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+using SpaceWarp.API.Mods;
+using SpaceWarp.API.Mods.JSON;
+using SpaceWarp.API.Versions;
+using SpaceWarpPatcher;
+
+namespace SpaceWarp.Backend.Modding;
+
+internal static class PluginRegister
+{
+ public static void RegisterAllMods()
+ {
+ RegisterAllBepInExMods();
+ GetDisabledPlugins();
+ RegisterAllCodelessMods();
+ RegisterAllKspMods();
+ RegisterAllErroredMods();
+ DisableMods();
+ }
+
+ private static ILogger Logger = (BepInExLogger)SpaceWarpPlugin.Logger;
+
+ private static ModInfo BepInExToSWInfo(PluginInfo plugin)
+ {
+ var newInfo = new ModInfo
+ {
+ Spec = SpecVersion.V2_0,
+ ModID = plugin.Metadata.GUID,
+ Name = plugin.Metadata.Name,
+ Author = "",
+ Description = "",
+ Source = "",
+ Version = plugin.Metadata.Version.ToString(),
+ Dependencies = plugin.Dependencies.Select(x => new DependencyInfo
+ {
+ ID = x.DependencyGUID,
+ Version = new SupportedVersionsInfo
+ {
+ Min = x.MinimumVersion.ToString(),
+ Max = "*"
+ }
+ }).ToList(),
+ SupportedKsp2Versions = new SupportedVersionsInfo
+ {
+ Min = "*",
+ Max = "*"
+ },
+ VersionCheck = null,
+ VersionCheckType = VersionCheckType.SwInfo,
+ Conflicts = plugin.Incompatibilities.Select(x => new DependencyInfo
+ {
+ ID = x.IncompatibilityGUID,
+ Version = new SupportedVersionsInfo
+ {
+ Min = "*",
+ Max = "*"
+ }
+ }).ToList()
+ };
+ return newInfo;
+ }
+
+ private static bool AssertFolderPath(BaseSpaceWarpPlugin plugin, string folderPath)
+ {
+ if (Path.GetFileName(folderPath) != "plugins") return true;
+
+ Logger.LogError(
+ $"Found Space Warp mod {plugin.Info.Metadata.Name} in the BepInEx/plugins directory. This mod will not be initialized.");
+
+ var descriptor = new SpaceWarpPluginDescriptor(
+ plugin,
+ plugin.Info.Metadata.GUID,
+ plugin.Info.Metadata.Name,
+ BepInExToSWInfo(plugin.Info),
+ new DirectoryInfo(folderPath),
+ false,
+ new BepInExConfigFile(plugin.Config)
+ );
+ PluginList.NoteBadDirectoryError(descriptor);
+ return false;
+ }
+
+ private static bool AssertModInfoExistence(BaseSpaceWarpPlugin plugin, string modInfoPath, string folderPath)
+ {
+ if (File.Exists(modInfoPath)) return true;
+
+ Logger.LogError(
+ $"Found Space Warp plugin {plugin.Info.Metadata.Name} without a swinfo.json next to it. This mod will not be initialized.");
+ PluginList.NoteMissingSwinfoError(new SpaceWarpPluginDescriptor(plugin,
+ plugin.Info.Metadata.GUID,
+ plugin.Info.Metadata.Name,
+ BepInExToSWInfo(plugin.Info),
+ new DirectoryInfo(folderPath)));
+ return false;
+ }
+
+ private static bool TryReadModInfo(BaseUnityPlugin plugin,
+ string modInfoPath, string folderPath, out ModInfo metadata)
+ {
+ try
+ {
+ metadata = JsonConvert.DeserializeObject(File.ReadAllText(modInfoPath));
+ }
+ catch
+ {
+ Logger.LogError(
+ $"Error reading metadata for spacewarp plugin {plugin.Info.Metadata.Name}. This mod will not be initialized");
+ PluginList.NoteMissingSwinfoError(new SpaceWarpPluginDescriptor(
+ plugin as BaseSpaceWarpPlugin,
+ plugin.Info.Metadata.GUID,
+ plugin.Info.Metadata.Name,
+ BepInExToSWInfo(plugin.Info),
+ new DirectoryInfo(folderPath)
+ ));
+ metadata = null;
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool AssertSpecificationCompliance(
+ SpaceWarpPluginDescriptor descriptor,
+ BaseUnityPlugin plugin,
+ ModInfo metadata,
+ string folderPath
+ ) => metadata.Spec < SpecVersion.V1_3 || AssertSpecVersion13Compliance(
+ descriptor,
+ plugin,
+ metadata,
+ folderPath
+ );
+
+ private static bool AssertSpecVersion13Compliance(
+ SpaceWarpPluginDescriptor descriptor,
+ BaseUnityPlugin plugin,
+ ModInfo metadata,
+ string folderPath
+ ) => AssertMatchingModID(descriptor, plugin, metadata, folderPath) &&
+ AssertMatchingVersions(descriptor, plugin, metadata, folderPath) &&
+ AssertAllDependenciesAreSpecified(descriptor, plugin, metadata);
+
+ private static bool AssertAllDependenciesAreSpecified(
+ SpaceWarpPluginDescriptor descriptor,
+ BaseUnityPlugin plugin,
+ ModInfo metadata
+ ) => plugin.Info.Dependencies.Aggregate(
+ true,
+ (current, dep) => current && AssertDependencyIsSpecified(plugin, descriptor, dep, metadata)
+ );
+
+ private static bool AssertDependencyIsSpecified(
+ BaseUnityPlugin plugin,
+ SpaceWarpPluginDescriptor descriptor,
+ BepInDependency dep,
+ ModInfo metadata
+ )
+ {
+ if (metadata.Dependencies.Any(
+ x => string.Equals(x.ID, dep.DependencyGUID, StringComparison.InvariantCultureIgnoreCase))
+ ) return true;
+
+ Logger.LogError(
+ $"Found Space Warp Plugin {plugin.Info.Metadata.Name} that has an unspecified swinfo dependency found in its BepInDependencies: {dep.DependencyGUID}");
+ PluginList.NoteUnspecifiedDependencyError(descriptor, dep.DependencyGUID);
+ metadata.Dependencies.Add(new DependencyInfo
+ {
+ ID = dep.DependencyGUID,
+ Version = new SupportedVersionsInfo
+ {
+ Min = dep.MinimumVersion.ToString(),
+ Max = "*"
+ }
+ });
+ return false;
+ }
+
+ private static string ClearPrerelease(string version)
+ {
+ var semver = new SemanticVersion(version);
+ return $"{semver.Major}.{semver.Minor}.{semver.Patch}{(semver.VersionNumbers.Count > 3 ? $".{semver.VersionNumbers[3]}" : "")}";
+ }
+
+ private static bool AssertMatchingVersions(
+ SpaceWarpPluginDescriptor descriptor,
+ BaseUnityPlugin plugin,
+ ModInfo metadata,
+ string folderPath
+ )
+ {
+ if (new Version(ClearPrerelease(metadata.Version)) == plugin.Info.Metadata.Version) return true;
+
+ Logger.LogError(
+ $"Found Space Warp plugin {plugin.Info.Metadata.Name} that's swinfo version ({metadata.Version}) does not match the plugin version ({plugin.Info.Metadata.Version}), this mod will not be initialized");
+ PluginList.NoteMismatchedVersionError(descriptor);
+ return false;
+ }
+
+ private static bool AssertMatchingModID(
+ SpaceWarpPluginDescriptor descriptor,
+ BaseUnityPlugin plugin,
+ ModInfo metadata,
+ string folderPath
+ )
+ {
+ var modID = metadata.ModID;
+ if (modID == plugin.Info.Metadata.GUID) return true;
+
+ Logger.LogError(
+ $"Found Space Warp plugin {plugin.Info.Metadata.Name} that has an swinfo.json w/ spec version >= 1.3 that's ModID is not the same as the plugins GUID, This mod will not be initialized.");
+ PluginList.NoteBadIDError(descriptor);
+ return false;
+ }
+
+ private static void RegisterSingleSpaceWarpPlugin(BaseSpaceWarpPlugin plugin)
+ {
+ var folderPath = Path.GetDirectoryName(plugin.Info.Location);
+ plugin.PluginFolderPath = folderPath;
+ if (!AssertFolderPath(plugin, folderPath)) return;
+
+ var modInfoPath = Path.Combine(folderPath!, "swinfo.json");
+
+ if (!AssertModInfoExistence(plugin, modInfoPath, folderPath)) return;
+
+ if (!TryReadModInfo(plugin, modInfoPath, folderPath, out var metadata)) return;
+
+ plugin.SpaceWarpMetadata = metadata;
+ var directoryInfo = new FileInfo(modInfoPath).Directory;
+ var descriptor = new SpaceWarpPluginDescriptor(
+ plugin,
+ metadata.Spec != SpecVersion.V1_2 ? metadata.ModID : plugin.Info.Metadata.GUID,
+ metadata.Name,
+ metadata,
+ directoryInfo,
+ true,
+ new BepInExConfigFile(plugin.Config)
+ );
+ descriptor.Plugin!.SWMetadata = descriptor;
+ if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath)) return;
+
+ PluginList.RegisterPlugin(descriptor);
+ }
+
+ private static void RegisterSingleBepInExPlugin(BaseUnityPlugin plugin)
+ {
+ if (PluginList.AllPlugins.Any(x => x.Plugin is BaseUnityPlugin bup && bup == plugin))
+ {
+ return;
+ }
+
+ var folderPath = Path.GetDirectoryName(plugin.Info.Location);
+ var modInfoPath = Path.Combine(folderPath!, "swinfo.json");
+ var directoryInfo = new DirectoryInfo(Path.GetDirectoryName(plugin.Info.Location)!);
+ if (File.Exists(modInfoPath))
+ {
+ if (!TryReadModInfo(plugin, modInfoPath, folderPath, out var metadata)) return;
+ var descriptor = new SpaceWarpPluginDescriptor(
+ new BepInExModAdapter(plugin),
+ metadata.Spec != SpecVersion.V1_2 ? metadata.ModID : plugin.Info.Metadata.GUID,
+ metadata.Name,
+ metadata,
+ directoryInfo,
+ false,
+ new BepInExConfigFile(plugin.Config)
+ );
+ descriptor.Plugin!.SWMetadata = descriptor;
+ if (!AssertSpecificationCompliance(descriptor, plugin, metadata, folderPath))
+ return;
+ PluginList.RegisterPlugin(descriptor);
+ }
+ else
+ {
+ PluginList.RegisterPlugin(GetBepInExDescriptor(plugin));
+ }
+ }
+
+ private static SpaceWarpPluginDescriptor GetBepInExDescriptor(BaseUnityPlugin plugin)
+ {
+ var pluginAdapter = new BepInExModAdapter(plugin);
+ var descriptor = new SpaceWarpPluginDescriptor(
+ pluginAdapter,
+ plugin.Info.Metadata.GUID,
+ plugin.Info.Metadata.Name,
+ BepInExToSWInfo(plugin.Info),
+ new DirectoryInfo(Path.GetDirectoryName(plugin.Info.Location)!),
+ false,
+ new BepInExConfigFile(plugin.Config)
+ );
+ pluginAdapter.SWMetadata = descriptor;
+ return descriptor;
+ }
+
+ private static SpaceWarpPluginDescriptor GetBepInExDescriptor(PluginInfo info) => new(
+ null,
+ info.Metadata.GUID,
+ info.Metadata.Name,
+ BepInExToSWInfo(info),
+ new DirectoryInfo(Path.GetDirectoryName(info.Location)!)
+ );
+
+ private static void RegisterAllBepInExMods()
+ {
+#pragma warning disable CS0618
+ foreach (var plugin in Chainloader.Plugins)
+#pragma warning restore CS0618
+ {
+ if (plugin is BaseSpaceWarpPlugin spaceWarpMod)
+ {
+ Logger.LogInfo($"Registering SpaceWarp plugin: {plugin.Info.Metadata.Name}");
+ RegisterSingleSpaceWarpPlugin(spaceWarpMod);
+ }
+ else
+ {
+ Logger.LogInfo($"Registering BIE plugin: {plugin.Info.Metadata.Name}");
+ RegisterSingleBepInExPlugin(plugin);
+ }
+ }
+ }
+
+ private static void RegisterAllCodelessMods()
+ {
+ var pluginPath = new DirectoryInfo(Paths.PluginPath);
+ foreach (var swinfo in pluginPath.GetFiles("swinfo.json", SearchOption.AllDirectories))
+ {
+ ModInfo swinfoData;
+ try
+ {
+ swinfoData = JsonConvert.DeserializeObject(File.ReadAllText(swinfo.FullName));
+ }
+ catch
+ {
+ Logger.LogError($"Error reading metadata file: {swinfo.FullName}, this mod will be ignored");
+ continue;
+ }
+
+ if (swinfoData.Spec < SpecVersion.V1_3)
+ {
+ Logger.LogWarning(
+ $"Found swinfo information for: {swinfoData.Name}, but its spec is less than v1.3, if this describes a \"codeless\" mod, it will be ignored");
+ continue;
+ }
+
+ var descriptor = new SpaceWarpPluginDescriptor(
+ new AssetOnlyMod(swinfoData.Name),
+ swinfoData.ModID,
+ swinfoData.Name,
+ swinfoData, swinfo.Directory,
+ true,
+ new BepInExConfigFile(FindOrCreateConfigFile(swinfoData.ModID))
+ );
+ descriptor.Plugin!.SWMetadata = descriptor;
+
+ Logger.LogInfo($"Attempting to register codeless mod: {swinfoData.ModID}, {swinfoData.Name}");
+
+ if (PluginList.AllPlugins.Any(
+ x => string.Equals(x.Guid, swinfoData.ModID, StringComparison.InvariantCultureIgnoreCase)
+ )) continue;
+
+ // Now we can just add it to our plugin list
+ PluginList.RegisterPlugin(descriptor);
+ }
+ }
+
+ private static ConfigFile FindOrCreateConfigFile(string guid)
+ {
+ var path = Path.Combine(Paths.ConfigPath, $"{guid}.cfg");
+ return new ConfigFile(path, true);
+ }
+
+ private static ModInfo KspToSwinfo(Ksp2ModInfo mod)
+ {
+ var newInfo = new ModInfo
+ {
+ Spec = SpecVersion.V1_3,
+ ModID = mod.ModName,
+ Name = mod.ModName,
+ Author = mod.ModAuthor,
+ Description = mod.ModDescription,
+ Source = "",
+ Version = mod.ModVersion.ToString(),
+ Dependencies = new List(),
+ SupportedKsp2Versions = new SupportedVersionsInfo
+ {
+ Min = "*",
+ Max = "*"
+ },
+ VersionCheck = null,
+ VersionCheckType = VersionCheckType.SwInfo
+ };
+ return newInfo;
+ }
+
+ private static void RegisterSingleKspMod(DirectoryInfo folder)
+ {
+ ModInfo metadata;
+ if (File.Exists(Path.Combine(folder.FullName, "swinfo.json")))
+ {
+ metadata = JsonConvert.DeserializeObject(
+ File.ReadAllText(Path.Combine(folder.FullName, "swinfo.json"))
+ );
+ }
+ else if (File.Exists(Path.Combine(folder.FullName, "modinfo.json")))
+ {
+ var modinfo = JsonConvert.DeserializeObject(
+ File.ReadAllText(Path.Combine(folder.FullName, "modinfo.json"))
+ );
+ metadata = KspToSwinfo(modinfo);
+ }
+ else return;
+
+ // This descriptor *will* be modified later
+ var descriptor = new SpaceWarpPluginDescriptor(
+ null,
+ metadata.ModID,
+ metadata.Name,
+ metadata,
+ folder,
+ false
+ )
+ {
+ LatePreInitialize = true
+ };
+ PluginList.RegisterPlugin(descriptor);
+ }
+
+ private static void RegisterAllKspMods()
+ {
+ var pluginPath = new DirectoryInfo(Path.Combine(Paths.GameRootPath, "GameData", "Mods"));
+ if (!pluginPath.Exists) return;
+ Logger.LogInfo($"KSP Loaded mods path: {pluginPath.FullName}");
+ // Lets quickly register every KSP loader loaded mod into the load order before anything, with late pre-initialize
+ foreach (var plugin in pluginPath.EnumerateDirectories())
+ {
+ Logger.LogInfo($"Attempting to register KSP loaded mod at {pluginPath.FullName}");
+ RegisterSingleKspMod(plugin);
+ }
+ }
+
+ private static void RegisterAllErroredMods()
+ {
+ // Lets do some magic here by copying some code to just get all the plugin types
+ var allPlugins = TypeLoader.FindPluginTypes(
+ Paths.PluginPath,
+ Chainloader.ToPluginInfo,
+ Chainloader.HasBepinPlugins,
+ "chainloader"
+ );
+ foreach (var plugin in allPlugins)
+ {
+ foreach (var info in plugin.Value)
+ {
+ info.Location = plugin.Key;
+ if (PluginList.AllPlugins.Any(
+ x => string.Equals(x.Guid, info.Metadata.GUID, StringComparison.InvariantCultureIgnoreCase)
+ )) continue;
+
+ var descriptor = new SpaceWarpPluginDescriptor(
+ null,
+ info.Metadata.GUID,
+ info.Metadata.Name,
+ BepInExToSWInfo(info),
+ new DirectoryInfo(Path.GetDirectoryName(info.Location)!)
+ );
+ var errored = PluginList.GetErrorDescriptor(descriptor);
+ errored.MissingDependencies = info.Dependencies.Select(x => x.DependencyGUID)
+ .Where(guid => PluginList.AllEnabledAndActivePlugins.All(x => x.Guid != guid))
+ .ToList();
+ }
+ }
+ }
+
+ private static void GetDisabledPlugins()
+ {
+ foreach (var plugin in ChainloaderPatch.DisabledPlugins)
+ {
+ GetSingleDisabledPlugin(plugin);
+ }
+ }
+
+ private static void GetSingleDisabledPlugin(PluginInfo plugin)
+ {
+ var folderPath = Path.GetDirectoryName(plugin.Location);
+ var swInfoPath = Path.Combine(folderPath!, "swinfo.json");
+ if (Path.GetFileName(folderPath) != "plugins" && File.Exists(swInfoPath))
+ {
+ try
+ {
+ var swInfo = JsonConvert.DeserializeObject(File.ReadAllText(swInfoPath));
+ var descriptor = new SpaceWarpPluginDescriptor(
+ null,
+ plugin.Metadata.GUID,
+ plugin.Metadata.Name,
+ swInfo,
+ new DirectoryInfo(folderPath)
+ );
+ PluginList.RegisterPlugin(descriptor);
+ PluginList.Disable(descriptor.Guid);
+ }
+ catch
+ {
+ var descriptor = GetBepInExDescriptor(plugin);
+ PluginList.RegisterPlugin(descriptor);
+ PluginList.Disable(descriptor.Guid);
+ }
+ }
+ else
+ {
+ var descriptor = GetBepInExDescriptor(plugin);
+ PluginList.RegisterPlugin(descriptor);
+ PluginList.Disable(descriptor.Guid);
+ }
+ }
+
+ private static void DisableMods()
+ {
+ foreach (var mod in ChainloaderPatch.DisabledPluginGuids)
+ {
+ PluginList.Disable(mod);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp/InternalUtilities/AssetHelpers.cs b/SpaceWarp.Core/InternalUtilities/AssetHelpers.cs
similarity index 65%
rename from SpaceWarp/InternalUtilities/AssetHelpers.cs
rename to SpaceWarp.Core/InternalUtilities/AssetHelpers.cs
index 764ea84f..5eeeaac3 100644
--- a/SpaceWarp/InternalUtilities/AssetHelpers.cs
+++ b/SpaceWarp.Core/InternalUtilities/AssetHelpers.cs
@@ -10,29 +10,29 @@ internal static class AssetHelpers
{
public static void LoadAddressable(string catalog)
{
- SpaceWarpManager.Logger.LogInfo($"Attempting to load {catalog}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"Attempting to load {catalog}");
var operation = Addressables.LoadContentCatalogAsync(catalog);
operation.WaitForCompletion();
if (operation.Status == AsyncOperationStatus.Failed)
{
- SpaceWarpManager.Logger.LogError($"Failed to load addressables catalog {catalog}");
+ SpaceWarpPlugin.Instance.SWLogger.LogError($"Failed to load addressables catalog {catalog}");
}
else
{
- SpaceWarpManager.Logger.LogInfo($"Loaded addressables catalog {catalog}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"Loaded addressables catalog {catalog}");
var locator = operation.Result;
- SpaceWarpManager.Logger.LogInfo($"{catalog} ----- {locator.LocatorId}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"{catalog} ----- {locator.LocatorId}");
GameManager.Instance.Assets.RegisterResourceLocator(locator);
}
}
internal static void LoadLocalizationFromFolder(string folder)
{
- SpaceWarpManager.Logger.LogInfo($"Attempting to load localizations from {folder}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"Attempting to load localizations from {folder}");
LanguageSourceData languageSourceData = null;
if (!Directory.Exists(folder))
{
- SpaceWarpManager.Logger.LogInfo($"{folder} does not exist, not loading localizations.");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"{folder} does not exist, not loading localizations.");
return;
}
@@ -40,27 +40,26 @@ internal static void LoadLocalizationFromFolder(string folder)
foreach (var csvFile in info.GetFiles("*.csv"))
{
languageSourceData ??= new LanguageSourceData();
- var csvData = File.ReadAllText(csvFile.FullName);
+ var csvData = File.ReadAllText(csvFile.FullName).Replace("\r\n", "\n");
languageSourceData.Import_CSV("", csvData, eSpreadsheetUpdateMode.AddNewTerms);
}
foreach (var i2CsvFile in info.GetFiles("*.i2csv"))
{
languageSourceData ??= new LanguageSourceData();
- var i2CsvData = File.ReadAllText(i2CsvFile.FullName);
+ var i2CsvData = File.ReadAllText(i2CsvFile.FullName).Replace("\r\n", "\n");
languageSourceData.Import_I2CSV("", i2CsvData, eSpreadsheetUpdateMode.AddNewTerms);
}
-
if (languageSourceData != null)
{
languageSourceData.OnMissingTranslation = LanguageSourceData.MissingTranslationAction.Fallback;
- SpaceWarpManager.Logger.LogInfo($"Loaded localizations from {folder}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"Loaded localizations from {folder}");
LocalizationManager.AddSource(languageSourceData);
}
else
{
- SpaceWarpManager.Logger.LogInfo($"No localizations found in {folder}");
+ SpaceWarpPlugin.Instance.SWLogger.LogInfo($"No localizations found in {folder}");
}
}
-}
+}
\ No newline at end of file
diff --git a/SpaceWarp/InternalUtilities/InternalExtensions.cs b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs
similarity index 84%
rename from SpaceWarp/InternalUtilities/InternalExtensions.cs
rename to SpaceWarp.Core/InternalUtilities/InternalExtensions.cs
index 5e0d1d35..46db62f8 100644
--- a/SpaceWarp/InternalUtilities/InternalExtensions.cs
+++ b/SpaceWarp.Core/InternalUtilities/InternalExtensions.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using UnityEngine;
namespace SpaceWarp.InternalUtilities;
diff --git a/SpaceWarp/InternalUtilities/PathHelpers.cs b/SpaceWarp.Core/InternalUtilities/PathHelpers.cs
similarity index 96%
rename from SpaceWarp/InternalUtilities/PathHelpers.cs
rename to SpaceWarp.Core/InternalUtilities/PathHelpers.cs
index 7e98f293..49eb5e80 100644
--- a/SpaceWarp/InternalUtilities/PathHelpers.cs
+++ b/SpaceWarp.Core/InternalUtilities/PathHelpers.cs
@@ -6,7 +6,7 @@ namespace SpaceWarp.InternalUtilities;
internal static class PathHelpers
{
///
- /// Creates a relative path from one file or folder to another.
+ /// Creates a relative path from one file or folder to another.
///
/// Contains the directory that defines the start of the relative path.
/// Contains the path that defines the endpoint of the relative path.
diff --git a/SpaceWarp.Core/Modules/ModuleManager.cs b/SpaceWarp.Core/Modules/ModuleManager.cs
new file mode 100644
index 00000000..29876ccd
--- /dev/null
+++ b/SpaceWarp.Core/Modules/ModuleManager.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using HarmonyLib;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+using ILogger = SpaceWarp.API.Logging.ILogger;
+
+namespace SpaceWarp.Modules;
+
+public static class ModuleManager
+{
+ internal static List AllSpaceWarpModules = new();
+ private static readonly ILogger ModuleManagerLogSource = new UnityLogSource("SpaceWarp.ModuleManager");
+
+ public static bool TryGetModule(string name, out SpaceWarpModule module)
+ {
+ module = AllSpaceWarpModules.FirstOrDefault(x => x.Name == name);
+ return module != null;
+ }
+
+ internal static void LoadAllModules()
+ {
+ var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
+ var modules = new DirectoryInfo(Path.Combine(location!.FullName, "modules"));
+ ModuleManagerLogSource.LogInfo($"Modules location: {modules}");
+ var configDirectory = new DirectoryInfo(Path.Combine(location!.FullName, "config"));
+ if (!Directory.Exists(configDirectory.FullName)) configDirectory.Create();
+ foreach (var module in modules.EnumerateFiles("*.dll", SearchOption.TopDirectoryOnly))
+ {
+ try
+ {
+ var assembly = Assembly.LoadFile(module.FullName);
+ // AllSpaceWarpModules.AddRange(assembly.GetExportedTypes()
+ // .Where(type => typeof(SpaceWarpModule).IsAssignableFrom(type)).Select(Activator.CreateInstance)
+ // .Cast());
+ foreach (var type in assembly.GetTypes().Where(type => typeof(SpaceWarpModule).IsAssignableFrom(type)))
+ {
+ ModuleManagerLogSource.LogInfo($"Loading module of type: {type}");
+ var mod = (SpaceWarpModule)Activator.CreateInstance(type);
+ ModuleManagerLogSource.LogInfo($"Module name: {mod.Name}");
+ AllSpaceWarpModules.Add(mod);
+ }
+
+ Harmony.CreateAndPatchAll(assembly);
+ }
+ catch (Exception e)
+ {
+ ModuleManagerLogSource.LogError($"Could not load module(s) from path {module} due to error: {e}");
+ }
+ }
+
+
+ TopologicallySortModules();
+ List toRemove = new();
+
+ foreach (var module in AllSpaceWarpModules)
+ {
+ try
+ {
+ module.ModuleLogger = new UnityLogSource(module.Name);
+ module.ModuleConfiguration = new JsonConfigFile(
+ Path.Combine(configDirectory.FullName, module.Name + ".cfg")
+ );
+ module.LoadModule();
+ }
+ catch (Exception e)
+ {
+ ModuleManagerLogSource.LogError(
+ $"Error loading module {module.Name} due to error: {e}.\n Removing {module.Name} from further initialization");
+ toRemove.Add(module);
+ }
+ }
+
+ foreach (var module in toRemove)
+ {
+ AllSpaceWarpModules.Remove(module);
+ }
+ }
+
+ private static void TopologicallySortModules()
+ {
+ var topologicalOrder = new List();
+ var clone = AllSpaceWarpModules.ToList();
+
+ var changed = true;
+ while (changed)
+ {
+ changed = false;
+ for (var i = clone.Count - 1; i >= 0; i--)
+ {
+ var module = clone[i];
+ var resolved = module.Prerequisites.All(prerequisite =>
+ AllSpaceWarpModules.All(x => x.Name != prerequisite) ||
+ topologicalOrder.Any(x => x.Name == prerequisite)
+ );
+ changed = changed || resolved;
+ if (!resolved) continue;
+ clone.RemoveAt(i);
+ topologicalOrder.Add(module);
+ }
+ }
+
+ AllSpaceWarpModules = topologicalOrder;
+ }
+
+ internal static void PreInitializeAllModules()
+ {
+ List toRemove = new();
+ foreach (var module in AllSpaceWarpModules)
+ {
+ try
+ {
+ ModuleManagerLogSource.LogInfo($"Pre-initializing: {module.Name}");
+ module.PreInitializeModule();
+ }
+ catch (Exception e)
+ {
+ ModuleManagerLogSource.LogError(
+ $"Error pre-initializing module {module.Name} due to error: {e}.\n Removing {module.Name} from further initialization");
+ toRemove.Add(module);
+ }
+ }
+
+ foreach (var module in toRemove)
+ {
+ AllSpaceWarpModules.Remove(module);
+ }
+ }
+
+ internal static void InitializeAllModules()
+ {
+ List toRemove = new();
+ foreach (var module in AllSpaceWarpModules)
+ {
+ try
+ {
+ ModuleManagerLogSource.LogInfo($"Initializing: {module.Name}");
+ module.InitializeModule();
+ }
+ catch (Exception e)
+ {
+ ModuleManagerLogSource.LogError(
+ $"Error initializing module {module.Name} due to error: {e}.\n Removing {module.Name} from further initialization");
+ toRemove.Add(module);
+ }
+ }
+
+ foreach (var module in toRemove)
+ {
+ AllSpaceWarpModules.Remove(module);
+ }
+ }
+
+ internal static void PostInitializeAllModules()
+ {
+ foreach (var module in AllSpaceWarpModules)
+ {
+ try
+ {
+ ModuleManagerLogSource.LogInfo($"Post-Initializing: {module.Name}");
+ module.PostInitializeModule();
+ }
+ catch (Exception e)
+ {
+ ModuleManagerLogSource.LogError(
+ $"Error post-initializing module {module.Name} due to error: {e}.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Modules/SpaceWarpModule.cs b/SpaceWarp.Core/Modules/SpaceWarpModule.cs
new file mode 100644
index 00000000..d2f89bd0
--- /dev/null
+++ b/SpaceWarp.Core/Modules/SpaceWarpModule.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+
+namespace SpaceWarp.Modules;
+
+public abstract class SpaceWarpModule
+{
+ public ILogger ModuleLogger;
+ public IConfigFile ModuleConfiguration;
+
+ public abstract string Name { get; }
+
+ public abstract void LoadModule();
+
+ public abstract void PreInitializeModule();
+
+ public abstract void InitializeModule();
+
+ public abstract void PostInitializeModule();
+
+ public virtual List Prerequisites => new();
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Patching/BootstrapPatch.cs b/SpaceWarp.Core/Patching/BootstrapPatch.cs
new file mode 100644
index 00000000..29a080fa
--- /dev/null
+++ b/SpaceWarp.Core/Patching/BootstrapPatch.cs
@@ -0,0 +1,137 @@
+using System.Collections.Generic;
+using HarmonyLib;
+using KSP.Game;
+using KSP.Game.Flow;
+using MonoMod.Cil;
+using SpaceWarp.API.Loading;
+using SpaceWarp.API.Mods;
+using SpaceWarp.Backend.Modding;
+using SpaceWarp.Patching.LoadingActions;
+
+namespace SpaceWarp.Patching;
+
+[HarmonyPatch]
+internal static class BootstrapPatch
+{
+ internal static bool ForceSpaceWarpLoadDueToError = false;
+ internal static SpaceWarpPluginDescriptor ErroredSWPluginDescriptor;
+
+ [HarmonyPatch(typeof(GameManager), nameof(GameManager.Awake))]
+ [HarmonyPrefix]
+ private static void GetAllMods()
+ {
+ PluginRegister.RegisterAllMods();
+ PluginList.ResolveDependenciesAndLoadOrder();
+ }
+
+ [HarmonyILManipulator]
+ [HarmonyPatch(typeof(GameManager), nameof(GameManager.StartBootstrap))]
+ private static void PatchInitializationsIL(ILContext ilContext, ILLabel endLabel)
+ {
+ ILCursor ilCursor = new(ilContext);
+
+ var flowProp = AccessTools.DeclaredProperty(typeof(GameManager), nameof(GameManager.LoadingFlow));
+
+ ilCursor.GotoNext(
+ MoveType.After,
+ instruction => instruction.MatchCallOrCallvirt(flowProp.SetMethod)
+ );
+
+ ilCursor.EmitDelegate(InjectBeforeGameLoadMethods);
+
+ ilCursor.GotoLabel(endLabel, MoveType.Before);
+ ilCursor.Index -= 1;
+ ilCursor.EmitDelegate(InjectAfterGameLoadMethods);
+ }
+
+ private static void InjectAfterGameLoadMethods()
+ {
+ var flow = GameManager.Instance.LoadingFlow;
+ var allPlugins = GetAllPlugins();
+
+ LatePreinitialize(allPlugins);
+ DoLoadingActions(allPlugins, flow);
+ foreach (var actionGenerator in Loading.GeneralLoadingActions)
+ {
+ flow.AddAction(actionGenerator());
+ }
+
+ foreach (var plugin in allPlugins)
+ {
+ flow.AddAction(new InitializeModAction(plugin));
+ }
+
+ foreach (var plugin in allPlugins)
+ {
+ flow.AddAction(new LoadLuaAction(plugin));
+ }
+
+ foreach (var plugin in allPlugins)
+ {
+ flow.AddAction(new PostInitializeModAction(plugin));
+ }
+ }
+
+ private static void DoLoadingActions(IList allPlugins, SequentialFlow flow)
+ {
+ foreach (var plugin in allPlugins)
+ {
+ flow.AddAction(new LoadAddressablesAction(plugin));
+ flow.AddAction(new LoadLocalizationAction(plugin));
+ DoOldStyleLoadingActions(flow, plugin);
+
+ foreach (var action in Loading.DescriptorLoadingActionGenerators)
+ {
+ flow.AddAction(action(plugin));
+ }
+ }
+ }
+
+ private static void DoOldStyleLoadingActions(SequentialFlow flow, SpaceWarpPluginDescriptor plugin)
+ {
+ if (plugin.Plugin is not BaseSpaceWarpPlugin baseSpaceWarpPlugin) return;
+ foreach (var action in Loading.LoadingActionGenerators)
+ {
+ flow.AddAction(action(baseSpaceWarpPlugin));
+ }
+ }
+
+ private static void LatePreinitialize(IList allPlugins)
+ {
+ foreach (var plugin in allPlugins.Where(plugin => plugin.LatePreInitialize))
+ {
+ GameManager.Instance.LoadingFlow.AddAction(new PreInitializeModAction(plugin));
+ }
+ }
+
+ private static IList GetAllPlugins()
+ {
+ IList allPlugins;
+ if (ForceSpaceWarpLoadDueToError)
+ {
+ var l = new List { ErroredSWPluginDescriptor };
+ l.AddRange(PluginList.AllEnabledAndActivePlugins);
+ allPlugins = l;
+ }
+ else
+ {
+ allPlugins = PluginList.AllPlugins.ToList();
+ }
+
+ return allPlugins;
+ }
+
+ private static void InjectBeforeGameLoadMethods()
+ {
+ if (ForceSpaceWarpLoadDueToError)
+ {
+ GameManager.Instance.LoadingFlow.AddAction(new PreInitializeModAction(ErroredSWPluginDescriptor));
+ }
+
+ foreach (var plugin in PluginList.AllEnabledAndActivePlugins)
+ {
+ if (plugin.Plugin != null && !plugin.LatePreInitialize)
+ GameManager.Instance.LoadingFlow.AddAction(new PreInitializeModAction(plugin));
+ }
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Core/Patching/CodeGenerationUtilities.cs b/SpaceWarp.Core/Patching/CodeGenerationUtilities.cs
new file mode 100644
index 00000000..60fcee96
--- /dev/null
+++ b/SpaceWarp.Core/Patching/CodeGenerationUtilities.cs
@@ -0,0 +1,29 @@
+using HarmonyLib;
+using System.Reflection.Emit;
+
+namespace SpaceWarp.Patching;
+
+internal static class CodeGenerationUtilities
+{
+ ///
+ /// Creates a CodeInstruction that pushed an integer to the stack.
+ ///
+ /// The integer to push
+ /// An new CodeInstruction to push i
+ internal static CodeInstruction PushIntInstruction(int i)
+ {
+ return i switch
+ {
+ 0 => new CodeInstruction(OpCodes.Ldc_I4_0),
+ 1 => new CodeInstruction(OpCodes.Ldc_I4_1),
+ 2 => new CodeInstruction(OpCodes.Ldc_I4_2),
+ 3 => new CodeInstruction(OpCodes.Ldc_I4_3),
+ 4 => new CodeInstruction(OpCodes.Ldc_I4_4),
+ 5 => new CodeInstruction(OpCodes.Ldc_I4_5),
+ 6 => new CodeInstruction(OpCodes.Ldc_I4_6),
+ 7 => new CodeInstruction(OpCodes.Ldc_I4_7),
+ 8 => new CodeInstruction(OpCodes.Ldc_I4_8),
+ _ => new CodeInstruction(OpCodes.Ldc_I4, i)
+ };
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp/Patching/ColorsPatch.cs b/SpaceWarp.Core/Patching/ColorsPatch.cs
similarity index 60%
rename from SpaceWarp/Patching/ColorsPatch.cs
rename to SpaceWarp.Core/Patching/ColorsPatch.cs
index 4acbb2c2..5fac92af 100644
--- a/SpaceWarp/Patching/ColorsPatch.cs
+++ b/SpaceWarp.Core/Patching/ColorsPatch.cs
@@ -1,70 +1,58 @@
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Logging;
+using Castle.Core.Internal;
using HarmonyLib;
using KSP.Game;
using KSP.Modules;
using KSP.OAB;
-using KSP.Sim;
-using KSP.Sim.impl;
using SpaceWarp.API.Assets;
using UnityEngine;
namespace SpaceWarp.Patching;
///
-/// This patch is meant to give modders a way to use the new colors system on KSP2.
-/// The patch will replace any renderer that has a "Parts Replace" or a "KSP2/Parts/Paintable" shader on it.
-/// It will copy all its values onto the new material, including the material name.
-/// Note: "Parts Replace" is obsolete and might be deleted on a later version.
-/// Patch created by LuxStice.
+/// This patch is meant to give modders a way to use the new colors system on KSP2.
+/// The patch will replace any renderer that has a "Parts Replace" or a "KSP2/Parts/Paintable" shader on it.
+/// It will copy all its values onto the new material, including the material name.
+/// Note: "Parts Replace" is obsolete and might be deleted in a later version.
+/// Patch created by LuxStice.
///
[HarmonyPatch]
internal class ColorsPatch
{
- private const string KSP2_OPAQUE_PATH = "KSP2/Scenery/Standard (Opaque)",
- KSP2_TRANSPARENT_PATH = "KSP2/Scenery/Standard (Transparent)",
- UNITY_STANDARD = "Standard";
+ private const string Ksp2OpaquePath = "KSP2/Scenery/Standard (Opaque)";
+ private const string Ksp2TransparentPath = "KSP2/Scenery/Standard (Transparent)";
+ private const string UnityStandard = "Standard";
[HarmonyPatch(typeof(ObjectAssemblyPartTracker), nameof(ObjectAssemblyPartTracker.OnPartPrefabLoaded))]
- internal static void Prefix(IObjectAssemblyAvailablePart obj, ref GameObject prefab)
+ public static void Prefix(IObjectAssemblyAvailablePart obj, ref GameObject prefab)
{
- ReplaceShader(ref prefab, Shader.Find(KSP2_OPAQUE_PATH), "Parts Replace", "KSP2/Parts/Paintable");
- }
-
- [HarmonyPostfix]
- [HarmonyPatch(typeof(SimulationObjectView), nameof(SimulationObjectView.InitializeView))]
- internal static void ApplyOnGameObjectFlight(GameObject instance, IUniverseView universe, SimulationObjectModel model)
- {
- ReplaceShader(ref instance, Shader.Find(KSP2_OPAQUE_PATH), "Parts Replace", "KSP2/Parts/Paintable");
- }
-
- internal static void ReplaceShader(ref GameObject target, Shader shaderToReplace,params string[] allowedShaders)
- {
- foreach (var renderer in target.GetComponentsInChildren(true))
+ foreach (var renderer in prefab.GetComponentsInChildren())
{
- string shaderName = renderer.material.shader.name;
- if (allowedShaders.Contains(shaderName))
+ var shaderName = renderer.material.shader.name;
+ if (shaderName is not ("Parts Replace" or "KSP2/Parts/Paintable")) continue;
+ Material material;
+ var mat = new Material(Shader.Find(Ksp2OpaquePath))
{
- var mat = new Material(shaderToReplace);
- mat.name = renderer.material.name;
- mat.CopyPropertiesFromMaterial(renderer.material);
- renderer.material = mat;
- }
+ name = (material = renderer.material).name
+ };
+ mat.CopyPropertiesFromMaterial(material);
+ renderer.material = mat;
}
}
//Everything below this point will be removed in the next patch
- private const int DIFFUSE = 0;
- private const int METTALLIC = 1;
- private const int BUMP = 2;
- private const int OCCLUSION = 3;
- private const int EMISSION = 4;
- private const int PAINT_MAP = 5;
-
-
- private const string displayName = "TTR"; //Taste the Rainbow - name by munix
+ private const int Diffuse = 0;
+ private const int Metallic = 1;
+ private const int Bump = 2;
+ private const int Occlusion = 3;
+ private const int Emission = 4;
+ private const int PaintMap = 5;
+
+ private const string DisplayName = "TTR"; //Taste the Rainbow - name by munix
private const bool LoadOnInit = true;
+
private static string[] _allParts;
private static Dictionary _partHash;
@@ -117,65 +105,63 @@ private static bool Init(MethodBase original)
Shader.PropertyToID("_PaintMaskGlossMap")
};
- _ksp2Opaque = Shader.Find(KSP2_OPAQUE_PATH);
- _ksp2Transparent = Shader.Find(KSP2_TRANSPARENT_PATH);
- _unityStandard = Shader.Find(UNITY_STANDARD);
+ _ksp2Opaque = Shader.Find(Ksp2OpaquePath);
+ _ksp2Transparent = Shader.Find(Ksp2TransparentPath);
+ _unityStandard = Shader.Find(UnityStandard);
- Logger = BepInEx.Logging.Logger.CreateLogSource(displayName);
+ Logger = BepInEx.Logging.Logger.CreateLogSource(DisplayName);
return true; // TODO: add config to enable/disable this patch, if disabled return false.
}
///
- /// Adds to internal parts list under
+ /// Adds to internal parts list under
/// allowing them to have the patch applied.
///
- /// guid of the mod that owns the parts.
+ /// guid of the mod that owns the parts.
///
/// Collection of partNames. Names that end in XS, S, M, L or XL will be counted as the same
/// part,
///
- internal static void DeclareParts(string modGUID, params string[] partNameList)
+ internal static void DeclareParts(string modGuid, params string[] partNameList)
{
- DeclareParts(modGUID, partNameList.ToList());
+ DeclareParts(modGuid, partNameList.ToList());
}
///
- /// Adds to internal parts list under
+ /// Adds to internal parts list under
/// allowing them to have the patch applied.
///
- /// guid of the mod that owns the parts.
+ /// guid of the mod that owns the parts.
///
/// Collection of partNames. Names that end in XS, S, M, L or XL will be counted as the same
/// part.
///
- internal static void DeclareParts(string modGUID, IEnumerable partNameList)
+ internal static void DeclareParts(string modGuid, IEnumerable partNameList)
{
- if (DeclaredParts.ContainsKey(modGUID))
+ if (DeclaredParts.ContainsKey(modGuid))
{
- LogWarning($"{modGUID} tried to declare their parts twice. Ignoring second call.");
+ LogWarning($"{modGuid} tried to declare their parts twice. Ignoring second call.");
return;
}
var nameList = partNameList as string[] ?? partNameList.ToArray();
if (!nameList.Any())
{
- LogWarning($"{modGUID} tried to declare no parts. Ignoring this call.");
+ LogWarning($"{modGuid} tried to declare no parts. Ignoring this call.");
return;
}
- DeclaredParts.Add(modGUID, nameList.ToArray());
+ DeclaredParts.Add(modGuid, nameList.ToArray());
}
internal static Texture[] GetTextures(string partName)
{
if (_partHash.ContainsKey(partName))
return _partHash[partName];
- else
- {
- LogError($"Requested textures from {partName} but part doesn't exist on declared parts!");
- return null;
- }
+
+ LogError($"Requested textures from {partName} but part doesn't exist on declared parts!");
+ return null;
}
private static void LoadDeclaredParts()
@@ -190,11 +176,11 @@ private static void LoadDeclaredParts()
if (LoadOnInit)
{
- foreach (var modGUID in DeclaredParts.Keys)
+ foreach (var modGuid in DeclaredParts.Keys)
{
- LoadTextures(modGUID);
+ LoadTextures(modGuid);
- allPartsTemp.AddRange(DeclaredParts[modGUID].Select(partName => TrimPartName(partName)));
+ allPartsTemp.AddRange(DeclaredParts[modGuid].Select(partName => TrimPartName(partName)));
}
}
@@ -233,11 +219,11 @@ private static void LoadTextures(string modGuid)
var count = 0; //already has diffuse
- if (AssetManager.TryGetAsset($"{pathWithoutSuffix}_{TextureSuffixes[DIFFUSE]}", out Texture2D dTex))
+ if (AssetManager.TryGetAsset($"{pathWithoutSuffix}_{TextureSuffixes[Diffuse]}", out Texture2D dTex))
{
- _partHash[trimmedPartName][DIFFUSE] = dTex;
+ _partHash[trimmedPartName][Diffuse] = dTex;
count++;
- LogMessage($"\t\t>({count}/6) Loaded {TextureNames[DIFFUSE]} texture");
+ LogMessage($"\t\t>({count}/6) Loaded {TextureNames[Diffuse]} texture");
}
else
{
@@ -247,19 +233,19 @@ private static void LoadTextures(string modGuid)
for (int i = 1; i < _propertyIds.Length; i++)
{
- if (AssetManager.TryGetAsset($"{pathWithoutSuffix}_{TextureSuffixes[i]}", out Texture2D Tex))
+ if (!AssetManager.TryGetAsset($"{pathWithoutSuffix}_{TextureSuffixes[i]}", out Texture2D tex)) continue;
+
+ count++;
+
+ if (i == Bump) //Converting texture to Bump texture
{
- count++;
-
- if (i == ColorsPatch.BUMP) //Converting texture to Bump texture
- {
- Texture2D normalTexture = new Texture2D(Tex.width, Tex.height, TextureFormat.RGBA32, false, true);
- Graphics.CopyTexture(Tex, normalTexture);
- Tex = normalTexture;
- }
- _partHash[trimmedPartName][i] = Tex;
- LogMessage($"\t\t>({count}/6) Loaded {TextureNames[i]} texture");
+ var normalTexture = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false, true);
+ Graphics.CopyTexture(tex, normalTexture);
+ tex = normalTexture;
}
+
+ _partHash[trimmedPartName][i] = tex;
+ LogMessage($"\t\t>({count}/6) Loaded {TextureNames[i]} texture");
}
if (count == 6)
@@ -317,30 +303,33 @@ internal static void Prefix()
internal static void Postfix(Module_Color __instance)
{
var partName = __instance.OABPart is not null ? __instance.OABPart.PartName : __instance.part.Name;
+ if (partName.IsNullOrEmpty()) return;
var trimmedPartName = TrimPartName(partName);
- if (DeclaredParts.Count > 0 && _allParts.Contains(trimmedPartName))
+ if (DeclaredParts.Count <= 0 || !_allParts.Contains(trimmedPartName)) return;
+
+ var mat = new Material(_ksp2Opaque)
{
- var mat = new Material(_ksp2Opaque);
- mat.name = __instance.GetComponentInChildren().material.name;
+ name = __instance.GetComponentInChildren().material.name
+ };
- foreach (var renderer in __instance.GetComponentsInChildren(true))
+ foreach (var renderer in __instance.GetComponentsInChildren(true))
+ {
+ if (renderer.material.shader.name != _unityStandard.name)
{
- if (renderer.material.shader.name != _unityStandard.name)
- {
- continue;
- }
+ continue;
+ }
- SetTexturesToMaterial(trimmedPartName, ref mat);
+ SetTexturesToMaterial(trimmedPartName, ref mat);
- renderer.material = mat;
+ renderer.material = mat;
- if (renderer.material.shader.name != _ksp2Opaque.name)
- {
- renderer.SetMaterial(mat); //Sometimes the material Set doesn't work, this seems to be more reliable.
- }
+ if (renderer.material.shader.name != _ksp2Opaque.name)
+ {
+ renderer.SetMaterial(mat); //Sometimes the material Set doesn't work, this seems to be more reliable.
}
- __instance.SomeColorUpdated();
}
+
+ __instance.SomeColorUpdated();
}
private static void LogMessage(object data)
@@ -357,4 +346,4 @@ private static void LogError(object data)
{
Logger.LogError($"{data}");
}
-}
+}
\ No newline at end of file
diff --git a/SpaceWarp/Patching/FixGetTypes.cs b/SpaceWarp.Core/Patching/FixGetTypes.cs
similarity index 80%
rename from SpaceWarp/Patching/FixGetTypes.cs
rename to SpaceWarp.Core/Patching/FixGetTypes.cs
index b10319d8..19ddcf90 100644
--- a/SpaceWarp/Patching/FixGetTypes.cs
+++ b/SpaceWarp.Core/Patching/FixGetTypes.cs
@@ -1,5 +1,6 @@
using System;
using System.Reflection;
+using BepInEx.Logging;
using HarmonyLib;
namespace SpaceWarp.Patching;
@@ -17,13 +18,15 @@ private static Exception GetTypesFix(Exception __exception, Assembly __instance,
return __exception;
}
- SpaceWarpManager.Logger.LogWarning(
+ var logger = new ManualLogSource("FixGetTypes");
+
+ logger.LogWarning(
$"Types failed to load from assembly {__instance.FullName} due to the reasons below, continuing anyway.");
- SpaceWarpManager.Logger.LogWarning($"Exception: {__exception}");
+ logger.LogWarning($"Exception: {__exception}");
foreach (var exception in reflectionTypeLoadException.LoaderExceptions)
{
- SpaceWarpManager.Logger.LogWarning(exception.ToString());
+ logger.LogWarning(exception.ToString());
}
__result = reflectionTypeLoadException.Types.Where(type => type != null).ToArray();
diff --git a/SpaceWarp/Patching/LoadingActions/AddressableAction.cs b/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
similarity index 90%
rename from SpaceWarp/Patching/LoadingActions/AddressableAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
index a9fdd2e3..47dac15c 100644
--- a/SpaceWarp/Patching/LoadingActions/AddressableAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/AddressableAction.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using JetBrains.Annotations;
using KSP.Game;
using KSP.Game.Flow;
using UnityEngine;
@@ -7,11 +8,13 @@
namespace SpaceWarp.Patching.LoadingActions;
+[Obsolete("This will be moved to SpaceWarp.API.Loading in 2.0.0")]
+[PublicAPI]
public class AddressableAction : FlowAction where T : UnityObject
{
private string Label;
private Action Action;
-
+
public AddressableAction(string name, string label, Action action) : base(name)
{
Label = label;
diff --git a/SpaceWarp/Patching/LoadingActions/DescriptorLoadingAction.cs b/SpaceWarp.Core/Patching/LoadingActions/DescriptorLoadingAction.cs
similarity index 64%
rename from SpaceWarp/Patching/LoadingActions/DescriptorLoadingAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/DescriptorLoadingAction.cs
index f1d5f237..b1ab21d0 100644
--- a/SpaceWarp/Patching/LoadingActions/DescriptorLoadingAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/DescriptorLoadingAction.cs
@@ -1,17 +1,22 @@
using System;
+using JetBrains.Annotations;
using KSP.Game.Flow;
using SpaceWarp.API.Mods;
namespace SpaceWarp.Patching.LoadingActions;
+[Obsolete("This will be moved to SpaceWarp.API.Loading in 2.0.0")]
+[PublicAPI]
public class DescriptorLoadingAction : FlowAction
{
private readonly Action _action;
private readonly SpaceWarpPluginDescriptor _plugin;
-
-
- public DescriptorLoadingAction(string actionName, Action action, SpaceWarpPluginDescriptor plugin) : base(
- $"{plugin.SWInfo.Name}: {actionName}")
+
+ public DescriptorLoadingAction(
+ string actionName,
+ Action action,
+ SpaceWarpPluginDescriptor plugin
+ ) : base($"{plugin.SWInfo.Name}: {actionName}")
{
_action = action;
_plugin = plugin;
@@ -21,7 +26,9 @@ public override void DoAction(Action resolve, Action reject)
{
try
{
- _action(_plugin);
+ if (_plugin.DoLoadingActions)
+ _action(_plugin);
+
resolve();
}
catch (Exception e)
@@ -29,7 +36,8 @@ public override void DoAction(Action resolve, Action reject)
if (_plugin.Plugin != null)
_plugin.Plugin.SWLogger.LogError(e.ToString());
else
- SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
+ SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
+
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/FunctionalLoadingActions.cs b/SpaceWarp.Core/Patching/LoadingActions/FunctionalLoadingActions.cs
similarity index 63%
rename from SpaceWarp/Patching/LoadingActions/FunctionalLoadingActions.cs
rename to SpaceWarp.Core/Patching/LoadingActions/FunctionalLoadingActions.cs
index 76283d90..ac4eec67 100644
--- a/SpaceWarp/Patching/LoadingActions/FunctionalLoadingActions.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/FunctionalLoadingActions.cs
@@ -1,20 +1,16 @@
-using AK.Wwise;
-using SpaceWarp.API.Sound;
-using SpaceWarp.Backend.Sound;
-using System;
+using System;
using System.Collections.Generic;
-using System.Drawing;
using System.IO;
-using System.Runtime.InteropServices;
-using Unity.Collections;
using UnityEngine;
-using UnityEngine.Assertions;
namespace SpaceWarp.Patching.LoadingActions;
internal static class FunctionalLoadingActions
{
- internal static List<(string name, UnityObject asset)> AssetBundleLoadingAction(string internalPath, string filename)
+ internal static List<(string name, UnityObject asset)> AssetBundleLoadingAction(
+ string internalPath,
+ string filename
+ )
{
var assetBundle = AssetBundle.LoadFromFile(filename);
if (assetBundle == null)
@@ -49,21 +45,6 @@ internal static class FunctionalLoadingActions
return assets;
}
- internal static List<(string name, UnityObject asset)> AssetSoundbankLoadingAction(string internalPath, string filename)
- {
- var fileData = File.ReadAllBytes(filename);
- //Since theres no UnityObject that relates to soundbanks it passes null, saving only the internalpath
- List<(string name, UnityObject asset)> assets = new() { ($"soundbanks/{internalPath}", null) };
-
- //Banks are identified under Bank.soundbanks with their internal path
- if (SoundAPI.LoadBank($"soundbanks/{internalPath}", fileData, out var bank))
- {
- return assets;
- }
- else
- throw new Exception(
- $"Failed to load soundbank {internalPath}");
- }
internal static List<(string name, UnityObject asset)> ImageLoadingAction(string internalPath, string filename)
{
diff --git a/SpaceWarp/Patching/LoadingActions/InitializeModAction.cs b/SpaceWarp.Core/Patching/LoadingActions/InitializeModAction.cs
similarity index 76%
rename from SpaceWarp/Patching/LoadingActions/InitializeModAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/InitializeModAction.cs
index fba6995c..04ad68eb 100644
--- a/SpaceWarp/Patching/LoadingActions/InitializeModAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/InitializeModAction.cs
@@ -18,12 +18,13 @@ public override void DoAction(Action resolve, Action reject)
{
try
{
- _plugin.Plugin.OnInitialized();
+ if (_plugin.DoLoadingActions)
+ _plugin.Plugin.OnInitialized();
resolve();
}
catch (Exception e)
{
- _plugin.Plugin.SWLogger.LogError(e.ToString());
+ (_plugin.Plugin ?? SpaceWarpPlugin.Instance).SWLogger.LogError(e.ToString());
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/LoadAddressablesAction.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadAddressablesAction.cs
similarity index 100%
rename from SpaceWarp/Patching/LoadingActions/LoadAddressablesAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/LoadAddressablesAction.cs
diff --git a/SpaceWarp/Patching/LoadingActions/LoadLocalizationAction.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadLocalizationAction.cs
similarity index 90%
rename from SpaceWarp/Patching/LoadingActions/LoadLocalizationAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/LoadLocalizationAction.cs
index 60add6f6..69646912 100644
--- a/SpaceWarp/Patching/LoadingActions/LoadLocalizationAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/LoadLocalizationAction.cs
@@ -10,8 +10,8 @@ internal sealed class LoadLocalizationAction : FlowAction
{
private readonly SpaceWarpPluginDescriptor _plugin;
- public LoadLocalizationAction(SpaceWarpPluginDescriptor plugin) : base(
- $"Loading localizations for plugin {plugin.SWInfo.Name}")
+ public LoadLocalizationAction(SpaceWarpPluginDescriptor plugin)
+ : base($"Loading localizations for plugin {plugin.SWInfo.Name}")
{
_plugin = plugin;
}
@@ -29,7 +29,7 @@ public override void DoAction(Action resolve, Action reject)
if (_plugin.Plugin != null)
_plugin.Plugin.SWLogger.LogError(e.ToString());
else
- SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
+ SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/LoadLuaAction.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadLuaAction.cs
similarity index 90%
rename from SpaceWarp/Patching/LoadingActions/LoadLuaAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/LoadLuaAction.cs
index 8f328b7e..0403bc07 100644
--- a/SpaceWarp/Patching/LoadingActions/LoadLuaAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/LoadLuaAction.cs
@@ -9,8 +9,8 @@ internal sealed class LoadLuaAction : FlowAction
{
private readonly SpaceWarpPluginDescriptor _plugin;
- public LoadLuaAction(SpaceWarpPluginDescriptor plugin) : base(
- $"Running lua scripts for {plugin.SWInfo.Name}")
+ public LoadLuaAction(SpaceWarpPluginDescriptor plugin)
+ : base($"Running lua scripts for {plugin.SWInfo.Name}")
{
_plugin = plugin;
}
@@ -34,9 +34,10 @@ public override void DoAction(Action resolve, Action reject)
if (_plugin.Plugin != null)
_plugin.Plugin.SWLogger.LogError(e.ToString());
else
- SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
+ SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
}
}
+
resolve();
}
catch (Exception e)
@@ -44,7 +45,7 @@ public override void DoAction(Action resolve, Action reject)
if (_plugin.Plugin != null)
_plugin.Plugin.SWLogger.LogError(e.ToString());
else
- SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
+ SpaceWarpPlugin.Logger.LogError(_plugin.SWInfo.Name + ": " + e);
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs b/SpaceWarp.Core/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs
similarity index 82%
rename from SpaceWarp/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs
index b6c0ec9b..dc0e075b 100644
--- a/SpaceWarp/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/LoadingAddressablesLocalizationsAction.cs
@@ -18,8 +18,10 @@ public override void DoAction(Action resolve, Action reject)
{
try
{
- GameManager.Instance.Assets.LoadByLabel("language_source",
- OnLanguageSourceAssetLoaded, delegate(IList languageAssetLocations)
+ GameManager.Instance.Assets.LoadByLabel(
+ "language_source",
+ OnLanguageSourceAssetLoaded,
+ delegate(IList languageAssetLocations)
{
if (languageAssetLocations != null)
{
@@ -27,7 +29,8 @@ public override void DoAction(Action resolve, Action reject)
}
resolve();
- });
+ }
+ );
}
catch (Exception e)
{
diff --git a/SpaceWarp/Patching/LoadingActions/ModLoadingAction.cs b/SpaceWarp.Core/Patching/LoadingActions/ModLoadingAction.cs
similarity index 66%
rename from SpaceWarp/Patching/LoadingActions/ModLoadingAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/ModLoadingAction.cs
index a38ac489..e595db71 100644
--- a/SpaceWarp/Patching/LoadingActions/ModLoadingAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/ModLoadingAction.cs
@@ -1,17 +1,23 @@
using System;
+using JetBrains.Annotations;
using KSP.Game.Flow;
using SpaceWarp.API.Mods;
namespace SpaceWarp.Patching.LoadingActions;
+[Obsolete("This will be moved to SpaceWarp.API.Loading in 2.0.0")]
+[PublicAPI]
public class ModLoadingAction : FlowAction
{
private Action Action;
private BaseSpaceWarpPlugin Plugin;
-
-
- public ModLoadingAction(string actionName, Action action, BaseSpaceWarpPlugin plugin) : base(
- $"{plugin.SpaceWarpMetadata.Name}:{actionName}")
+
+
+ public ModLoadingAction(
+ string actionName,
+ Action action,
+ BaseSpaceWarpPlugin plugin
+ ) : base($"{plugin.SpaceWarpMetadata.Name}:{actionName}")
{
Action = action;
Plugin = plugin;
diff --git a/SpaceWarp/Patching/LoadingActions/PostInitializeModAction.cs b/SpaceWarp.Core/Patching/LoadingActions/PostInitializeModAction.cs
similarity index 68%
rename from SpaceWarp/Patching/LoadingActions/PostInitializeModAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/PostInitializeModAction.cs
index 17eb4ef9..e8c5894d 100644
--- a/SpaceWarp/Patching/LoadingActions/PostInitializeModAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/PostInitializeModAction.cs
@@ -8,8 +8,8 @@ internal sealed class PostInitializeModAction : FlowAction
{
private readonly SpaceWarpPluginDescriptor _plugin;
- public PostInitializeModAction(SpaceWarpPluginDescriptor plugin) : base(
- $"Post-initialization for plugin {plugin.Name}")
+ public PostInitializeModAction(SpaceWarpPluginDescriptor plugin)
+ : base($"Post-initialization for plugin {plugin.Name}")
{
_plugin = plugin;
}
@@ -18,12 +18,13 @@ public override void DoAction(Action resolve, Action reject)
{
try
{
- _plugin.Plugin.OnPostInitialized();
+ if (_plugin.DoLoadingActions)
+ _plugin.Plugin.OnPostInitialized();
resolve();
}
catch (Exception e)
{
- _plugin.Plugin.SWLogger.LogError(e.ToString());
+ (_plugin.Plugin ?? SpaceWarpPlugin.Instance).SWLogger.LogError(e.ToString());
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/PreInitializeModAction.cs b/SpaceWarp.Core/Patching/LoadingActions/PreInitializeModAction.cs
similarity index 50%
rename from SpaceWarp/Patching/LoadingActions/PreInitializeModAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/PreInitializeModAction.cs
index ef25af9b..a8aac19c 100644
--- a/SpaceWarp/Patching/LoadingActions/PreInitializeModAction.cs
+++ b/SpaceWarp.Core/Patching/LoadingActions/PreInitializeModAction.cs
@@ -8,22 +8,31 @@ internal sealed class PreInitializeModAction : FlowAction
{
private readonly SpaceWarpPluginDescriptor _plugin;
- public PreInitializeModAction(SpaceWarpPluginDescriptor plugin) : base(
- $"Pre-initialization for plugin {plugin.Name}")
+ public PreInitializeModAction(SpaceWarpPluginDescriptor plugin)
+ : base($"Pre-initialization for plugin {plugin.Name}")
{
_plugin = plugin;
}
public override void DoAction(Action resolve, Action reject)
{
+ SpaceWarpPlugin.Logger.LogInfo($"Pre-initializing: {_plugin.Name}?");
try
{
- _plugin.Plugin?.OnPreInitialized();
+ if (_plugin.DoLoadingActions)
+ {
+ SpaceWarpPlugin.Logger.LogInfo($"YES! {_plugin.Plugin}");
+ _plugin.Plugin.OnPreInitialized();
+ }
+ else
+ {
+ SpaceWarpPlugin.Logger.LogInfo("NO!!");
+ }
resolve();
}
catch (Exception e)
{
- _plugin.Plugin?.SWLogger.LogError(e.ToString());
+ (_plugin.Plugin ?? SpaceWarpPlugin.Instance).SWLogger.LogError(e.ToString());
reject(null);
}
}
diff --git a/SpaceWarp/Patching/LoadingActions/ResolvingPatchOrderAction.cs b/SpaceWarp.Core/Patching/LoadingActions/ResolvingPatchOrderAction.cs
similarity index 100%
rename from SpaceWarp/Patching/LoadingActions/ResolvingPatchOrderAction.cs
rename to SpaceWarp.Core/Patching/LoadingActions/ResolvingPatchOrderAction.cs
diff --git a/SpaceWarp.Core/Patching/ModLoaderPatch.cs b/SpaceWarp.Core/Patching/ModLoaderPatch.cs
new file mode 100644
index 00000000..98d10a64
--- /dev/null
+++ b/SpaceWarp.Core/Patching/ModLoaderPatch.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using HarmonyLib;
+using KSP.Modding;
+using Newtonsoft.Json;
+using SpaceWarp.API.Configuration;
+using SpaceWarp.API.Logging;
+using SpaceWarp.API.Mods;
+using SpaceWarp.API.Mods.JSON;
+using SpaceWarp.Backend.Modding;
+using SpaceWarp.InternalUtilities;
+using SpaceWarpPatcher;
+using UnityEngine;
+using File = System.IO.File;
+
+namespace SpaceWarp.Patching;
+
+[HarmonyPatch(typeof(KSP2Mod))]
+public static class ModLoaderPatch
+{
+ private static ModInfo KSPToSwinfo(KSP2Mod mod)
+ {
+ var newInfo = new ModInfo
+ {
+ Spec = SpecVersion.V1_3,
+ ModID = mod.ModName,
+ Name = mod.ModName,
+ Author = mod.ModAuthor,
+ Description = mod.ModDescription,
+ Source = "",
+ Version = mod.ModVersion.ToString(),
+ Dependencies = new List(),
+ SupportedKsp2Versions = new SupportedVersionsInfo
+ {
+ Min = "*",
+ Max = "*"
+ },
+ VersionCheck = null,
+ VersionCheckType = VersionCheckType.SwInfo
+ };
+ return newInfo;
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(KSP2Mod.Load))]
+ private static bool LoadPre(KSP2Mod __instance, ref bool __result, out bool __state)
+ {
+ SpaceWarpPlugin.Logger.LogInfo($"KSP2Mod.Load (Pre): {__instance.ModName}");
+ var path = __instance.ModRootPath;
+ var info = File.Exists(Path.Combine(path, "swinfo.json"))
+ ? JsonConvert.DeserializeObject(File.ReadAllText(path))
+ : KSPToSwinfo(__instance);
+ var disabled = ChainloaderPatch.DisabledPluginGuids.Contains(info.ModID);
+ __state = disabled;
+ return !disabled;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(KSP2Mod.Load))]
+ private static void LoadPost(KSP2Mod __instance, ref bool __result, ref bool __state)
+ {
+ SpaceWarpPlugin.Logger.LogInfo($"KSP2Mod.Load (Post): {__instance.ModName}");
+ if (__state) return;
+ var path = __instance.ModRootPath;
+ var info = File.Exists(Path.Combine(path, "swinfo.json"))
+ ? JsonConvert.DeserializeObject(File.ReadAllText(path))
+ : KSPToSwinfo(__instance);
+ var descriptor =
+ PluginList.AllPlugins.FirstOrDefault(x =>
+ string.Equals(x.Guid, info.ModID, StringComparison.InvariantCultureIgnoreCase));
+ if (descriptor == null) return;
+ var go = new GameObject(__instance.ModName);
+ go.Persist();
+ var addAdapter = true;
+ if (__instance.EntryPoint != null)
+ {
+ if (LoadCodeBasedMod(__instance, ref __result, go, ref descriptor.Plugin, ref addAdapter,
+ ref descriptor.ConfigFile, ref descriptor.DoLoadingActions)) return;
+ }
+ else
+ {
+ __instance.modType = KSP2ModType.ContentOnly;
+ }
+
+ SpaceWarpPlugin.Logger.LogInfo($"KSP2Mod.Load (Loaded stuff): {__instance.ModName}");
+
+ if (addAdapter)
+ {
+ var adapter = go.AddComponent();
+ adapter.AdaptedMod = __instance;
+ descriptor.Plugin = adapter;
+ }
+
+ descriptor.Plugin!.SWMetadata = descriptor;
+
+ go.SetActive(true);
+ // var descriptor = new SpaceWarpPluginDescriptor(swMod, info.ModID, info.Name, info, new DirectoryInfo(path), configFile);
+ }
+
+ private static bool LoadCodeBasedMod(
+ KSP2Mod __instance,
+ ref bool __result,
+ GameObject go,
+ ref ISpaceWarpMod swMod,
+ ref bool addAdapter,
+ ref IConfigFile configFile,
+ ref bool isSWMod
+ )
+ {
+ // Lets take a simple guess at what needs to be done.
+ if (File.Exists(Path.Combine(__instance.ModRootPath, __instance.EntryPoint)))
+ {
+ if (__instance.EntryPoint.EndsWith(".dll"))
+ {
+ if (LoadModWithDLLEntryPoint(__instance, ref __result, go, ref swMod, ref addAdapter, ref configFile,
+ ref isSWMod)) return true;
+ }
+ else if (__instance.EntryPoint.EndsWith(".lua"))
+ {
+ if (LoadModWithLuaEntryPoint(__instance, ref __result)) return true;
+ }
+ }
+ else
+ {
+ __instance.modType = KSP2ModType.Error;
+ __result = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool LoadModWithLuaEntryPoint(KSP2Mod __instance, ref bool __result)
+ {
+ try
+ {
+ __instance.modCore = new KSP2LuaModCore(
+ __instance.APIVersion,
+ __instance.ModName,
+ __instance.EntryPoint,
+ __instance.ModRootPath
+ );
+ __instance.modType = KSP2ModType.Lua;
+ __instance.currentState = KSP2ModState.Active;
+ }
+ catch (Exception e)
+ {
+ SpaceWarpPlugin.Logger.LogError(e);
+ __instance.modType = KSP2ModType.Error;
+ __result = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool LoadModWithDLLEntryPoint(
+ KSP2Mod __instance,
+ ref bool __result,
+ GameObject go,
+ ref ISpaceWarpMod swMod,
+ ref bool addAdapter,
+ ref IConfigFile configFile,
+ ref bool isSWMod
+ )
+ {
+ try
+ {
+ var asm = Assembly.LoadFile(Path.Combine(__instance.ModRootPath, __instance.EntryPoint));
+ __instance.modType = KSP2ModType.CSharp;
+ foreach (var type in asm.GetTypes())
+ {
+ if (!typeof(Mod).IsAssignableFrom(type) || type.IsAbstract) continue;
+ var comp = go.AddComponent(type);
+ if (comp is not BaseKspLoaderSpaceWarpMod baseKspLoaderSpaceWarpMod) continue;
+
+ SpaceWarpPlugin.Logger.LogInfo($"Loading mod: {comp}");
+ isSWMod = true;
+ baseKspLoaderSpaceWarpMod.SWLogger = new UnityLogSource(__instance.ModName);
+ // baseKspLoaderSpaceWarpMod.modFolder = __instance.ModRootPath;
+ configFile = baseKspLoaderSpaceWarpMod.SWConfiguration = new JsonConfigFile(
+ Path.Combine(__instance.ModRootPath, "config.cfg")
+ );
+ swMod = baseKspLoaderSpaceWarpMod;
+ addAdapter = false;
+ }
+
+ __instance.currentState = KSP2ModState.Active;
+ }
+ catch (Exception e)
+ {
+ SpaceWarpPlugin.Logger.LogError(e);
+ __instance.modType = KSP2ModType.Error;
+ __result = false;
+ return true;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp/Patching/SequentialFlowLoadersPatcher.cs b/SpaceWarp.Core/Patching/SequentialFlowLoadersPatcher.cs
similarity index 50%
rename from SpaceWarp/Patching/SequentialFlowLoadersPatcher.cs
rename to SpaceWarp.Core/Patching/SequentialFlowLoadersPatcher.cs
index 319eed0c..e84a7067 100644
--- a/SpaceWarp/Patching/SequentialFlowLoadersPatcher.cs
+++ b/SpaceWarp.Core/Patching/SequentialFlowLoadersPatcher.cs
@@ -2,83 +2,85 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
-using System.Text;
-using System.Threading.Tasks;
using BepInEx.Logging;
using HarmonyLib;
using KSP.Game;
using KSP.Game.Flow;
-using Newtonsoft.Json.Linq;
-using static MoonSharp.Interpreter.Debugging.DebuggerAction;
namespace SpaceWarp.Patching;
[HarmonyPatch]
internal static class SequentialFlowLoadersPatcher
{
- internal const int FLOW_METHOD_STARTGAME = 0;
- internal const int FLOW_METHOD_PRIVATELOADCOMMON = 1;
- internal const int FLOW_METHOD_PRIVATESAVECOMMON = 2;
+ internal const int FlowMethodStartgame = 0;
+ internal const int FlowMethodPrivateloadcommon = 1;
+ internal const int FlowMethodPrivatesavecommon = 2;
- static SequentialFlowAdditions[] sequentialFlowAdditions = new SequentialFlowAdditions[]
+ private static SequentialFlowAdditions[] _sequentialFlowAdditions =
{
new(typeof(GameManager).GetMethod("StartGame")), // Must be index FLOW_METHOD_STARTGAME
- new(AccessTools.Method("KSP.Game.SaveLoadManager:PrivateLoadCommon")), // Must be index FLOW_METHOD_PRIVATELOADCOMMON
- new(AccessTools.Method("KSP.Game.SaveLoadManager:PrivateSaveCommon")), // Must be index FLOW_METHOD_PRIVATESAVECOMMON
+ new(AccessTools.Method(
+ "KSP.Game.SaveLoadManager:PrivateLoadCommon")), // Must be index FLOW_METHOD_PRIVATELOADCOMMON
+ new(AccessTools.Method(
+ "KSP.Game.SaveLoadManager:PrivateSaveCommon")), // Must be index FLOW_METHOD_PRIVATESAVECOMMON
};
internal static void AddConstructor(string after, Type flowAction, int methodIndex)
{
- sequentialFlowAdditions[methodIndex].AddConstructor(after, flowAction);
+ _sequentialFlowAdditions[methodIndex].AddConstructor(after, flowAction);
}
internal static void AddFlowAction(string after, FlowAction flowAction, int methodIndex)
{
- sequentialFlowAdditions[methodIndex].AddAction(after, flowAction);
+ _sequentialFlowAdditions[methodIndex].AddAction(after, flowAction);
}
public static SequentialFlow Apply(SequentialFlow flow, object[] methodArguments, int methodIndex)
{
- sequentialFlowAdditions[methodIndex].ApplyTo(flow, methodArguments);
+ _sequentialFlowAdditions[methodIndex].ApplyTo(flow, methodArguments);
return flow;
}
- static IEnumerable TranspileSequentialFlowBuilderMethod(IEnumerable instructions, int methodIndex)
+ private static IEnumerable TranspileSequentialFlowBuilderMethod(
+ IEnumerable instructions,
+ int methodIndex
+ )
{
- var StartFlow = typeof(SequentialFlow).GetMethod("StartFlow");
+ var startFlow = typeof(SequentialFlow).GetMethod("StartFlow");
- foreach (CodeInstruction instruction in instructions)
+ foreach (var instruction in instructions)
{
- if (instruction.opcode == OpCodes.Callvirt && StartFlow == (MethodInfo)instruction.operand)
+ if (instruction.opcode == OpCodes.Callvirt && startFlow == (MethodInfo)instruction.operand)
{
// Call to StartFlow found!
// Before it occurs, insert any extra flow actions.
// Get list of relevant arguments to pass to FlowAction constructors.
// `parameterCount` has a `+ 1` because `GetParameters()` doesn't include the instance parameter (this).
- var parameters = sequentialFlowAdditions[methodIndex].method.GetParameters().Where(parameter => !parameter.ParameterType.IsValueType).ToArray();
+ var parameters = _sequentialFlowAdditions[methodIndex].method.GetParameters()
+ .Where(parameter => !parameter.ParameterType.IsValueType).ToArray();
var parameterCount = parameters.Length + 1;
// Creation of argument `methodArguments` to `Apply()`:
// Construct an array to hold the arguments.
yield return CodeGenerationUtilities.PushIntInstruction(parameterCount);
- yield return new(OpCodes.Newarr, typeof(object));
+ yield return new CodeInstruction(OpCodes.Newarr, typeof(object));
// Assign `this` to element 0.
- yield return new(OpCodes.Dup);
- yield return new(OpCodes.Ldc_I4_0);
- yield return new(OpCodes.Ldarg_0);
- yield return new(OpCodes.Stelem_Ref);
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Ldc_I4_0);
+ yield return new CodeInstruction(OpCodes.Ldarg_0);
+ yield return new CodeInstruction(OpCodes.Stelem_Ref);
// Assign the other arguments.
for (int i = 1; i < parameterCount; i++)
{
var parameter = parameters[i - 1];
- yield return new(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Dup);
yield return CodeGenerationUtilities.PushIntInstruction(i);
- yield return new(OpCodes.Ldarg_S, (byte)(parameter.Position + 1));
- yield return new(OpCodes.Stelem_Ref);
+ yield return new CodeInstruction(OpCodes.Ldarg_S, (byte)(parameter.Position + 1));
+ yield return new CodeInstruction(OpCodes.Stelem_Ref);
}
// Creation of argument `methodIndex` to `Apply()`:
@@ -86,7 +88,10 @@ static IEnumerable TranspileSequentialFlowBuilderMethod(IEnumer
// Call `Apply()`.
// `flow` is already on the stack and does not need to be created.
- yield return new(OpCodes.Call, typeof(SequentialFlowLoadersPatcher).GetMethod("Apply"));
+ yield return new CodeInstruction(
+ OpCodes.Call,
+ typeof(SequentialFlowLoadersPatcher).GetMethod("Apply")
+ );
}
// Copy everything else.
@@ -99,73 +104,80 @@ static IEnumerable TranspileSequentialFlowBuilderMethod(IEnumer
[HarmonyPrefix]
public static void PrefixGameManagerStartGame(GameManager __instance)
{
- sequentialFlowAdditions[FLOW_METHOD_STARTGAME].ApplyTo(__instance.LoadingFlow, new object[] { __instance });
+ _sequentialFlowAdditions[FlowMethodStartgame].ApplyTo(
+ __instance.LoadingFlow,
+ new object[] { __instance }
+ );
}
[HarmonyPatch(typeof(SaveLoadManager))]
[HarmonyPatch("PrivateLoadCommon")]
[HarmonyTranspiler]
- public static IEnumerable TranspileSaveLoadManagerPrivateLoadCommon(IEnumerable instructions)
+ public static IEnumerable TranspileSaveLoadManagerPrivateLoadCommon(
+ IEnumerable instructions)
{
- return TranspileSequentialFlowBuilderMethod(instructions, FLOW_METHOD_PRIVATELOADCOMMON);
+ return TranspileSequentialFlowBuilderMethod(instructions, FlowMethodPrivateloadcommon);
}
[HarmonyPatch(typeof(SaveLoadManager))]
[HarmonyPatch("PrivateSaveCommon")]
[HarmonyTranspiler]
- public static IEnumerable TranspileSaveLoadManagerPrivateSaveCommon(IEnumerable instructions)
+ public static IEnumerable TranspileSaveLoadManagerPrivateSaveCommon(
+ IEnumerable instructions)
{
- return TranspileSequentialFlowBuilderMethod(instructions, FLOW_METHOD_PRIVATESAVECOMMON);
+ return TranspileSequentialFlowBuilderMethod(instructions, FlowMethodPrivatesavecommon);
}
private class SequentialFlowAdditions
{
- readonly HashSet availableTypes;
- readonly List> insertAfter = new();
- readonly internal MethodInfo method;
+ private readonly HashSet availableTypes;
+ private readonly List> insertAfter = new();
+ internal readonly MethodInfo method;
internal SequentialFlowAdditions(MethodInfo method)
{
- availableTypes = new(method.GetParameters().Select(parameter => parameter.ParameterType).Where(type => !type.IsValueType)) { method.DeclaringType };
+ availableTypes =
+ new HashSet(method.GetParameters().Select(parameter => parameter.ParameterType)
+ .Where(type => !type.IsValueType)) { method.DeclaringType };
this.method = method;
}
internal void AddConstructor(string after, Type flowAction)
{
// Determine the correct constructor to use.
- var constructor = flowAction.GetConstructors().OrderByDescending(constructor => constructor.GetParameters().Length).Where(constructor =>
- {
- HashSet seen = new();
-
- foreach (var parameter in constructor.GetParameters())
+ var constructor = flowAction.GetConstructors()
+ .OrderByDescending(constructor => constructor.GetParameters().Length).Where(constructor =>
{
- if (!availableTypes.Contains(parameter.ParameterType))
- return false;
+ HashSet seen = new();
- if (!seen.Add(parameter.GetType()))
- return false;
- }
+ foreach (var parameter in constructor.GetParameters())
+ {
+ if (!availableTypes.Contains(parameter.ParameterType))
+ return false;
- return true;
- }).FirstOrDefault() ?? throw new InvalidOperationException($"Flow action type {flowAction.Name} does not have a public constructor that has parameters compatible with {method.DeclaringType.Name}.{method.Name}");
+ if (!seen.Add(parameter.GetType()))
+ return false;
+ }
+
+ return true;
+ }).FirstOrDefault() ?? throw new InvalidOperationException(
+ $"Flow action type {flowAction.Name} does not have a public constructor that has parameters compatible with {method.DeclaringType.Name}.{method.Name}");
- insertAfter.Add(new(after, constructor));
+ insertAfter.Add(new KeyValuePair(after, constructor));
}
internal void AddAction(string after, FlowAction action)
{
- insertAfter.Add(new(after, action));
+ insertAfter.Add(new KeyValuePair(after, action));
}
- static FlowAction Construct(ConstructorInfo constructor, object[] methodArguments)
+ private static FlowAction Construct(ConstructorInfo constructor, object[] methodArguments)
{
// Figure out which type of object goes where in the arguments list.
- Dictionary parameterIndices = new();
-
- foreach (var parameter in constructor.GetParameters())
- {
- parameterIndices.Add(parameter.ParameterType, parameter.Position);
- }
+ var parameterIndices = constructor.GetParameters().ToDictionary(
+ parameter => parameter.ParameterType,
+ parameter => parameter.Position
+ );
// Create and populate the arguments list
var arguments = new object[constructor.GetParameters().Length];
@@ -181,7 +193,7 @@ static FlowAction Construct(ConstructorInfo constructor, object[] methodArgument
return (FlowAction)constructor.Invoke(arguments);
}
- void AddActionsAfter(string name, List actions, bool[] added, object[] methodArguments)
+ private void AddActionsAfter(string name, List actions, bool[] added, object[] methodArguments)
{
for (int i = 0; i < added.Length; i++)
{
@@ -189,28 +201,22 @@ void AddActionsAfter(string name, List actions, bool[] added, object
if (added[i])
continue;
- if (insertAfter[i].Key == name)
- {
- added[i] = true;
+ if (insertAfter[i].Key != name) continue;
- FlowAction action = null;
+ added[i] = true;
- if (insertAfter[i].Value is ConstructorInfo constructor)
- {
- action = Construct(constructor, methodArguments);
- }
-
- else if (insertAfter[i].Value is FlowAction flowAction)
- {
- action = flowAction;
- }
+ var action = insertAfter[i].Value switch
+ {
+ ConstructorInfo constructor => Construct(constructor, methodArguments),
+ FlowAction flowAction => flowAction,
+ _ => null
+ };
- actions.Add(action);
+ actions.Add(action);
- // There could be actions set to run after the newly inserted action,
- // so this needs to be called again.
- AddActionsAfter(action.Name, actions, added, methodArguments);
- }
+ // There could be actions set to run after the newly inserted action,
+ // so this needs to be called again.
+ AddActionsAfter(action.Name, actions, added, methodArguments);
}
}
@@ -255,22 +261,22 @@ internal void ApplyTo(SequentialFlow flow, object[] methodArguments)
// Warn about any actions that were not used.
for (int i = 0; i < added.Length; i++)
{
- if (!added[i])
- {
- if (insertAfter[i].Value is ConstructorInfo constructor)
- {
- var actionType = constructor.DeclaringType;
- var logger = Logger.CreateLogSource($"{actionType.Assembly.GetName().Name}/{actionType.Name}");
- logger.LogWarning($"Flow action {actionType.Name} was set to be inserted after \"{insertAfter[i].Key}\" in {method.Name}, however that action does not exist in that flow");
- }
+ if (added[i]) continue;
- else if (insertAfter[i].Value is FlowAction action)
- {
- var logger = Logger.CreateLogSource("SequentialFlow");
- logger.LogWarning($"Flow action \"{action.Name}\" was set to be inserted after \"{insertAfter[i].Key}\" in {method.Name}, however that action does not exist in that flow");
- }
+ if (insertAfter[i].Value is ConstructorInfo constructor)
+ {
+ var actionType = constructor.DeclaringType;
+ var logger = Logger.CreateLogSource($"{actionType.Assembly.GetName().Name}/{actionType.Name}");
+ logger.LogWarning(
+ $"Flow action {actionType.Name} was set to be inserted after \"{insertAfter[i].Key}\" in {method.Name}, however that action does not exist in that flow");
+ }
+ else if (insertAfter[i].Value is FlowAction action)
+ {
+ var logger = Logger.CreateLogSource("SequentialFlow");
+ logger.LogWarning(
+ $"Flow action \"{action.Name}\" was set to be inserted after \"{insertAfter[i].Key}\" in {method.Name}, however that action does not exist in that flow");
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/SpaceWarp/Patching/SettingsManagerPatcher.cs b/SpaceWarp.Core/Patching/SettingsManagerPatcher.cs
similarity index 100%
rename from SpaceWarp/Patching/SettingsManagerPatcher.cs
rename to SpaceWarp.Core/Patching/SettingsManagerPatcher.cs
diff --git a/SpaceWarp.Core/SpaceWarp.Core.csproj b/SpaceWarp.Core/SpaceWarp.Core.csproj
new file mode 100644
index 00000000..591e9ff3
--- /dev/null
+++ b/SpaceWarp.Core/SpaceWarp.Core.csproj
@@ -0,0 +1,43 @@
+
+
+
+
+ $(SpaceWarpVersion)
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ true
+ SpaceWarpPatcher.dll
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SpaceWarp.Core/SpaceWarpPlugin.cs b/SpaceWarp.Core/SpaceWarpPlugin.cs
new file mode 100644
index 00000000..92abfd40
--- /dev/null
+++ b/SpaceWarp.Core/SpaceWarpPlugin.cs
@@ -0,0 +1,115 @@
+global using UnityObject = UnityEngine.Object;
+global using System.Linq;
+using System;
+using System.IO;
+using System.Reflection;
+using BepInEx;
+using BepInEx.Logging;
+using HarmonyLib;
+using I2.Loc;
+using JetBrains.Annotations;
+using KSP.IO;
+using KSP.ScriptInterop.impl.moonsharp;
+using MoonSharp.Interpreter;
+using MoonSharp.Interpreter.Interop;
+using SpaceWarp.API.Loading;
+using SpaceWarp.API.Lua;
+using SpaceWarp.API.Mods;
+using SpaceWarp.InternalUtilities;
+using UitkForKsp2;
+using SpaceWarp.Modules;
+using SpaceWarp.Patching.LoadingActions;
+
+namespace SpaceWarp;
+
+[BepInDependency("com.bepis.bepinex.configurationmanager", "17.1")]
+[BepInDependency(UitkForKsp2Plugin.ModGuid, UitkForKsp2Plugin.ModVer)]
+[BepInIncompatibility("com.shadow.quantum")]
+[BepInPlugin(ModGuid, ModName, ModVer)]
+public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin
+{
+ public static SpaceWarpPlugin Instance;
+
+ [PublicAPI] public const string ModGuid = "com.github.x606.spacewarp";
+ [PublicAPI] public const string ModName = "Space Warp";
+ [PublicAPI] public const string ModVer = MyPluginInfo.PLUGIN_VERSION; // TODO: Don't hard code this, but I don't know much msbuild stuff so @munix wil have to do that,
+ // and @munix is really lazy to do it right now but he definitely will at some point :P
+
+ internal ScriptEnvironment GlobalLuaState;
+
+ internal new static ManualLogSource Logger;
+
+ public SpaceWarpPlugin()
+ {
+ Assembly.LoadFile(
+ $"{new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName}\\SpaceWarp.dll");
+ Logger = base.Logger;
+ Instance = this;
+ }
+
+ private static void OnLanguageSourceAssetLoaded(LanguageSourceAsset asset)
+ {
+ if (!asset || LocalizationManager.Sources.Contains(asset.mSource))
+ {
+ return;
+ }
+
+ asset.mSource.owner = asset;
+ LocalizationManager.AddSource(asset.mSource);
+ }
+ public void Awake()
+ {
+ IOProvider.Init();
+
+ Harmony.CreateAndPatchAll(typeof(SpaceWarpPlugin).Assembly, ModGuid);
+ ModuleManager.LoadAllModules();
+
+ Loading.AddAssetLoadingAction("bundles", "loading asset bundles", FunctionalLoadingActions.AssetBundleLoadingAction, "bundle");
+ Loading.AddAssetLoadingAction("images", "loading images", FunctionalLoadingActions.ImageLoadingAction);
+ Loading.AddAddressablesLoadingAction("localization","language_source",OnLanguageSourceAssetLoaded);
+ }
+
+ private void SetupLuaState()
+ {
+ // I have been warned and I do not care
+ UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;
+
+ GlobalLuaState = (ScriptEnvironment)Game.ScriptEnvironment;
+ GlobalLuaState.script.Globals["SWLog"] = Logger;
+
+ // Now we loop over every assembly and import static lua classes for methods
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ UserData.RegisterAssembly(assembly);
+ foreach (var type in assembly.GetTypes())
+ {
+ // Now we can easily create API Objects from a [SpaceWarpLuaAPI] attribute
+ foreach (var attr in type.GetCustomAttributes())
+ {
+ if (attr is not SpaceWarpLuaAPIAttribute luaAPIAttribute) continue;
+ // As this seems to be used here
+ GlobalLuaState.script.Globals[luaAPIAttribute.LuaName] = UserData.CreateStatic(type);
+ break;
+ }
+ }
+ }
+ }
+
+ public override void OnPreInitialized()
+ {
+ // Persist all game objects so I don't need to stomp on config
+ BepInEx.Bootstrap.Chainloader.ManagerObject.Persist();
+ ModuleManager.PreInitializeAllModules();
+ }
+
+ public override void OnInitialized()
+ {
+ ModuleManager.InitializeAllModules();
+ SetupLuaState();
+ }
+
+ public override void OnPostInitialized()
+ {
+ ModuleManager.PostInitializeAllModules();
+ }
+}
diff --git a/SpaceWarp/API/Game/Extensions/PartProviderExtensions.cs b/SpaceWarp.Game/API/Game/Extensions/PartProviderExtensions.cs
similarity index 86%
rename from SpaceWarp/API/Game/Extensions/PartProviderExtensions.cs
rename to SpaceWarp.Game/API/Game/Extensions/PartProviderExtensions.cs
index 619ddaf6..d77af419 100644
--- a/SpaceWarp/API/Game/Extensions/PartProviderExtensions.cs
+++ b/SpaceWarp.Game/API/Game/Extensions/PartProviderExtensions.cs
@@ -1,9 +1,12 @@
using System.Collections.Generic;
+using System.Linq;
+using JetBrains.Annotations;
using KSP.Game;
using KSP.Sim.Definitions;
namespace SpaceWarp.API.Game.Extensions;
+[PublicAPI]
public static class PartProviderExtensions
{
public static IEnumerable WithModule(this PartProvider provider) where T : ModuleData
diff --git a/SpaceWarp/API/Game/Extensions/VesselVehicleExtensions.cs b/SpaceWarp.Game/API/Game/Extensions/VesselVehicleExtensions.cs
similarity index 98%
rename from SpaceWarp/API/Game/Extensions/VesselVehicleExtensions.cs
rename to SpaceWarp.Game/API/Game/Extensions/VesselVehicleExtensions.cs
index 12584da6..e8b4df65 100644
--- a/SpaceWarp/API/Game/Extensions/VesselVehicleExtensions.cs
+++ b/SpaceWarp.Game/API/Game/Extensions/VesselVehicleExtensions.cs
@@ -1,9 +1,11 @@
-using KSP.Sim.impl;
+using JetBrains.Annotations;
+using KSP.Sim.impl;
using KSP.Sim.State;
using UnityEngine;
namespace SpaceWarp.API.Game.Extensions;
+[PublicAPI]
public static class VesselVehicleExtensions
{
public static void SetMainThrottle(this VesselVehicle vehicle, float throttle)
diff --git a/SpaceWarp/API/Game/Messages/StateChanges.cs b/SpaceWarp.Game/API/Game/Messages/StateChanges.cs
similarity index 97%
rename from SpaceWarp/API/Game/Messages/StateChanges.cs
rename to SpaceWarp.Game/API/Game/Messages/StateChanges.cs
index ab29eb0e..c0f5768f 100644
--- a/SpaceWarp/API/Game/Messages/StateChanges.cs
+++ b/SpaceWarp.Game/API/Game/Messages/StateChanges.cs
@@ -1,19 +1,21 @@
using System;
+using JetBrains.Annotations;
using KSP.Game;
using KSP.Messages;
namespace SpaceWarp.API.Game.Messages;
///
-/// A class that contains a list of events that are published either when a state is entered or left
+/// A class that contains a list of events that are published either when a state is entered or left
///
+[PublicAPI]
public static class StateChanges
{
#region State changing
///
- /// Invoked when the game state is changed
- /// Action(Message,PreviousState,CurrentState)
+ /// Invoked when the game state is changed
+ /// Action(Message,PreviousState,CurrentState)
///
public static event Action GameStateChanged;
@@ -141,7 +143,6 @@ internal static void OnGameStateEntered(MessageCenterMessage message)
}
}
-
internal static void OnGameStateLeft(MessageCenterMessage message)
{
var msg = message as GameStateLeftMessage;
@@ -217,5 +218,6 @@ internal static void OnGameStateChanged(MessageCenterMessage message)
var msg = message as GameStateChangedMessage;
GameStateChanged?.Invoke(msg!, msg!.PreviousState, msg!.CurrentState);
}
+
#endregion
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Game/Messages/StateLoadings.cs b/SpaceWarp.Game/API/Game/Messages/StateLoadings.cs
similarity index 94%
rename from SpaceWarp/API/Game/Messages/StateLoadings.cs
rename to SpaceWarp.Game/API/Game/Messages/StateLoadings.cs
index 26e61904..94df110c 100644
--- a/SpaceWarp/API/Game/Messages/StateLoadings.cs
+++ b/SpaceWarp.Game/API/Game/Messages/StateLoadings.cs
@@ -1,8 +1,10 @@
using System;
+using JetBrains.Annotations;
using KSP.Messages;
namespace SpaceWarp.API.Game.Messages;
+[PublicAPI]
public class StateLoadings
{
public static event Action TrainingCenterLoaded;
@@ -11,7 +13,7 @@ public class StateLoadings
public static void TrainingCenterLoadedHandler(MessageCenterMessage message)
{
TrainingCenterLoaded?.Invoke(message as TrainingCenterLoadedMessage);
- }
+ }
public static void TrackingStationLoadedHandler(MessageCenterMessage message)
{
TrackingStationLoaded?.Invoke(message as TrackingStationLoadedMessage);
@@ -21,5 +23,5 @@ public static void TrackingStationUnloadedHandler(MessageCenterMessage message)
{
TrackingStationUnloaded?.Invoke(message as TrackingStationUnloadedMessage);
}
-
+
}
\ No newline at end of file
diff --git a/SpaceWarp/API/Game/Vehicle.cs b/SpaceWarp.Game/API/Game/Vehicle.cs
similarity index 86%
rename from SpaceWarp/API/Game/Vehicle.cs
rename to SpaceWarp.Game/API/Game/Vehicle.cs
index 78966006..68902970 100644
--- a/SpaceWarp/API/Game/Vehicle.cs
+++ b/SpaceWarp.Game/API/Game/Vehicle.cs
@@ -1,10 +1,12 @@
-using KSP.Game;
+using JetBrains.Annotations;
+using KSP.Game;
using KSP.Sim.impl;
using SpaceWarp.API.Lua;
namespace SpaceWarp.API.Game;
[SpaceWarpLuaAPI("Vehicle")]
+[PublicAPI]
public static class Vehicle
{
public static VesselVehicle ActiveVesselVehicle => GameManager.Instance.Game.ViewController._activeVesselVehicle;
diff --git a/SpaceWarp.Game/Modules/Game.cs b/SpaceWarp.Game/Modules/Game.cs
new file mode 100644
index 00000000..461156ff
--- /dev/null
+++ b/SpaceWarp.Game/Modules/Game.cs
@@ -0,0 +1,35 @@
+using JetBrains.Annotations;
+using KSP.Game;
+using KSP.Messages;
+using SpaceWarp.API.Game.Messages;
+
+namespace SpaceWarp.Modules;
+
+[UsedImplicitly]
+public class Game : SpaceWarpModule
+{
+ public override string Name => "SpaceWarp.Game";
+
+ public override void LoadModule()
+ {
+ }
+
+ public override void PreInitializeModule()
+ {
+ }
+
+ public override void InitializeModule()
+ {
+ var game = GameManager.Instance.Game;
+ game.Messages.Subscribe(typeof(GameStateEnteredMessage), StateChanges.OnGameStateEntered, false, true);
+ game.Messages.Subscribe(typeof(GameStateLeftMessage), StateChanges.OnGameStateLeft, false, true);
+ game.Messages.Subscribe(typeof(GameStateChangedMessage), StateChanges.OnGameStateChanged, false, true);
+ game.Messages.Subscribe(typeof(TrackingStationLoadedMessage), StateLoadings.TrackingStationLoadedHandler, false, true);
+ game.Messages.Subscribe(typeof(TrackingStationUnloadedMessage), StateLoadings.TrackingStationUnloadedHandler, false, true);
+ game.Messages.Subscribe(typeof(TrainingCenterLoadedMessage), StateLoadings.TrainingCenterLoadedHandler, false, true);
+ }
+
+ public override void PostInitializeModule()
+ {
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Game/SpaceWarp.Game.csproj b/SpaceWarp.Game/SpaceWarp.Game.csproj
new file mode 100644
index 00000000..9f663217
--- /dev/null
+++ b/SpaceWarp.Game/SpaceWarp.Game.csproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBus.cs b/SpaceWarp.Messaging/API/Messaging/MessageBus.cs
new file mode 100644
index 00000000..dc220ba3
--- /dev/null
+++ b/SpaceWarp.Messaging/API/Messaging/MessageBus.cs
@@ -0,0 +1,663 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using UnityEngine;
+
+namespace SpaceWarp.API.Messaging;
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish()
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke();
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (int i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10, T12 arg11)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10, T12 arg11, T13 arg12)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10, T12 arg11, T13 arg12, T14 arg13)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12,
+ arg13);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers = new();
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10, T12 arg11, T13 arg12, T14 arg13, T15 arg14)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12,
+ arg13, arg14);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
+
+[PublicAPI]
+public class MessageBus : MessageBusBase
+{
+ private readonly List> _handlers =
+ new();
+
+ internal override IReadOnlyList Handlers => _handlers;
+ internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index);
+
+ public void Subscribe(Action handler)
+ {
+ if (handler == null)
+ throw new ArgumentNullException();
+
+ _handlers.Add(handler);
+ }
+
+ public void Unsubscribe(Action handler)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ if (_handlers[i] == handler)
+ _handlers.RemoveAt(i);
+ }
+
+ public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9,
+ T11 arg10, T12 arg11, T13 arg12, T14 arg13, T15 arg14, T16 arg15)
+ {
+ for (var i = _handlers.Count; i-- > 0;)
+ {
+ try
+ {
+ _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12,
+ arg13, arg14, arg15);
+ }
+ catch (Exception e)
+ {
+ Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs b/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs
new file mode 100644
index 00000000..f8ae2782
--- /dev/null
+++ b/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace SpaceWarp.API.Messaging;
+
+[PublicAPI]
+public abstract class MessageBusBase
+{
+ public string Name { get; internal set; }
+ internal abstract IReadOnlyList Handlers { get; }
+ internal abstract void RemoveHandlerAt(int index);
+}
\ No newline at end of file
diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs b/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs
new file mode 100644
index 00000000..8b256e92
--- /dev/null
+++ b/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using UnityEngine;
+
+namespace SpaceWarp.API.Messaging;
+
+[PublicAPI]
+public static class MessageBusManager
+{
+ private static Dictionary _messagesBusesByName = new Dictionary