diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a3ce42f3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.cs text diff=csharp +*.csx text diff=csharp +*.sln text eol=crlf +*.csproj text eol=crlf + +*.csv text eol=lf diff --git a/.github/README.md b/.github/README.md index b92b4a1f..5b07a8c8 100644 --- a/.github/README.md +++ b/.github/README.md @@ -56,7 +56,7 @@ KSP2_Root_Folder/ │ │ │ │ │ ├── *.bundle │ │ │ │ ├── images/ │ │ │ │ │ ├── * -│ │ │ ├── localization/ +│ │ │ ├── localizations/ │ │ │ │ ├── *.csv │ │ │ ├── addressables/ │ │ │ │ ├── catalog.json diff --git a/Directory.Build.props b/Directory.Build.props index 81ae7375..cdb60af6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,15 @@ - 1.1.3 - net472 + 1.2.0 + netstandard2.0 11 true false false - - CS0436 + + $(NoWarn),CS0436,CS1591 + true + + true + Space Warp contributors + A C# modding API for KSP2 built on top of BepInEx + icon.png + SpaceWarp + https://raw.githubusercontent.com/SpaceWarpDev/SpaceWarp/main/LICENSE + ..\build + https://spacewarp.org + README.md + SpaceWarp;KSP2;modding + https://github.com/SpaceWarpDev/SpaceWarp + Space Warp + $(NoWarn),NU5118,NU5125 + + + + + + + + true + lib\$(TargetFramework) + + + + \ No newline at end of file diff --git a/SpaceWarp/SpaceWarpManager.cs b/SpaceWarp/SpaceWarpManager.cs index 983c23b2..0848f1c6 100644 --- a/SpaceWarp/SpaceWarpManager.cs +++ b/SpaceWarp/SpaceWarpManager.cs @@ -5,7 +5,6 @@ using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; -using I2.Loc; using SpaceWarpPatcher; using Newtonsoft.Json; using SpaceWarp.API.Assets; @@ -14,7 +13,7 @@ using SpaceWarp.API.Mods.JSON; using SpaceWarp.API.UI.Appbar; using SpaceWarp.Backend.UI.Appbar; -using SpaceWarp.UI; +using SpaceWarp.UI.ModList; using UnityEngine; namespace SpaceWarp; @@ -41,7 +40,7 @@ internal static class SpaceWarpManager private static GUISkin _skin; - public static ModListUI ModListUI { get; internal set; } + public static ModListController ModListController { get; internal set; } public static GUISkin Skin { @@ -144,7 +143,7 @@ internal static void GetSpaceWarpPlugins() public static void Initialize(SpaceWarpPlugin spaceWarpPlugin) { - Logger = spaceWarpPlugin.Logger; + Logger = SpaceWarpPlugin.Logger; SpaceWarpFolder = Path.GetDirectoryName(spaceWarpPlugin.Info.Location); @@ -159,21 +158,35 @@ internal static void CheckKspVersions() ?.GetValue(null) as string; foreach (var plugin in SpaceWarpPlugins) { - ModsUnsupported[plugin.SpaceWarpMetadata.ModID] = - !plugin.SpaceWarpMetadata.SupportedKsp2Versions.IsSupported(kspVersion); + CheckModKspVersion(plugin.Info.Metadata.GUID, plugin.SpaceWarpMetadata, kspVersion); } foreach (var info in NonSpaceWarpInfos) { - ModsUnsupported[info.Item2.ModID] = !info.Item2.SupportedKsp2Versions.IsSupported(kspVersion); + CheckModKspVersion(info.Item1.Info.Metadata.GUID, info.Item2, kspVersion); } foreach (var info in DisabledInfoPlugins) { - ModsUnsupported[info.Item2.ModID] = !info.Item2.SupportedKsp2Versions.IsSupported(kspVersion); + CheckModKspVersion(info.Item1.Metadata.GUID, info.Item2, kspVersion); } } + private static void CheckModKspVersion(string guid, ModInfo modInfo, string kspVersion) + { + var unsupported = true; + try + { + unsupported = !modInfo.SupportedKsp2Versions.IsSupported(kspVersion); + } + catch (Exception e) + { + Logger.LogError($"Unable to check KSP version for {guid} due to error {e}"); + } + + ModsUnsupported[guid] = unsupported; + } + private static List<(string name, UnityObject asset)> AssetBundleLoadingAction(string internalPath, string filename) { var assetBundle = AssetBundle.LoadFromFile(filename); @@ -213,18 +226,17 @@ internal static void CheckKspVersions() { var tex = new Texture2D(2, 2, TextureFormat.ARGB32, false) { - filterMode = FilterMode.Point + filterMode = FilterMode.Point }; var fileData = File.ReadAllBytes(filename); tex.LoadImage(fileData); // Will automatically resize - List<(string name, UnityObject asset)> assets = new(); - assets.Add(($"images/{internalPath}",tex)); + List<(string name, UnityObject asset)> assets = new() { ($"images/{internalPath}", tex) }; return assets; } internal static void InitializeSpaceWarpsLoadingActions() { - Loading.AddAssetLoadingAction("bundles","loading asset bundles",AssetBundleLoadingAction,"bundle"); - Loading.AddAssetLoadingAction("images","loading images",ImageLoadingAction); + Loading.AddAssetLoadingAction("bundles", "loading asset bundles", AssetBundleLoadingAction, "bundle"); + Loading.AddAssetLoadingAction("images", "loading images", ImageLoadingAction); } } \ No newline at end of file diff --git a/SpaceWarp/SpaceWarpPlugin.cs b/SpaceWarp/SpaceWarpPlugin.cs index 61c8e8a7..a3baaced 100644 --- a/SpaceWarp/SpaceWarpPlugin.cs +++ b/SpaceWarp/SpaceWarpPlugin.cs @@ -3,26 +3,32 @@ using System; using System.Collections; using System.Reflection; +using System.Xml; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using KSP.Messages; +using UitkForKsp2.API; using Newtonsoft.Json; +using SpaceWarp.API.Assets; using SpaceWarp.API.Game.Messages; using SpaceWarp.API.Mods; using SpaceWarp.API.Mods.JSON; -using SpaceWarp.API.UI; using SpaceWarp.API.Versions; using SpaceWarp.UI; using SpaceWarp.UI.Debug; +using SpaceWarp.UI.ModList; +using UitkForKsp2; using UnityEngine; using UnityEngine.Networking; +using UnityEngine.UIElements; namespace SpaceWarp; [BepInDependency(ConfigurationManager.ConfigurationManager.GUID, ConfigurationManager.ConfigurationManager.Version)] +[BepInDependency(UitkForKsp2Plugin.ModGuid, UitkForKsp2Plugin.ModVer)] [BepInPlugin(ModGuid, ModName, ModVer)] public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin { @@ -44,7 +50,12 @@ public sealed class SpaceWarpPlugin : BaseSpaceWarpPlugin internal ConfigEntry ConfigWarningColor; private string _kspVersion; - internal new ManualLogSource Logger => base.Logger; + internal new static ManualLogSource Logger; + + public SpaceWarpPlugin() + { + Logger = base.Logger; + } public void Awake() { @@ -78,9 +89,9 @@ public void Awake() BepInEx.Logging.Logger.Listeners.Add(new SpaceWarpConsoleLogListener(this)); Harmony.CreateAndPatchAll(typeof(SpaceWarpPlugin).Assembly, ModGuid); - + SpaceWarpManager.InitializeSpaceWarpsLoadingActions(); - + SpaceWarpManager.Initialize(this); } @@ -93,7 +104,6 @@ public override void OnInitialized() Game.Messages.Subscribe(typeof(GameStateLeftMessage), StateChanges.OnGameStateLeft, false, true); Game.Messages.Subscribe(typeof(GameStateChangedMessage), StateChanges.OnGameStateChanged, false, true); - InitializeUI(); if (_configFirstLaunch.Value) { _configFirstLaunch.Value = false; @@ -103,6 +113,10 @@ public override void OnInitialized() prompt.spaceWarpPlugin = this; } + SpaceWarpManager.CheckKspVersions(); + + InitializeUI(); + if (ConfigCheckVersions.Value) { CheckVersions(); @@ -111,25 +125,23 @@ public override void OnInitialized() { ClearVersions(); } - - SpaceWarpManager.CheckKspVersions(); } public void ClearVersions() { foreach (var plugin in SpaceWarpManager.SpaceWarpPlugins) { - SpaceWarpManager.ModsOutdated[plugin.SpaceWarpMetadata.ModID] = false; + SpaceWarpManager.ModsOutdated[plugin.Info.Metadata.GUID] = false; } foreach (var info in SpaceWarpManager.NonSpaceWarpInfos) { - SpaceWarpManager.ModsOutdated[info.Item2.ModID] = false; + SpaceWarpManager.ModsOutdated[info.Item1.Info.Metadata.GUID] = false; } foreach (var info in SpaceWarpManager.DisabledInfoPlugins) { - SpaceWarpManager.ModsOutdated[info.Item2.ModID] = false; + SpaceWarpManager.ModsOutdated[info.Item1.Metadata.GUID] = false; } } @@ -140,7 +152,7 @@ public void CheckVersions() { if (plugin.SpaceWarpMetadata.VersionCheck != null) { - StartCoroutine(CheckVersion(plugin.SpaceWarpMetadata)); + StartCoroutine(CheckVersion(plugin.Info.Metadata.GUID, plugin.SpaceWarpMetadata)); } } @@ -148,7 +160,7 @@ public void CheckVersions() { if (info.Item2.VersionCheck != null) { - StartCoroutine(CheckVersion(info.Item2)); + StartCoroutine(CheckVersion(info.Item1.Info.Metadata.GUID, info.Item2)); } } @@ -156,43 +168,83 @@ public void CheckVersions() { if (info.Item2.VersionCheck != null) { - StartCoroutine(CheckVersion(info.Item2)); + StartCoroutine(CheckVersion(info.Item1.Metadata.GUID, info.Item2)); } } } - private static bool OlderThan(string currentVersion, string onlineVersion) + private IEnumerator CheckVersion(string guid, ModInfo modInfo) { - return VersionUtility.CompareSemanticVersionStrings(currentVersion, onlineVersion) < 0; - } - - private IEnumerator CheckVersion(ModInfo pluginInfo) - { - var www = UnityWebRequest.Get(pluginInfo.VersionCheck); + var www = UnityWebRequest.Get(modInfo.VersionCheck); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { - Logger.LogInfo($"Unable to check version for {pluginInfo.ModID} due to error {www.error}"); + Logger.LogInfo($"Unable to check version for {guid} due to error {www.error}"); } else { + var isOutdated = false; var results = www.downloadHandler.text; try { - var checkInfo = JsonConvert.DeserializeObject(results); - if (!checkInfo.SupportedKsp2Versions.IsSupported(_kspVersion)) + isOutdated = modInfo.VersionCheckType switch { - yield break; - } - - SpaceWarpManager.ModsOutdated[pluginInfo.ModID] = OlderThan(pluginInfo.Version, checkInfo.Version); + VersionCheckType.SwInfo => CheckJsonVersion(guid, modInfo.Version, results), + VersionCheckType.Csproj => CheckCsprojVersion(guid, modInfo.Version, results), + _ => throw new ArgumentOutOfRangeException(nameof(modInfo), "Invalid version_check_type") + }; } catch (Exception e) { - Logger.LogError($"Unable to check version for {pluginInfo.ModID} due to error {e}"); + Logger.LogError($"Unable to check version for {guid} due to error {e}"); } + + SpaceWarpManager.ModListController.UpdateOutdated(guid, isOutdated); + } + } + + private bool CheckJsonVersion(string guid, string version, string json) + { + var checkInfo = JsonConvert.DeserializeObject(json); + if (!checkInfo.SupportedKsp2Versions.IsSupported(_kspVersion)) + { + return false; + } + + var isOutdated = VersionUtility.IsOlderThan(version, checkInfo.Version); + SpaceWarpManager.ModsOutdated[guid] = isOutdated; + return isOutdated; + } + + private bool CheckCsprojVersion(string guid, string version, string csproj) + { + var document = new XmlDocument(); + document.LoadXml(csproj); + + var ksp2VersionMin = document.GetElementsByTagName("Ksp2VersionMin")[0]?.InnerText + ?? SupportedVersionsInfo.DefaultMin; + var ksp2VersionMax = document.GetElementsByTagName("Ksp2VersionMax")[0]?.InnerText + ?? SupportedVersionsInfo.DefaultMax; + + if (!VersionUtility.IsSupported(_kspVersion, ksp2VersionMin, ksp2VersionMax)) + { + return false; } + + var checkVersionTags = document.GetElementsByTagName("Version"); + var checkVersion = checkVersionTags[0]?.InnerText; + if (checkVersion == null || checkVersionTags.Count != 1) + { + throw new ArgumentOutOfRangeException( + nameof(csproj), + "There must be exactly 1 Version tag in the checked .csproj" + ); + } + + var isOutdated = VersionUtility.IsOlderThan(version, checkVersion); + SpaceWarpManager.ModsOutdated[guid] = isOutdated; + return isOutdated; } private void InitializeUI() @@ -200,20 +252,16 @@ private void InitializeUI() SpaceWarpManager.ConfigurationManager = (ConfigurationManager.ConfigurationManager)Chainloader .PluginInfos[ConfigurationManager.ConfigurationManager.GUID].Instance; - GameObject modUIObject = new("Space Warp Mod UI"); - modUIObject.Persist(); - modUIObject.transform.SetParent(transform); - SpaceWarpManager.ModListUI = modUIObject.AddComponent(); - - modUIObject.SetActive(true); + var modListUxml = AssetManager.GetAsset($"spacewarp/modlist/modlist.uxml"); + var modList = Window.CreateFromUxml(modListUxml, "Space Warp Mod List", transform, true); + SpaceWarpManager.ModListController = modList.gameObject.AddComponent(); + modList.gameObject.Persist(); GameObject consoleUIObject = new("Space Warp Console"); consoleUIObject.Persist(); consoleUIObject.transform.SetParent(Chainloader.ManagerObject.transform); consoleUIObject.AddComponent(); consoleUIObject.SetActive(true); - - MainMenu.RegisterLocalizedMenuButton("SpaceWarp/Mods", SpaceWarpManager.ModListUI.ToggleVisible); } } \ No newline at end of file diff --git a/SpaceWarp/UI/ModList/ModListController.cs b/SpaceWarp/UI/ModList/ModListController.cs new file mode 100644 index 00000000..18d6c4d8 --- /dev/null +++ b/SpaceWarp/UI/ModList/ModListController.cs @@ -0,0 +1,544 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using BepInEx; +using I2.Loc; +using SpaceWarp.API.Assets; +using SpaceWarp.API.Mods.JSON; +using SpaceWarp.API.UI; +using SpaceWarpPatcher; +using UitkForKsp2.API; +using UnityEngine; +using UnityEngine.UIElements; + +namespace SpaceWarp.UI.ModList; + +public class ModListController : MonoBehaviour +{ + private VisualTreeAsset _listEntryTemplate; + private VisualTreeAsset _dependencyTemplate; + + // Mod list UI element references + private VisualElement _container; + + private Button _enableAllButton; + private Button _disableAllButton; + private Button _revertChangesButton; + private Label _changesLabel; + private readonly LocalizedString _changesLabelText = "SpaceWarp/ModList/ChangesDetected"; + + private Foldout _spaceWarpModFoldout; + private VisualElement _spaceWarpModList; + private Foldout _otherModFoldout; + private VisualElement _otherInfoModList; + private VisualElement _otherModList; + private Foldout _disabledModFoldout; + private VisualElement _disabledInfoModList; + private VisualElement _disabledModList; + + private Button _openModsFolderButton; + private Button _openConfigManagerButton; + + // Details UI element references + private VisualElement _detailsContainer; + private Label _detailsNameLabel; + private Label _detailsIdLabel; + private Label _detailsAuthorLabel; + private Label _detailsVersionLabel; + private Button _detailsSourceLink; + private StyleFloat _detailsSourceLinkInitialBorderWidth; + private Label _detailsDescriptionLabel; + private Label _detailsKspVersionLabel; + private VisualElement _detailsOutdatedWarning; + private VisualElement _detailsUnsupportedWarning; + private VisualElement _detailsDisabledWarning; + private Foldout _detailsDependenciesFoldout; + private VisualElement _detailsDependenciesList; + + // State + private bool _isLoaded; + private bool _isWindowVisible; + + private readonly Dictionary _modItemElements = new(); + + private Dictionary _toggles; + private Dictionary _initialToggles; + + private static readonly IReadOnlyList NoToggleGuids = new List + { + SpaceWarpPlugin.ModGuid, + ConfigurationManager.ConfigurationManager.GUID + }; + + private void Awake() + { + _listEntryTemplate = AssetManager.GetAsset($"spacewarp/modlist/modlistitem.uxml"); + _dependencyTemplate = AssetManager.GetAsset($"spacewarp/modlist/modlistdependency.uxml"); + + MainMenu.RegisterLocalizedMenuButton("SpaceWarp/Mods", ToggleWindow); + } + + private void OnEnable() + { + if (_isLoaded) + { + return; + } + + SetupDocument(); + InitializeElements(); + FillModLists(); + SetupToggles(); + SetupButtons(); + _isLoaded = true; + } + + private void Update() + { + if (Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.M)) + { + ToggleWindow(); + } + + if (_isWindowVisible && Input.GetKey(KeyCode.Escape)) + { + HideWindow(); + } + } + + private void SetupDocument() + { + var document = GetComponent(); + if (document.TryGetComponent(out var localization)) + { + localization.Localize(); + } + else + { + document.EnableLocalization(); + } + + _container = document.rootVisualElement; + + StartCoroutine(SetupWindow()); + } + + private IEnumerator SetupWindow() + { + yield return new WaitForFixedUpdate(); + + var root = _container.hierarchy[0]; + root.transform.position = new Vector3( + (Screen.width - root.boundingBox.width) / 2, + (Screen.height - root.boundingBox.height) / 2 + ); + + yield return new WaitForFixedUpdate(); + + _container.style.display = DisplayStyle.None; + } + + private void InitializeElements() + { + // Register a callback for the back button + _container.Q