diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f7fd1e..3e6ef4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,17 +12,29 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Download NuGet + id: download-nuget + run: | + sudo curl -o /usr/local/bin/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + + - name: Install jq + uses: dcarbone/install-jq-action@v2.1.0 + - name: Build the solution run: | + version=$(jq -r '.version' plugin_template/swinfo.json) + echo "Version is $version" dotnet build "CommunityFixes.sln" -c Release + echo "release_filename=CommunityFixes-$version.zip" >> $GITHUB_ENV echo "zip=$(ls -1 dist/CommunityFixes-*.zip | head -n 1)" >> $GITHUB_ENV + echo "upload_url=$(wget -qO- https://api.github.com/repos/$GITHUB_REPOSITORY/releases | jq '.[0].upload_url' | tr -d \")" >> $GITHUB_ENV - name: Upload zip to release - uses: actions/upload-release-asset@v1.0.1 + uses: shogo82148/actions-upload-release-asset@v1.7.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ env.upload_url }} asset_path: ${{ env.zip }} - asset_name: ${{ env.artifact_name }} - asset_content_type: application/zip \ No newline at end of file + asset_name: ${{ env.release_filename }} + asset_content_type: application/zip diff --git a/README.md b/README.md index 7d10295..cf4e398 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ This project aims to bring together community bug fixes for Kerbal Space Program - **Revert After Recovery Fix** by [munix](https://github.com/jan-bures) - Fixes the Revert buttons being enabled after recovering a vessel. - **Experiment Biome Pause Fix** by [dmarcuse](https://github.com/dmarcuse) - Fixes experiments that don't care about biome pausing when the biome changes. - **Stock Mission Fix** by [Cheese](https://github.com/cheese3660) - Fixes the incorrect completion conditions of the mission _Second Dibs: Gold Edition_. +- **Resource Manager UI Fix** by [munix](https://github.com/jan-bures) - Fixes the Resource Manager bug where moving a tank from the right pane back to the left pane caused it to duplicate. +- **Decoupled Craft Name Fix** by [munix](https://github.com/jan-bures) - Decoupled and docked/undocked vessels get names based on the original vessels instead of "Default Name" and "(Combined)". ## Planned fixes To see what fixes are planned to be implemented, you can visit the [Issues page](https://github.com/Bit-Studios/CommunityFixes/issues) on the project's GitHub. diff --git a/plugin_template/swinfo.json b/plugin_template/swinfo.json index 8b96eba..26366dc 100644 --- a/plugin_template/swinfo.json +++ b/plugin_template/swinfo.json @@ -5,7 +5,7 @@ "name": "Community Fixes", "description": "Community project that aims to bring together bug fixes for KSP 2.", "source": "https://github.com/KSP2Community/CommunityFixes", - "version": "0.10.0", + "version": "0.11.0", "version_check": "https://raw.githubusercontent.com/KSP2Community/CommunityFixes/main/plugin_template/swinfo.json", "ksp2_version": { "min": "0.2.0", diff --git a/src/CommunityFixes/CommunityFixesConfig.cs b/src/CommunityFixes/CommunityFixesConfig.cs deleted file mode 100644 index 2506db5..0000000 --- a/src/CommunityFixes/CommunityFixesConfig.cs +++ /dev/null @@ -1,30 +0,0 @@ -using BepInEx.Configuration; - -namespace CommunityFixes; - -public class CommunityFixesConfig -{ - private const string TogglesSection = "Toggle fixes"; - - private readonly Dictionary> _fixesEnabled = new(); - internal ConfigFile File { get; } - - public CommunityFixesConfig(ConfigFile fileFileFile) - { - File = fileFileFile; - } - - public bool LoadConfig(Type type, string name) - { - // If the toggle value for a fix class is already defined, we return it - if (_fixesEnabled.TryGetValue(type, out var isEnabled)) - { - return isEnabled.Value; - } - - // Otherwise create a new config entry for the fix class and return its default value (true) - var configEntry = File.Bind(TogglesSection, type.Name, true, name); - _fixesEnabled.Add(type, configEntry); - return configEntry.Value; - } -} \ No newline at end of file diff --git a/src/CommunityFixes/CommunityFixesMod.cs b/src/CommunityFixes/CommunityFixesMod.cs index ea472f4..cf646e9 100644 --- a/src/CommunityFixes/CommunityFixesMod.cs +++ b/src/CommunityFixes/CommunityFixesMod.cs @@ -16,7 +16,7 @@ public class CommunityFixesMod : BaseSpaceWarpPlugin [PublicAPI] public const string ModVer = MyPluginInfo.PLUGIN_VERSION; private static readonly Assembly Assembly = typeof(CommunityFixesMod).Assembly; - internal new static CommunityFixesConfig Config; + internal new static Configuration Config; private readonly List _fixes = new(); @@ -33,11 +33,11 @@ private void Awake() return; } - Config = new CommunityFixesConfig(base.Config); + Config = new Configuration(base.Config); foreach (var type in types) { - if (type.IsAbstract || !HasFixType(type)) + if (type.IsAbstract || !type.IsSubclassOf(typeof(BaseFix))) { continue; } @@ -66,9 +66,7 @@ public override void OnInitialized() private bool LoadFix(Type type) { - var fixName = GetFixName(type); - - if (!Config.LoadConfig(type, fixName)) + if (!Config.IsFixEnabled(type)) { return false; } @@ -84,40 +82,4 @@ private bool LoadFix(Type type) return true; } - - private static string GetFixName(Type type) - { - var attributes = Attribute.GetCustomAttributes(type); - foreach (var attribute in attributes) - { - if (attribute is FixAttribute fix) - { - return fix.Name; - } - } - - throw new Exception($"The attribute {typeof(FixAttribute).FullName} has to be declared on a fix class."); - } - - private static bool HasFixType(Type type) - { - if (type == null) - { - return false; - } - - // return all inherited types - var currentBaseType = type.BaseType; - while (currentBaseType != null) - { - if (currentBaseType == typeof(BaseFix)) - { - return true; - } - - currentBaseType = currentBaseType.BaseType; - } - - return false; - } } \ No newline at end of file diff --git a/src/CommunityFixes/Configuration.cs b/src/CommunityFixes/Configuration.cs new file mode 100644 index 0000000..d851735 --- /dev/null +++ b/src/CommunityFixes/Configuration.cs @@ -0,0 +1,55 @@ +using BepInEx.Configuration; +using CommunityFixes.Fix; + +namespace CommunityFixes; + +internal class Configuration +{ + private const string TogglesSection = "Toggle fixes"; + + private readonly Dictionary> _fixesEnabled = new(); + private readonly ConfigFile _file; + + /// + /// Creates a new config file object. + /// + /// The config file to use. + public Configuration(ConfigFile file) + { + _file = file; + } + + /// + /// Gets the toggle value for a fix class. + /// + /// Type of the fix class. + /// The toggle value for the fix class. + public bool IsFixEnabled(Type type) + { + // If the toggle value for a fix class is already defined, we return it + if (_fixesEnabled.TryGetValue(type, out var isEnabled)) + { + return isEnabled.Value; + } + + // Otherwise create a new config entry for the fix class and return its default value (true) + var metadata = FixAttribute.GetForType(type); + var configEntry = _file.Bind(TogglesSection, type.Name, true, metadata.Name); + _fixesEnabled.Add(type, configEntry); + return configEntry.Value; + } + + /// + /// Binds a config entry to a hack class. + /// + /// The hack class type. + /// The config entry key. + /// The config entry default value. + /// The config entry description. + /// The config entry type. + /// The config entry. + public ConfigEntry BindFixValue(Type hackType, string key, T defaultValue, string description = null) + { + return _file.Bind($"{hackType.Name} settings", key, defaultValue, description); + } +} \ No newline at end of file diff --git a/src/CommunityFixes/Fix/BaseFix.cs b/src/CommunityFixes/Fix/BaseFix.cs index 7133a6c..f6105a0 100644 --- a/src/CommunityFixes/Fix/BaseFix.cs +++ b/src/CommunityFixes/Fix/BaseFix.cs @@ -1,21 +1,54 @@ -using BepInEx.Logging; +using BepInEx.Configuration; using HarmonyLib; using KSP.Game; +using SpaceWarp.API.Logging; namespace CommunityFixes.Fix; +/// +/// Base class for all fixes. +/// public abstract class BaseFix : KerbalMonoBehaviour { - public virtual void OnInitialized() - { - } + private Configuration Config { get; } + /// + /// The logger for the fix. + /// + internal ILogger Logger { get; } + + /// + /// The harmony instance for the fix. + /// protected Harmony HarmonyInstance { get; } - internal ManualLogSource Logger { get; } + /// + /// Creates a new fix. + /// protected BaseFix() { - Logger = BepInEx.Logging.Logger.CreateLogSource($"CF/{GetType().Name}"); + Config = CommunityFixesMod.Config; + Logger = new BepInExLogger(BepInEx.Logging.Logger.CreateLogSource($"CF/{GetType().Name}")); HarmonyInstance = new Harmony(GetType().FullName); } + + /// + /// Binds a config entry to a fix class. + /// + /// The config entry key. + /// The config entry default value. + /// The config entry description. + /// The config entry type. + /// The config entry. + private protected ConfigEntry BindConfigValue(string key, T defaultValue, string description = null) + { + return Config.BindFixValue(GetType(), key, defaultValue, description); + } + + /// + /// Called when the fix is initialized. + /// + public virtual void OnInitialized() + { + } } \ No newline at end of file diff --git a/src/CommunityFixes/Fix/DecoupledCraftNameFix/DecoupledCraftNameFix.cs b/src/CommunityFixes/Fix/DecoupledCraftNameFix/DecoupledCraftNameFix.cs new file mode 100644 index 0000000..6328e7d --- /dev/null +++ b/src/CommunityFixes/Fix/DecoupledCraftNameFix/DecoupledCraftNameFix.cs @@ -0,0 +1,81 @@ +using System.Text.RegularExpressions; +using HarmonyLib; +using KSP.Messages; +using KSP.Sim.impl; + +namespace CommunityFixes.Fix.FairingEjectSidewaysFix; + +[Fix("Decoupled craft name fix")] +public class DecoupledCraftNameFix : BaseFix +{ + public override void OnInitialized() + { + Messages.Subscribe(msg => HandleDecoupleMessage((DecoupleMessage)msg)); + Messages.Subscribe(msg => HandleUndockMessage((VesselUndockedMessage)msg)); + + HarmonyInstance.PatchAll(typeof(DecoupledCraftNameFix)); + } + + private void HandleDecoupleMessage(DecoupleMessage decoupleMessage) + { + var part1Guid = new IGGuid(Guid.Parse(decoupleMessage.PartGuid)); + var part2Guid = decoupleMessage.OtherPartGuid; + + var vessel1 = Game.UniverseModel.FindPartComponent(part1Guid)?.PartOwner?.SimulationObject?.Vessel; + var vessel2 = Game.UniverseModel.FindPartComponent(part2Guid)?.PartOwner?.SimulationObject?.Vessel; + + HandleSeparationEvent(vessel1, vessel2); + } + + private void HandleUndockMessage(VesselUndockedMessage undockMessage) + { + VesselComponent vessel1 = undockMessage.VesselOne?.Model; + VesselComponent vessel2 = undockMessage.VesselTwo?.Model; + + HandleSeparationEvent(vessel1, vessel2); + } + + private void HandleSeparationEvent(VesselComponent vessel1, VesselComponent vessel2) + { + Logger.LogDebug($"Separated: {vessel1?.Name}, {vessel2?.Name}"); + + if (vessel2 is not { Name: var newName } || + !newName.StartsWith("Default Name") || + string.IsNullOrEmpty(vessel1?.Name)) + { + return; + } + + var match = Regex.Match(vessel1!.Name, @"-(\d+)$"); + newName = match.Success + ? Regex.Replace(vessel1.Name, @"-\d+$", $"-{int.Parse(match.Groups[1].Value) + 1}") + : $"{vessel1.Name}-2"; + + Logger.LogDebug($"Renaming {vessel2.Name} to {newName}"); + + vessel2.SimulationObject.Name = newName; + } + + [HarmonyPatch(typeof(SpaceSimulation), nameof(SpaceSimulation.CreateCombinedVesselSimObject))] + [HarmonyPrefix] + public static void CreateCombinedVesselSimObjectPrefix( + // ReSharper disable once InconsistentNaming + ref string __state, + VesselComponent masterVessel + ) + { + __state = masterVessel.Name; + } + + [HarmonyPatch(typeof(SpaceSimulation), nameof(SpaceSimulation.CreateCombinedVesselSimObject))] + [HarmonyPostfix] + public static void CreateCombinedVesselSimObjectPostfix( + // ReSharper disable once InconsistentNaming + string __state, + // ReSharper disable once InconsistentNaming + ref SimulationObjectModel __result + ) + { + __result.Name = __state; + } +} \ No newline at end of file diff --git a/src/CommunityFixes/Fix/FixAttribute.cs b/src/CommunityFixes/Fix/FixAttribute.cs index 20389f3..ef13703 100644 --- a/src/CommunityFixes/Fix/FixAttribute.cs +++ b/src/CommunityFixes/Fix/FixAttribute.cs @@ -1,12 +1,44 @@ -namespace CommunityFixes.Fix; +using System.Reflection; +namespace CommunityFixes.Fix; + +/// +/// Attribute for all fixes. +/// [AttributeUsage(AttributeTargets.Class)] public class FixAttribute: Attribute { + /// + /// The name of the fix displayed as description in the config file. + /// public string Name { get; } + /// + /// Creates a new fix attribute. + /// + /// The name of the fix displayed as description in the config file. public FixAttribute(string name) { Name = name; } + + /// + /// Gets the instance of the fix attribute on a type. + /// + /// The type to get the fix attribute from. + /// The fix attribute instance. + /// Thrown if the type does not have a fix attribute. + internal static FixAttribute GetForType(Type type) + { + try + { + return type.GetCustomAttribute(); + } + catch (Exception) + { + throw new Exception( + $"The attribute {typeof(FixAttribute).FullName} has to be declared on the class {type.FullName}." + ); + } + } } \ No newline at end of file diff --git a/src/CommunityFixes/Fix/ResourceManagerUIFix/ResourceManagerUIFix.cs b/src/CommunityFixes/Fix/ResourceManagerUIFix/ResourceManagerUIFix.cs new file mode 100644 index 0000000..d7714c4 --- /dev/null +++ b/src/CommunityFixes/Fix/ResourceManagerUIFix/ResourceManagerUIFix.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using KSP.UI; +using UnityEngine; + +namespace CommunityFixes.Fix.ResourceManagerUIFix; + +[Fix("Resource Manager UI Fix")] +public class ResourceManagerUIFix : BaseFix +{ + public override void OnInitialized() + { + HarmonyInstance.PatchAll(typeof(ResourceManagerUIFix)); + } + + [HarmonyPatch(typeof(ResourceManagerUI), nameof(ResourceManagerUI.Initialize))] + [HarmonyPostfix] + // ReSharper disable once InconsistentNaming + public static void PatchInitialize(ResourceManagerUI __instance) + { + __instance._partFamiliesTransform = GameObject.Find( + "/GameManager/Default Game Instance(Clone)/UI Manager(Clone)/Popup Canvas/ResourceManagerApp(Clone)/" + + "KSP2UIWindow/Root/Window-App/GRP-Body/MainContent/LeftContent/Parts List Section" + ).GetComponent(); + } +} \ No newline at end of file