diff --git a/README.md b/README.md index e443d279..9964aab9 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ Note: Use at your own risk, as this is an early version that is expected to unde To compile this project, you will need to follow these steps: -1. Copy everything in the `Kerbal Space Program 2\KSP2_x64_Data\Managed` folder into the `external_dlls/` folder. -2. Run one of the build scripts (see below for more info) and copy the contents from the correct build output directory into the KSP2 root directory. -3. Launch KSP2 and wait until the title screen appears. You should see a mods folder under the `SpaceWarp` folder. -4. Drag any mods that follow the structure below into that mods folder. +1. Install NuGet +2. Copy everything in the `Kerbal Space Program 2\KSP2_x64_Data\Managed` folder into the `external_dlls/` folder. +3. Run one of the build scripts (see below for more info) and copy the contents from the correct build output directory into the KSP2 root directory. +4. Launch KSP2 and wait until the title screen appears. You should see a mods folder under the `SpaceWarp` folder. +5. Drag any mods that follow the structure below into that mods folder. Mods are currently implemented as monobehaviours with two fields: a `Logger` for logging and a `Manager` that points to Spacewarp. A mod template generator exists as a Python script. diff --git a/SpaceWarp/API/Logging/ModLogger.cs b/SpaceWarp/API/Logging/ModLogger.cs index 4d1157a0..42ba2486 100644 --- a/SpaceWarp/API/Logging/ModLogger.cs +++ b/SpaceWarp/API/Logging/ModLogger.cs @@ -31,7 +31,7 @@ private void InternalLog(LogLevel level, string message) protected override void Log(LogLevel level, string message) { - if ((int)level >= StartupManager.SpaceWarpObject.SpaceWarpConfiguration.LogLevel) + if ((int)level >= SpaceWarpGlobalConfiguration.Instance.LogLevel) { InternalLog(level,message); } diff --git a/SpaceWarp/API/SpaceWarpGlobalConfiguration.cs b/SpaceWarp/API/SpaceWarpGlobalConfiguration.cs index f8b24b80..40061e23 100644 --- a/SpaceWarp/API/SpaceWarpGlobalConfiguration.cs +++ b/SpaceWarp/API/SpaceWarpGlobalConfiguration.cs @@ -1,13 +1,63 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; +using System.IO; using Newtonsoft.Json; +using UnityEngine; namespace SpaceWarp.API { [JsonObject(MemberSerialization.OptIn)] public class SpaceWarpGlobalConfiguration { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] - [DefaultValue((int)Logging.LogLevel.Info)] + public static SpaceWarpGlobalConfiguration Instance; + private const string MODS_FOLDER_NAME = "Mods"; + public static string SPACE_WARP_PATH = Directory.GetCurrentDirectory() + "/SpaceWarp/"; + public static string MODS_FULL_PATH = SPACE_WARP_PATH + MODS_FOLDER_NAME; + + private static string SPACEWARP_CONFIG_FULL_PATH = MODS_FULL_PATH + "/" + SPACE_WARP_CONFIG_FILE_NAME; + private const string SPACE_WARP_CONFIG_FILE_NAME = "space_warp_config.json"; + + /// + /// Loading Global Configuration and puting it into Instance. + /// + public static void Init() + { + if (!File.Exists(SPACEWARP_CONFIG_FULL_PATH)) + { + Instance = new SpaceWarpGlobalConfiguration(); + Instance.ApplyDefaultValues(); + } + else + { + try + { + string json = File.ReadAllText(SPACEWARP_CONFIG_FULL_PATH); + Instance = JsonConvert.DeserializeObject(json); + } + catch (Exception exception) + { + //TODO: log this in a nicer way, for now I guess we can just construct a new logger + Debug.LogError($"Loading space warp config failed\nException: {exception}"); + + File.Delete(SPACEWARP_CONFIG_FULL_PATH); + Init(); + return; + } + } + + try + { + File.WriteAllLines(SPACEWARP_CONFIG_FULL_PATH, new[] { JsonConvert.SerializeObject(Instance) }); + } + catch (Exception exception) + { + //TODO: log this in a nicer way, for now I guess we can just construct a new logger + Debug.LogError($"Saving the spacewarp config failed\nException: {exception}"); + } + } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + [DefaultValue((int)Logging.LogLevel.Info)] public int LogLevel { get; set; } public void ApplyDefaultValues() diff --git a/SpaceWarp/API/SpaceWarpManager.cs b/SpaceWarp/API/SpaceWarpManager.cs index 1f02777a..cb628510 100644 --- a/SpaceWarp/API/SpaceWarpManager.cs +++ b/SpaceWarp/API/SpaceWarpManager.cs @@ -32,9 +32,6 @@ public class SpaceWarpManager : Manager public static string SPACE_WARP_PATH = Directory.GetCurrentDirectory() + "/SpaceWarp/"; public static string MODS_FULL_PATH = SPACE_WARP_PATH + MODS_FOLDER_NAME; - private const string SPACE_WARP_CONFIG_FILE_NAME = "space_warp_config.json"; - private static string SPACEWARP_CONFIG_FULL_PATH = MODS_FULL_PATH + "/" + SPACE_WARP_CONFIG_FILE_NAME; - public SpaceWarpGlobalConfiguration SpaceWarpConfiguration; private readonly List _allModScripts = new List(); @@ -56,7 +53,6 @@ protected override void Start() private void Initialize() { InitializeConfigManager(); - InitializeSpaceWarpConfig(); InitializeModLogger(); @@ -481,7 +477,7 @@ private void InitializeModConfig(Type config_type, string mod_id) _modLogger.Error($"Loading mod config failed\nException: {exception}"); File.Delete(config_path); - InitializeSpaceWarpConfig(); + InitializeModConfig(config_type, mod_id); return; } } @@ -500,46 +496,6 @@ private void InitializeModConfig(Type config_type, string mod_id) configurationManager.Add(mod_id,(config_type,modConfiguration,config_path)); } } - - /// - /// Tried to find the SpaceWarp config file in the game, if none is found one is created. - /// - /// - private void InitializeSpaceWarpConfig() - { - if (!File.Exists(SPACEWARP_CONFIG_FULL_PATH)) - { - SpaceWarpConfiguration = new SpaceWarpGlobalConfiguration(); - SpaceWarpConfiguration.ApplyDefaultValues(); - } - else - { - try - { - string json = File.ReadAllText(SPACEWARP_CONFIG_FULL_PATH); - SpaceWarpConfiguration = JsonConvert.DeserializeObject(json); - } - catch (Exception exception) - { - //TODO: log this in a nicer way, for now I guess we can just construct a new logger - new ModLogger("Space Warp").Error($"Loading space warp config failed\nException: {exception}"); - - File.Delete(SPACEWARP_CONFIG_FULL_PATH); - InitializeSpaceWarpConfig(); - return; - } - } - - try - { - File.WriteAllLines(SPACEWARP_CONFIG_FULL_PATH, new[] { JsonConvert.SerializeObject(SpaceWarpConfiguration) }); - } - catch(Exception exception) - { - //TODO: log this in a nicer way, for now I guess we can just construct a new logger - new ModLogger("Space Warp").Error($"Saving the spacewarp config failed\nException: {exception}"); - } - } /// /// Initializes a mod object. diff --git a/SpaceWarp/Compilation/ModCompiler.cs b/SpaceWarp/Compilation/ModCompiler.cs index 0c7ead37..db5f1e26 100644 --- a/SpaceWarp/Compilation/ModCompiler.cs +++ b/SpaceWarp/Compilation/ModCompiler.cs @@ -107,8 +107,26 @@ private static Assembly CompileNewAssemblyAndCache(string modID, string modSrcPa var compilation = CSharpCompilation.Create(modID + ".dll", trees, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); + var result = compilation.Emit(CACHE_LOCATION + modID + ".dll"); - return Assembly.LoadFile(CACHE_LOCATION + modID + ".dll"); + foreach (Diagnostic diagnostic in result.Diagnostics) + { + if (diagnostic.WarningLevel == 0) + { + _logger.Error(diagnostic.ToString()); + } + else + { + _logger.Info(diagnostic.ToString()); + } + } + + _logger.Info(result.ToString()); + if (!result.Success) + { + File.Delete(CACHE_LOCATION + modID + ".dll"); + } + return !result.Success ? null : Assembly.LoadFile(CACHE_LOCATION + modID + ".dll"); } } } \ No newline at end of file diff --git a/SpaceWarp/Entrypoint.cs b/SpaceWarp/Entrypoint.cs index d29f2213..08651d40 100644 --- a/SpaceWarp/Entrypoint.cs +++ b/SpaceWarp/Entrypoint.cs @@ -1,5 +1,6 @@ using HarmonyLib; using KSP.Logging; +using SpaceWarp.API; using SpaceWarp.UI; using System.Reflection; using UnityEngine.SceneManagement; @@ -22,6 +23,8 @@ public class SpaceWarpEntrypoint /// public static void Start() { + SpaceWarpGlobalConfiguration.Init(); + SceneManager.sceneLoaded += OnSceneLoaded; KspLogManager.AddLogCallback(SpaceWarpConsoleLogListener.LogCallback); diff --git a/SpaceWarp/UI/ModConfigurationSection.cs b/SpaceWarp/UI/ModConfigurationSection.cs index 4ed78ae8..10536300 100644 --- a/SpaceWarp/UI/ModConfigurationSection.cs +++ b/SpaceWarp/UI/ModConfigurationSection.cs @@ -8,7 +8,7 @@ public class ModConfigurationSection { public bool Open = false; - public readonly List<(string name, FieldInfo info, object confAttribute)> Properties = new List<(string name, FieldInfo info, object confAttribute)>(); + public readonly List<(string name, FieldInfo info, object confAttribute, string currentStringValue)> Properties = new List<(string name, FieldInfo info, object confAttribute, string currentStringValue)>(); public readonly List<(string path, ModConfigurationSection section)> SubSections = new List<(string path, ModConfigurationSection section)>(); private ModConfigurationSection TouchSubSection(string subsection) @@ -25,7 +25,7 @@ private ModConfigurationSection TouchSubSection(string subsection) return sub2; } - public void Insert(string[] path, (string name, FieldInfo info, object confAttribute) property) + public void Insert(string[] path, (string name, FieldInfo info, object confAttribute, string currentStringValue) property) { StringBuilder sb = new StringBuilder(); foreach (string t in path) diff --git a/SpaceWarp/UI/ModConfigurationUI.cs b/SpaceWarp/UI/ModConfigurationUI.cs index f0fa5630..1e570d05 100644 --- a/SpaceWarp/UI/ModConfigurationUI.cs +++ b/SpaceWarp/UI/ModConfigurationUI.cs @@ -5,6 +5,7 @@ using SpaceWarp.API.Configuration; using SpaceWarp.API.Managers; using UnityEngine; +using UnityEngine.Serialization; namespace SpaceWarp.UI { @@ -14,8 +15,7 @@ public class ModConfigurationUI : KerbalMonoBehaviour public Type ConfigurationType; public object ConfigurationObject; - public string ModName; - public string ModID; + [FormerlySerializedAs("ModID")] public string modID; private int _windowWidth = 350; private int _windowHeight = 700; @@ -23,7 +23,7 @@ public class ModConfigurationUI : KerbalMonoBehaviour private static GUIStyle _boxStyle; - private readonly ModConfigurationSection _rootSection = new ModConfigurationSection(); + private ModConfigurationSection _rootSection; private void Awake() { @@ -33,6 +33,7 @@ private void Awake() public void Start() { + _rootSection = new ModConfigurationSection(); foreach (FieldInfo field in ConfigurationType.GetFields(BindingFlags.Instance | BindingFlags.Public)) { object attribute; @@ -48,17 +49,19 @@ public void Start() ConfigFieldAttribute fieldAttribute = field.GetCustomAttribute(); - if (fieldAttribute != null) + if (fieldAttribute == null) { - attribute = fieldAttribute; - attributeName = fieldAttribute.Name; - _rootSection.Insert(section.Split(new []{'/'},StringSplitOptions.RemoveEmptyEntries), (attributeName, field, attribute)); + // attribute = fieldAttribute; + // attributeName = fieldAttribute.Name; + // _rootSection.Insert(section.Split(new []{'/'},StringSplitOptions.RemoveEmptyEntries), (attributeName, field, attribute, field.GetValue(ConfigurationObject).ToString())); + continue; } - attribute = fieldAttribute; - attributeName = fieldAttribute?.Name; + attributeName = fieldAttribute.Name; - _rootSection.Insert(section.Split(new []{'/'},StringSplitOptions.RemoveEmptyEntries), (attributeName, field, attribute)); + var value = field.GetValue(ConfigurationObject); + _rootSection.Insert(section.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries), + (attributeName, field, attribute, value != null ? value.ToString() : "")); } _windowRect = new Rect((Screen.width * 0.15f), (Screen.height * 0.15f), 0, 0); @@ -67,7 +70,7 @@ public void Start() public void OnGUI() { int controlID = GUIUtility.GetControlID(FocusType.Passive); - string header = $"{ModID} configuration"; + string header = $"{modID} configuration"; GUILayoutOption width = GUILayout.Width((float)(_windowWidth * 0.5)); GUILayoutOption height = GUILayout.Height((float)(_windowHeight * 0.5)); @@ -75,34 +78,48 @@ public void OnGUI() _windowRect = GUILayout.Window(controlID, _windowRect, FillWindow, header, width, height); } - private void EditorInputField(string fieldName, FieldInfo info) + private string EditorInputField(string fieldName, FieldInfo info, string current) { + var result = ""; GUILayout.BeginHorizontal(); if (info.FieldType != typeof(bool)) { GUILayout.Label(fieldName); - string rawInputValue = GUILayout.TextField(info.GetValue(ConfigurationObject).ToString()); - object convertedInputValue = TypeDescriptor.GetConverter(info.FieldType).ConvertFromInvariantString(rawInputValue); - - info.SetValue(ConfigurationObject, convertedInputValue); + string rawInputValue = GUILayout.TextField(current); + result = rawInputValue; + try + { + object convertedInputValue = TypeDescriptor.GetConverter(info.FieldType) + .ConvertFromInvariantString(rawInputValue); + info.SetValue(ConfigurationObject, convertedInputValue); + } + catch + { + // ignored + } } else { bool toggleValue = GUILayout.Toggle((bool)info.GetValue(ConfigurationObject), fieldName); - + result = toggleValue.ToString(); info.SetValue(ConfigurationObject, toggleValue); } GUILayout.EndHorizontal(); + return result; } - private void EditorForField((string name, FieldInfo info, object confAttribute) field) + private string EditorForField((string name, FieldInfo info, object confAttribute, string currentStringValue) field) { if (field.confAttribute is ConfigFieldAttribute) { - EditorInputField(field.name, field.info); + return EditorInputField(field.name, field.info, field.currentStringValue); + } + else + { + return ""; } } @@ -118,9 +135,13 @@ private void SectionPropertyViewer(string sectionName, ModConfigurationSection s return; } - foreach ((string name, FieldInfo info, object confAttribute) property in section.Properties) + + for (int i = 0; i < section.Properties.Count; i++) { - EditorForField(property); + var prop = section.Properties[i]; + var str = EditorForField(prop); + prop.currentStringValue = str; + section.Properties[i] = prop; } foreach ((string path, ModConfigurationSection section) sub in section.SubSections) @@ -140,9 +161,12 @@ private void FillWindow(int windowID) GUILayout.BeginVertical(); // These are the root properties - foreach ((string name, FieldInfo info, object confAttribute) field in _rootSection.Properties) + for (int i = 0; i < _rootSection.Properties.Count; i++) { - EditorForField(field); + var prop = _rootSection.Properties[i]; + var str = EditorForField(prop); + prop.currentStringValue = str; + _rootSection.Properties[i] = prop; } foreach ((string path, ModConfigurationSection section) section in _rootSection.SubSections) @@ -155,7 +179,7 @@ private void FillWindow(int windowID) //Run saving code from the configuration manager if (ManagerLocator.TryGet(out ConfigurationManager configurationManager)) { - configurationManager.UpdateConfiguration(ModID); + configurationManager.UpdateConfiguration(modID); } Destroy(this); } diff --git a/SpaceWarp/UI/ModListUI.cs b/SpaceWarp/UI/ModListUI.cs index 5257c582..a9dae230 100644 --- a/SpaceWarp/UI/ModListUI.cs +++ b/SpaceWarp/UI/ModListUI.cs @@ -137,7 +137,7 @@ private void CreateModConfigurationUI() configUI.ConfigurationType = config.configType; configUI.ConfigurationObject = config.configObject; - configUI.name = _selectedModInfo.name; + configUI.modID = _selectedMod; go.SetActive(true); } diff --git a/SpaceWarp/UI/SpaceWarpConsole.cs b/SpaceWarp/UI/SpaceWarpConsole.cs index 0461ea17..e2f8d61d 100644 --- a/SpaceWarp/UI/SpaceWarpConsole.cs +++ b/SpaceWarp/UI/SpaceWarpConsole.cs @@ -41,7 +41,6 @@ public void Start() private void Awake() { - KspLogManager.AddLogCallback(LogCallback); _windowWidth = (int)(Screen.width * 0.5f); _windowHeight = (int)(Screen.height * 0.5f); @@ -65,30 +64,7 @@ private void OnGUI() _windowRect = GUILayout.Window(controlID, _windowRect, DrawConsole, header, width, height); } - - private void LogCallback(string condition, string stackTrace, LogType type) - { - switch (type) - { - case LogType.Error: - _debugMessages.Add($"[ERR] {condition}"); - break; - case LogType.Assert: - _debugMessages.Add($"[AST] {condition}"); - break; - case LogType.Warning: - _debugMessages.Add($"[WRN] {condition}"); - break; - case LogType.Log: - _debugMessages.Add($"[LOG] {condition}"); - break; - case LogType.Exception: - _debugMessages.Add($"[EXC] {condition}"); - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } + private void Update() { if (Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.C)) @@ -102,8 +78,8 @@ private void DrawConsole(int windowID) _boxStyle = GUI.skin.GetStyle("Box"); GUILayout.BeginVertical(); _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, false, true); - - foreach (string debugMessage in _debugMessages) + + foreach (string debugMessage in SpaceWarpConsoleLogListener.DebugMessages) { GUILayout.Label(debugMessage); } diff --git a/builder.py b/builder.py index ed457429..674f5989 100644 --- a/builder.py +++ b/builder.py @@ -25,12 +25,46 @@ def clean(): if os.path.exists(os.path.join(SPACEWARP_DIR, "obj")): shutil.rmtree(os.path.join(SPACEWARP_DIR, "obj")) +def do_nuget_source(): + sources_output = subprocess.run(["nuget", "sources", "list"], capture_output=True) + + if not "bepinex" in str(sources_output.stdout, "utf-8").lower(): + print("=> Adding BepInEx source to Nuget config...") + + nuget_src_out = subprocess.run(["nuget", "sources", "add", "-Name", "BepInEx", "-Source", "https://nuget.bepinex.dev/v3/index.json"], capture_output=True) + + print(" |=>| STDOUT") + + for line in str(nuget_src_out.stdout, "utf-8").splitlines(): + print(f" {line}") + + print(" |=>| STDERR") + + for line in str(nuget_src_out.stderr, "utf-8").splitlines(): + print(f" {line}") + + print("=> Restoring project dependencies...") + + nuget_out = subprocess.run(["nuget", "restore"], capture_output=True) + + print(" |=>| STDOUT") + + for line in str(nuget_out.stdout, "utf-8").splitlines(): + print(f" {line}") + + print(" |=>| STDERR") + + for line in str(nuget_out.stderr, "utf-8").splitlines(): + print(f" {line}") + def build(release = False, doorstop = False): build_type = "Doorstop" if doorstop else "BepInEx" dotnet_args = ["dotnet", "build", os.path.join(SPACEWARP_DIR, "SpaceWarp.csproj"), "-c", "Release" if release else "Debug"] build_output_dir = os.path.join(SPACEWARP_DIR, "bin", "Release" if release else "Debug") output_dir = os.path.join(BUILD_DIR, build_type, "BepInEx", "plugins", "SpaceWarp") + do_nuget_source() + if doorstop: dotnet_args.append("-p:DefineConstants=\"DOORSTOP_BUILD\"") output_dir = os.path.join(BUILD_DIR, build_type, "SpaceWarp", "core")