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;