From 369e4acd5459e044ffc30c2eacc77e0d992d90dc Mon Sep 17 00:00:00 2001 From: siimav <1120038+siimav@users.noreply.github.com> Date: Fri, 8 May 2026 02:24:33 +0300 Subject: [PATCH 1/3] Prevent KSP from needlessly generating contracts --- .../ContractConfigurator.csproj | 1 + .../ContractConfigurator/Harmony/Contract.cs | 33 +++++++++++++++++++ .../Harmony/ContractSystem.cs | 17 ++++++++++ 3 files changed, 51 insertions(+) create mode 100644 source/ContractConfigurator/Harmony/Contract.cs diff --git a/source/ContractConfigurator/ContractConfigurator.csproj b/source/ContractConfigurator/ContractConfigurator.csproj index bfe294de..30a2863d 100644 --- a/source/ContractConfigurator/ContractConfigurator.csproj +++ b/source/ContractConfigurator/ContractConfigurator.csproj @@ -139,6 +139,7 @@ + diff --git a/source/ContractConfigurator/Harmony/Contract.cs b/source/ContractConfigurator/Harmony/Contract.cs new file mode 100644 index 00000000..ff76efac --- /dev/null +++ b/source/ContractConfigurator/Harmony/Contract.cs @@ -0,0 +1,33 @@ +using Contracts; +using HarmonyLib; +using System; +using System.Collections; +using System.Threading.Tasks; + +namespace ContractConfigurator.Harmony +{ + [HarmonyPatch(typeof(Contract))] + internal class PatchContract + { + /// + /// KSP's ContractSystem calls Contract.Generate() with State.Generated when filling the offered contracts. + /// ConfiguredContract.Generate() always returns false for these calls (contractType is unset on fresh instances) so each attempt is a wasted allocation. + /// Short-circuit here before Activator.CreateInstance is reached. The pre-loader uses State.Withdrawn, which is allowed through. + /// + /// + /// + /// + /// + [HarmonyPrefix] + [HarmonyPatch("Generate", new Type[] { typeof(Type), typeof(Contract.ContractPrestige), typeof(int), typeof(Contract.State) })] + internal static bool Prefix_Generate(ref Contract __result, Type contractType, Contract.State state) + { + if (contractType == typeof(ConfiguredContract) && state == Contract.State.Generated) + { + __result = null; + return false; + } + return true; + } + } +} diff --git a/source/ContractConfigurator/Harmony/ContractSystem.cs b/source/ContractConfigurator/Harmony/ContractSystem.cs index fe78fbfd..30a59e37 100644 --- a/source/ContractConfigurator/Harmony/ContractSystem.cs +++ b/source/ContractConfigurator/Harmony/ContractSystem.cs @@ -41,6 +41,23 @@ internal static void PostfixAction(ConfigNode gameNode) } } + /// + /// Make KSP count pre-loaded CC contracts toward the offered-contract quota + /// + /// + /// + [HarmonyPostfix] + [HarmonyPatch("CountContracts")] + internal static void Postfix_CountContracts(ref int __result, Contract.ContractPrestige difficulty) + { + if (ContractPreLoader.Instance == null) return; + foreach (ConfiguredContract c in ContractPreLoader.Instance.PendingContracts()) + { + if (c.Prestige == difficulty) + __result++; + } + } + private class PostfixEnumerator : IEnumerable { public IEnumerator enumerator; From 850c14ec79fc62e204fa1c5f6b647651c7e43e12 Mon Sep 17 00:00:00 2001 From: siimav <1120038+siimav@users.noreply.github.com> Date: Fri, 8 May 2026 03:21:23 +0300 Subject: [PATCH 2/3] Make ContractPreLoader.PendingContracts() return the same list --- .../ScenarioModules/ContractPreLoader.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/ContractConfigurator/ScenarioModules/ContractPreLoader.cs b/source/ContractConfigurator/ScenarioModules/ContractPreLoader.cs index ace1864a..116aae58 100644 --- a/source/ContractConfigurator/ScenarioModules/ContractPreLoader.cs +++ b/source/ContractConfigurator/ScenarioModules/ContractPreLoader.cs @@ -29,7 +29,7 @@ public class ContractPreLoader : ScenarioModule private static System.Random rand = new System.Random(); private static int nextContractGroup = rand.Next(); - private List contracts = new List(); + private readonly List contracts = new List(); private string lastKey = null; private double lastGenerationFailure; @@ -376,7 +376,7 @@ public override void OnSave(ConfigNode node) { try { - foreach (ConfiguredContract contract in contracts.Where(c => c.ContractState == Contract.State.Offered)) + foreach (ConfiguredContract contract in contracts) { ConfigNode child = new ConfigNode("CONTRACT"); node.AddNode(child); @@ -436,9 +436,15 @@ public override void OnLoad(ConfigNode node) } } - public IEnumerable PendingContracts() + /// + /// Contracts that have been generated but not yet accepted. Not all of may be presented to the player. + /// Do not edit the returned collection directly. + /// + /// + public List PendingContracts() { - return contracts.Where(c => c.ContractState == Contract.State.Offered); + // contracts only ever holds State.Offered contracts + return contracts; } } } From cd76ba272b62f94ef70dcbde6ec0eac41bb64ba2 Mon Sep 17 00:00:00 2001 From: siimav <1120038+siimav@users.noreply.github.com> Date: Sat, 9 May 2026 03:41:52 +0300 Subject: [PATCH 3/3] Skip weighted random selection when ConfiguredContract is the only registered contract type --- .../Harmony/ContractSystem.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source/ContractConfigurator/Harmony/ContractSystem.cs b/source/ContractConfigurator/Harmony/ContractSystem.cs index 30a59e37..d62da850 100644 --- a/source/ContractConfigurator/Harmony/ContractSystem.cs +++ b/source/ContractConfigurator/Harmony/ContractSystem.cs @@ -58,6 +58,22 @@ internal static void Postfix_CountContracts(ref int __result, Contract.ContractP } } + /// + /// Skip weighted random selection when ConfiguredContract is the only registered contract type. + /// + [HarmonyPrefix] + [HarmonyPatch("WeightedContractChoice")] + internal static bool Prefix_WeightedContractChoice(ref Type __result) + { + if (ContractSystem.ContractTypes?.Count == 1 && + ContractSystem.ContractTypes[0] == typeof(ConfiguredContract)) + { + __result = typeof(ConfiguredContract); + return false; + } + return true; + } + private class PostfixEnumerator : IEnumerable { public IEnumerator enumerator;