From e3c4d656ba1d20604772d91c44481918a9b79a72 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Thu, 2 Apr 2026 23:27:31 +0200 Subject: [PATCH 01/12] fix: disable GetWorldObjectsInRange when player is in space In recent fixes we assured that space destination tiles are not scanned when checking for objects in range. But we forgot that player can also be in space and this creates huge log spam since scanning for objects in range for Alert is done all the time. Fix it in two places - in function polling and in Alert call. Signed-off-by: Jakub Raczynski --- Source/RimWar/Planet/WorldUtility.cs | 7 +++++++ Source/RimWar/Utility/Alert_NearbyRimWarObject.cs | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/RimWar/Planet/WorldUtility.cs b/Source/RimWar/Planet/WorldUtility.cs index 3d434ba..fd7f825 100644 --- a/Source/RimWar/Planet/WorldUtility.cs +++ b/Source/RimWar/Planet/WorldUtility.cs @@ -1381,6 +1381,13 @@ public static List GetWorldObjectsInRange(PlanetTile from, float ra { List tmpObjects = null; //List tmpObjects = Find.WorldObjects.AllWorldObjects; + + if (from.Layer == Find.WorldGrid.Orbit) + { + Log.WarningOnce("Attempting to GetWorldObjectsInRange while in space", 664799); + return null; + } + if (WorldObjectsHolder == null) CopyData(); lock (locker) diff --git a/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs b/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs index 8ee91ee..4904339 100644 --- a/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs +++ b/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs @@ -32,8 +32,14 @@ public List WONearbyResult woGTIList.Clear(); RimWar.Options.SettingsRef settingsRef = new Options.SettingsRef(); if (settingsRef.alertRange > 0) - { - foreach (WorldObject wo in WorldUtility.GetWorldObjectsInRange(Find.AnyPlayerHomeMap.Tile, settingsRef.alertRange)) + { + Map playerMap = Find.AnyPlayerHomeMap; + if (playerMap.Tile.Layer == Find.WorldGrid.Orbit) + { + return null; + } + + foreach (WorldObject wo in WorldUtility.GetWorldObjectsInRange(playerMap.Tile, settingsRef.alertRange)) { if (wo != null && wo.Faction != Faction.OfPlayer) { From 6a1f549f40b4786fa1857c5ff55a2d09ac897e76 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Sun, 5 Apr 2026 16:20:41 +0200 Subject: [PATCH 02/12] Revert "Further reduce settlements in range scan operations" This reverts commit ce005439fec1677ed27eb6bd34e55da0d0b0cab1. After considereation and testing, for big ranges and longer saves, having such low limit creates trend towards using caravans mostly, due to amount of friendly settlements in proximity. --- Source/RimWar/Planet/WorldUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/RimWar/Planet/WorldUtility.cs b/Source/RimWar/Planet/WorldUtility.cs index fd7f825..306bae2 100644 --- a/Source/RimWar/Planet/WorldUtility.cs +++ b/Source/RimWar/Planet/WorldUtility.cs @@ -21,7 +21,7 @@ public class WorldUtility private static WorldComponent_PowerTracker wcpt = null; // Do not scan literally everything, put some arbitrary limit - private const int maxObjectsPerScan = 5; + private const int maxObjectsPerScan = 20; public static WorldComponent_PowerTracker Get_WCPT() { From 7995ccfb821c108df6957ce00879a0e5d41129cc Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Sun, 5 Apr 2026 16:41:34 +0200 Subject: [PATCH 03/12] Optimize 'Nearby Activity' alert to not spam on every tick Alerts in Rimworld are updated every tick and there are some measures that need to be taken by mods. Generally AlertsReadoutUpdate does update values every 60 ticks, but that is still quite a lot. While solution in this commit is far from optimal, this improves performance a bit and makes notification readable. Signed-off-by: Jakub Raczynski --- .../Utility/Alert_NearbyRimWarObject.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs b/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs index 4904339..737e400 100644 --- a/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs +++ b/Source/RimWar/Utility/Alert_NearbyRimWarObject.cs @@ -8,12 +8,15 @@ using Verse; using RimWar.Planet; using UnityEngine; +using Verse.Noise; namespace RimWar.Utility { public class Alert_NearbyRimWarObject : Alert { private List woGTIList = new List(); + private int updateRate = 0; + private static readonly int refreshRate = 60; private List WOGTIList { get @@ -28,32 +31,38 @@ public List WONearbyResult { get { - woNearbyResult.Clear(); - woGTIList.Clear(); - RimWar.Options.SettingsRef settingsRef = new Options.SettingsRef(); - if (settingsRef.alertRange > 0) - { - Map playerMap = Find.AnyPlayerHomeMap; - if (playerMap.Tile.Layer == Find.WorldGrid.Orbit) - { - return null; - } + if (updateRate >= refreshRate) { + updateRate = 0; - foreach (WorldObject wo in WorldUtility.GetWorldObjectsInRange(playerMap.Tile, settingsRef.alertRange)) + woNearbyResult.Clear(); + woGTIList.Clear(); + RimWar.Options.SettingsRef settingsRef = new Options.SettingsRef(); + if (settingsRef.alertRange > 0) { - if (wo != null && wo.Faction != Faction.OfPlayer) + Map playerMap = Find.AnyPlayerHomeMap; + if (playerMap.Tile.Layer == Find.WorldGrid.Orbit) + { + return null; + } + + foreach (WorldObject wo in WorldUtility.GetWorldObjectsInRange(playerMap.Tile, settingsRef.alertRange)) { - List tmpList = WorldUtility.GetRimWarObjectsAt(wo.Tile); - if (tmpList != null && tmpList.Count > 0) + if (wo != null && wo.Faction != Faction.OfPlayer) { - foreach (WarObject warObj in tmpList) + List tmpList = WorldUtility.GetRimWarObjectsAt(wo.Tile); + if (tmpList != null && tmpList.Count > 0) { - woNearbyResult.Add(warObj); + foreach (WarObject warObj in tmpList) + { + woNearbyResult.Add(warObj); + } + woGTIList.Add(wo); } - woGTIList.Add(wo); } } - } + } + } else { + updateRate++; } List orderedList = woNearbyResult.OrderBy(name => name.Name).ToList(); From 6ccfd197ee293062f50b587cd935302dc74c8147 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Mon, 6 Apr 2026 17:57:32 +0200 Subject: [PATCH 04/12] Add defeat for factions without bases In Vanilla faction can be defeated if all its bases are destroyed. This only triggers for player actions so this does not work for RimWar. Defeated factions is highlighted in other way and cannot generate quests, quest raids and raids of course in Vanilla. Add this feature for RimWar, polling if faction has bases once in a while. Signed-off-by: Jakub Raczynski --- .../Planet/WorldComponent_PowerTracker.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs index b211e01..c53dd31 100644 --- a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs +++ b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs @@ -483,9 +483,9 @@ private static int CountOnPlanetFactions() return count; } - public void CheckForNewFactions() + private void CheckForNewFactions() { - if(WorldComponent_PowerTracker.CountOnPlanetFactions() > this.RimWarData.Count) + if (WorldComponent_PowerTracker.CountOnPlanetFactions() > this.RimWarData.Count) { Utility.RimWar_DebugToolsPlanet.ResetFactions(false, true); } @@ -727,8 +727,47 @@ public void UpdatePlayerAggression() this.minimumHeatForPlayerAction = Mathf.Clamp(this.minimumHeatForPlayerAction - Rand.RangeInclusive(1, 2), 0, 10000); } + private void CheckFactionDefeated() + { + /* This shouldn't be needed as Rimworld should mark faction as + * defeated by itself, but this does not seem to be a case. + * Usually faction can be only defeated by player destroying last + * settlement, but this will happen without player intervention. + * As such, mark faction as defeated ourselves if not already. + */ + List factionList = Find.World.factionManager.AllFactionsVisible.ToList(); + + for (int i = 0; i < factionList.Count; i++) + { + /* TODO Actually check both layers, but traders can be killed only by player currently. + * TODO This will also defeat "Refugee faction" but I find it neat, + * them coming to player defeated. Sadly RimWorld itself does not + * have pretty check for these factions. + * But need to fix this. + */ + if (factionList[i].IsPlayer || factionList[i] == Faction.OfTradersGuild) + continue; + + if (!Find.WorldObjects.AnyFactionSettlementOnRootSurface(factionList[i])) + { + if (factionList[i].defeated) + continue; + + factionList[i].defeated = true; + + Find.LetterStack.ReceiveLetter( + // TODO add translation + "Faction destroyed", + string.Format("All settlements of {0} have been destroyed.", factionList[i].Name), + LetterDefOf.PositiveEvent + ); + } + } + } + public void UpdateFactions() { + CheckFactionDefeated(); IncrementSettlementGrowth(); ReconstituteSettlements(); UpdateFactionSettlements(this.RimWarData.RandomElement()); From a9b98c21aaf988a6ef97d6041c9452037f0d163b Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Mon, 6 Apr 2026 18:04:11 +0200 Subject: [PATCH 05/12] Disable 'threading' option by default Threading still has many problems and we would need to rewrite a lot of logic for proper multithreading. Eg. current codebase does not use mutexes at all and is generally not secured enough to run multithreaded. Also RimWorld itself does change what can be run from main thread and what cannot, so it is better to disable it (by default only). Signed-off-by: Jakub Raczynski --- Source/RimWar/Options/Settings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/RimWar/Options/Settings.cs b/Source/RimWar/Options/Settings.cs index cc111bf..d17e634 100644 --- a/Source/RimWar/Options/Settings.cs +++ b/Source/RimWar/Options/Settings.cs @@ -30,7 +30,7 @@ public class Settings : Verse.ModSettings public float objectMovementMultiplier = 1f; //performance controls - public bool threadingEnabled = true; + public bool threadingEnabled = false; public int averageEventFrequency = 150; public int settlementEventDelay = 50000; public int settlementScanDelay = 60000; @@ -85,7 +85,7 @@ public override void ExposeData() Scribe_Values.Look(ref this.heatFrequency, "heatFrequency", 2500f, false); Scribe_Values.Look(ref this.settlementGrowthRate, "settlementGrowthRate", 1f, false); Scribe_Values.Look(ref this.noPermanentEnemies, "noPermanentEnemies", false, true); - Scribe_Values.Look(ref this.threadingEnabled, "threadingEnabled", true, true); + Scribe_Values.Look(ref this.threadingEnabled, "threadingEnabled", false, true); Scribe_Values.Look(ref this.vassalNotification, "vassalNotification", false, false); Scribe_Values.Look(ref this.alliedNotification, "alliedNotification", false, false); Scribe_Values.Look(ref this.allowDropPodRaids, "allowDropPodRaids", true, true); From cdcce3eee27c4145cd0dd1c86ca180e324c67cc5 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Tue, 7 Apr 2026 12:19:01 +0200 Subject: [PATCH 06/12] fix: Check if weak faction has parties before destroying Recently we added setting of 'defeated' flag for factions without settlements, but we missed option that there might be party on the map that captures settlement. Add check if all parties are dead before defeating faction. Signed-off-by: Jakub Raczynski --- Source/RimWar/Planet/WorldComponent_PowerTracker.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs index c53dd31..b582409 100644 --- a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs +++ b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs @@ -750,6 +750,11 @@ private void CheckFactionDefeated() if (!Find.WorldObjects.AnyFactionSettlementOnRootSurface(factionList[i])) { + // Check for active parties + List wosList = WorldUtility.GetRimWarDataForFaction(factionList[i]).FactionUnits; + if (wosList != null && wosList.Count > 0) + continue; + if (factionList[i].defeated) continue; From 2990d6e152fbda557ec8485816c1fef9964668d5 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Wed, 8 Apr 2026 18:52:04 +0200 Subject: [PATCH 07/12] Harmony: Refactor code for clearer input and further expansion Create separate function that allows passing prefix/sufix function without too much null passing. It makes code more clear and allows to add new patches easier. Also change some function names, there is bit of inconsequential naming of some function (missing prefix/sufix in function name). Signed-off-by: Jakub Raczynski --- Source/RimWar/Harmony/HarmonyPatches.cs | 229 ++++++++++++------------ 1 file changed, 114 insertions(+), 115 deletions(-) diff --git a/Source/RimWar/Harmony/HarmonyPatches.cs b/Source/RimWar/Harmony/HarmonyPatches.cs index de4f490..96ed8d1 100644 --- a/Source/RimWar/Harmony/HarmonyPatches.cs +++ b/Source/RimWar/Harmony/HarmonyPatches.cs @@ -29,12 +29,26 @@ namespace RimWar.Harmony [StaticConstructorOnStartup] public class RimWarMod : Mod { + private readonly string ModId = "rimworld.torann.rimwar"; public static int RimpointsPerGift = 90; private static readonly Type patchType = typeof(RimWarMod); + private HarmonyLib.Harmony harmonyInstance; + + private void PatchHarmonyMethod(Type rimworldMethodType, + string rimworldMethodName, + Type[] prms = null, + HarmonyMethod prefixMethod = null, + HarmonyMethod postfixMethod = null, + HarmonyMethod transpilerMethod = null) + { + harmonyInstance.Patch(AccessTools.Method(rimworldMethodType, rimworldMethodName, prms, null), + prefix: prefixMethod, postfix: postfixMethod, transpiler: transpilerMethod); + } + public RimWarMod(ModContentPack content) : base(content) { - HarmonyLib.Harmony harmonyInstance = new HarmonyLib.Harmony("rimworld.torann.rimwar"); + harmonyInstance = new HarmonyLib.Harmony(ModId); //Postfix //1.3 // //harmonyInstance.Patch(AccessTools.Method(typeof(TransportPodsArrivalAction_Shuttle), "Arrived", new Type[] @@ -42,50 +56,108 @@ public RimWarMod(ModContentPack content) : base(content) // typeof(List), // typeof(int) // }, null), null, new HarmonyMethod(patchType, "ShuttleArrived_SettlementHasAttackers_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(TransportersArrivalAction_AttackSettlement), "Arrived", new Type[] - { - typeof(List), - typeof(PlanetTile) - }, null), null, new HarmonyMethod(patchType, "PodsArrived_SettlementHasAttackers_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(RimWorld.Planet.SettlementUtility), "AttackNow", new Type[] - { - typeof(Caravan), - typeof(RimWorld.Planet.Settlement) - }, null), null, new HarmonyMethod(patchType, "AttackNow_SettlementReinforcement_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(Settlement), "GetInspectString", new Type[] - { - }, null), null, new HarmonyMethod(patchType, "Settlement_InspectString_WithPoints_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(Caravan_PathFollower), "StartPath", new Type[] - { - typeof(PlanetTile), - typeof(CaravanArrivalAction), - typeof(bool), - typeof(bool) - }, null), null, new HarmonyMethod(patchType, "Pather_StartPath_WarObjects", null), null); + + PatchHarmonyMethod(typeof(TransportersArrivalAction_AttackSettlement), + "Arrived", + new Type[]{ typeof(List), + typeof(PlanetTile) }, + postfixMethod: new HarmonyMethod(patchType, nameof(PodsArrived_SettlementHasAttackers_Postfix))); + PatchHarmonyMethod(typeof(RimWorld.Planet.SettlementUtility), + "AttackNow", + new Type[]{ typeof(Caravan), + typeof(RimWorld.Planet.Settlement) }, + postfixMethod: new HarmonyMethod(patchType, nameof(AttackNow_SettlementReinforcement_Postfix))); + PatchHarmonyMethod(typeof(Settlement), + "GetInspectString", + postfixMethod: new HarmonyMethod(patchType, nameof(Settlement_InspectString_WithPoints_Postfix))); + PatchHarmonyMethod(typeof(Caravan_PathFollower), "StartPath", + new Type[]{ typeof(PlanetTile), + typeof(CaravanArrivalAction), + typeof(bool), + typeof(bool) }, + postfixMethod: new HarmonyMethod(patchType, nameof(Pather_StartPath_WarObjects_Postfix))); + PatchHarmonyMethod(typeof(WorldSelectionDrawer), "DrawSelectionOverlays", + postfixMethod: new HarmonyMethod(patchType, nameof(WorldCapitolOverlay))); + PatchHarmonyMethod(typeof(CaravanEnterMapUtility), "Enter", + new Type[]{ typeof(Caravan), typeof(Map), + typeof(Func), + typeof(CaravanDropInventoryMode), + typeof(bool) }, + postfixMethod: new HarmonyMethod(patchType, nameof(AttackInjuredSettlement_Postfix))); + PatchHarmonyMethod(typeof(Settlement), "GetShuttleFloatMenuOptions", + new Type[]{ typeof(IEnumerable), + typeof(Action) }, + postfixMethod: new HarmonyMethod(patchType, nameof(Settlement_ShuttleReinforce_Postfix))); + PatchHarmonyMethod(typeof(ThingSetMaker), "Generate", + new Type[]{ typeof(ThingSetMakerParams) }, + postfixMethod: new HarmonyMethod(patchType, nameof(ThingSetMaker_TraderCheck_Postfix))); + PatchHarmonyMethod(typeof(FactionGiftUtility), "GiveGift", + new Type[]{ typeof(List), + typeof(Settlement) }, + prefixMethod: new HarmonyMethod(patchType, nameof(GivePodGiftAsRimWarPoints_Prefix))); + PatchHarmonyMethod(typeof(FactionGiftUtility), "GiveGift", + new Type[]{ typeof(List), + typeof(Faction), + typeof(GlobalTargetInfo) }, + prefixMethod: new HarmonyMethod(patchType, nameof(GiveGiftAsRimWarPoints_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker), "TryExecute", + new Type[]{ typeof(IncidentParms) }, + prefixMethod: new HarmonyMethod(patchType, nameof(IncidentWorker_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker_CaravanDemand), "ActionGive", + new Type[]{ typeof(Caravan), + typeof(List), + typeof(List) }, + prefixMethod: new HarmonyMethod(patchType, nameof(Caravan_Give_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker_NeutralGroup), "TryResolveParms", + new Type[]{ typeof(IncidentParms) }, + prefixMethod: new HarmonyMethod(patchType, nameof(TryResolveParms_Points_Prefix))); + PatchHarmonyMethod(typeof(CaravanExitMapUtility), "ExitMapAndCreateCaravan", + new Type[]{ typeof(IEnumerable), + typeof(Faction), + typeof(PlanetTile), + typeof(PlanetTile), + typeof(PlanetTile), + typeof(bool) }, + prefixMethod: new HarmonyMethod(patchType, nameof(ExitMapPostBattle_Prefix))); + PatchHarmonyMethod(typeof(IncidentQueue), "Add", + new Type[]{ typeof(IncidentDef), + typeof(int), + typeof(IncidentParms), + typeof(int) }, + prefixMethod: new HarmonyMethod(patchType, nameof(IncidentQueueAdd_Replacement_Prefix))); + PatchHarmonyMethod(typeof(FactionDialogMaker), "CallForAid", + new Type[]{ typeof(Map), typeof(Faction) }, + prefixMethod: new HarmonyMethod(patchType, nameof(CallForAid_Replacement_Patch))); + PatchHarmonyMethod(typeof(SymbolStack), "Push", + new Type[]{ typeof(string), + typeof(ResolveParams), + typeof(string) }, + prefixMethod: new HarmonyMethod(patchType, nameof(GenStep_Map_Params_Prefix))); + PatchHarmonyMethod(typeof(GenStep_Settlement), "ScatterAt", + new Type[]{ typeof(IntVec3), + typeof(Map), + typeof(GenStepParams), + typeof(int) }, + prefixMethod: new HarmonyMethod(patchType, nameof(GenStep_Map_ID_Prefix))); + + PatchHarmonyMethod(typeof(WorldPathPool), "GetEmptyWorldPath", + prefixMethod: new HarmonyMethod(patchType, nameof(WorldPathPool_Prefix_Patch))); + PatchHarmonyMethod(typeof(IncidentWorker_Ambush_EnemyFaction), "CanFireNowSub", + prefixMethod: new HarmonyMethod(patchType, nameof(CanFireNow_Ambush_EnemyFaction_RemovalPatch_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker_CaravanDemand), "CanFireNowSub", + prefixMethod: new HarmonyMethod(patchType, nameof(CanFireNow_CaravanDemand_RemovalPatch_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker_CaravanMeeting), "CanFireNowSub", + prefixMethod: new HarmonyMethod(patchType, nameof(CanFireNow_CaravanMeeting_RemovalPatch_Prefix))); + PatchHarmonyMethod(typeof(IncidentWorker_PawnsArrive), "CanFireNowSub", + prefixMethod: new HarmonyMethod(patchType, nameof(CanFireNow_PawnsArrive_RemovalPatch_Prefix))); + + harmonyInstance.PatchAll(); + //harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_CaravanMeeting), "RemoveAllPawnsAndPassToWorld", new Type[] // { // typeof(Caravan) // }, null), null, new HarmonyMethod(patchType, "Caravan_MoveOn_Prefix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(WorldSelectionDrawer), "DrawSelectionOverlays", new Type[] - { - }, null), null, new HarmonyMethod(patchType, "WorldCapitolOverlay", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(CaravanEnterMapUtility), "Enter", new Type[] - { - typeof(Caravan), - typeof(Map), - typeof(Func), - typeof(CaravanDropInventoryMode), - typeof(bool) - }, null), null, new HarmonyMethod(patchType, "AttackInjuredSettlement_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(Settlement), "GetShuttleFloatMenuOptions", new Type[] - { - typeof(IEnumerable), - typeof(Action) - }, null), null, new HarmonyMethod(patchType, "Settlement_ShuttleReinforce_Postfix", null), null); - harmonyInstance.Patch(AccessTools.Method(typeof(ThingSetMaker), "Generate", new Type[] - { - typeof(ThingSetMakerParams) - }, null), null, new HarmonyMethod(patchType, "ThingSetMaker_TraderCheck_Postfix", null), null); + //harmonyInstance.Patch(AccessTools.Method(typeof(PlaySettings), "DoPlaySettingsGlobalControls", new Type[] // { // typeof(WidgetRow), @@ -100,40 +172,6 @@ public RimWarMod(ModContentPack content) : base(content) //Prefix - harmonyInstance.Patch(AccessTools.Method(typeof(FactionGiftUtility), "GiveGift", new Type[] - { - typeof(List), - typeof(Settlement) - }, null), new HarmonyMethod(patchType, "GivePodGiftAsRimWarPoints_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(FactionGiftUtility), "GiveGift", new Type[] - { - typeof(List), - typeof(Faction), - typeof(GlobalTargetInfo) - }, null), new HarmonyMethod(patchType, "GiveGiftAsRimWarPoints_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker), "TryExecute", new Type[] - { - typeof(IncidentParms) - }, null), new HarmonyMethod(patchType, "IncidentWorker_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_CaravanDemand), "ActionGive", new Type[] - { - typeof(Caravan), - typeof(List), - typeof(List) - }, null), new HarmonyMethod(patchType, "Caravan_Give_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_NeutralGroup), "TryResolveParms", new Type[] - { - typeof(IncidentParms) - }, null), new HarmonyMethod(patchType, "TryResolveParms_Points_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(CaravanExitMapUtility), "ExitMapAndCreateCaravan", new Type[] - { - typeof(IEnumerable), - typeof(Faction), - typeof(PlanetTile), - typeof(PlanetTile), - typeof(PlanetTile), - typeof(bool) - }, null), new HarmonyMethod(patchType, "ExitMapPostBattle_Prefix", null), null, null); //Unused //harmonyInstance.Patch(AccessTools.Method(typeof(Faction), "TryAffectGoodwillWith", new Type[] // { @@ -144,45 +182,6 @@ public RimWarMod(ModContentPack content) : base(content) // typeof(HistoryEventDef), // typeof(GlobalTargetInfo?) // }, null), new HarmonyMethod(patchType, "TryAffectGoodwillWith_Reduction_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentQueue), "Add", new Type[] - { - typeof(IncidentDef), - typeof(int), - typeof(IncidentParms), - typeof(int) - }, null), new HarmonyMethod(patchType, "IncidentQueueAdd_Replacement_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(FactionDialogMaker), "CallForAid", new Type[] - { - typeof(Map), - typeof(Faction) - }, null), new HarmonyMethod(patchType, "CallForAid_Replacement_Patch", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(SymbolStack), "Push", new Type[] - { - typeof(string), - typeof(ResolveParams), - typeof(string) - }, null), new HarmonyMethod(patchType, "GenStep_Map_Params_Prefix", null), null, null); - harmonyInstance.Patch(AccessTools.Method(typeof(GenStep_Settlement), "ScatterAt", new Type[] - { - typeof(IntVec3), - typeof(Map), - typeof(GenStepParams), - typeof(int) - }, null), new HarmonyMethod(patchType, "GenStep_Map_ID_Prefix", null), null, null); - - harmonyInstance.Patch(AccessTools.Method(typeof(WorldPathPool), "GetEmptyWorldPath"), - prefix: new HarmonyMethod(patchType, nameof(WorldPathPool_Prefix_Patch))); - - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_Ambush_EnemyFaction), "CanFireNowSub"), - prefix: new HarmonyMethod(patchType, nameof(CanFireNow_Ambush_EnemyFaction_RemovalPatch_Prefix))); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_CaravanDemand), "CanFireNowSub"), - prefix: new HarmonyMethod(patchType, nameof(CanFireNow_CaravanDemand_RemovalPatch_Prefix))); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_CaravanMeeting), "CanFireNowSub"), - prefix: new HarmonyMethod(patchType, nameof(CanFireNow_CaravanMeeting_RemovalPatch_Prefix))); - harmonyInstance.Patch(AccessTools.Method(typeof(IncidentWorker_PawnsArrive), "CanFireNowSub"), - prefix: new HarmonyMethod(patchType, nameof(CanFireNow_PawnsArrive_RemovalPatch_Prefix))); - - harmonyInstance.PatchAll(); } //public static void WorldSettings_RimWarControls(PlaySettings __instance, ref WidgetRow row, bool worldView) @@ -641,7 +640,7 @@ private static void Postfix(FactionManager __instance, Faction faction) } } - public static void Pather_StartPath_WarObjects(Caravan_PathFollower __instance, Caravan ___caravan, PlanetTile destTile, CaravanArrivalAction arrivalAction, ref bool __result, bool repathImmediately = false, bool resetPauseStatus = true) + public static void Pather_StartPath_WarObjects_Postfix(Caravan_PathFollower __instance, Caravan ___caravan, PlanetTile destTile, CaravanArrivalAction arrivalAction, ref bool __result, bool repathImmediately = false, bool resetPauseStatus = true) { if (__result == true) { From 7750ca7b9417ee1d77856a7b61073906b10d65af Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Wed, 8 Apr 2026 20:18:00 +0200 Subject: [PATCH 08/12] Harmony: Add check for incompatible mods People love complaining 'RimWar is broken' while having >800 random mods (true story). We cannot and WILL NOT support some random mods. This commit purpose is to add 'big red warning' that some mods are breaking RimWar. It will work only when RimWar is loaded after them though. One of examples used for testing is VEF (Vanilla Expanded Framework) and VVE (Vanillla Vehicles Expanded). Signed-off-by: Jakub Raczynski --- Source/RimWar/Harmony/HarmonyPatches.cs | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Source/RimWar/Harmony/HarmonyPatches.cs b/Source/RimWar/Harmony/HarmonyPatches.cs index 96ed8d1..0030861 100644 --- a/Source/RimWar/Harmony/HarmonyPatches.cs +++ b/Source/RimWar/Harmony/HarmonyPatches.cs @@ -26,6 +26,7 @@ namespace RimWar.Harmony // static HarmonyPatches() // { + [StaticConstructorOnStartup] public class RimWarMod : Mod { @@ -42,8 +43,31 @@ private void PatchHarmonyMethod(Type rimworldMethodType, HarmonyMethod postfixMethod = null, HarmonyMethod transpilerMethod = null) { - harmonyInstance.Patch(AccessTools.Method(rimworldMethodType, rimworldMethodName, prms, null), - prefix: prefixMethod, postfix: postfixMethod, transpiler: transpilerMethod); + MethodInfo targetMethod = AccessTools.Method(rimworldMethodType, rimworldMethodName, prms, null); + Patches info = HarmonyLib.Harmony.GetPatchInfo(targetMethod); + if (info != null) { + foreach (Patch patch in info.Prefixes) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + patch.owner, targetMethod.Name)); + } + foreach (Patch patch in info.Postfixes) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + patch.owner, targetMethod.Name)); + } + foreach (Patch patch in info.Transpilers) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + patch.owner, targetMethod.Name)); + } + } + + harmonyInstance.Patch(targetMethod, prefix: prefixMethod, + postfix: postfixMethod, transpiler: transpilerMethod); } public RimWarMod(ModContentPack content) : base(content) From 0fdc250840fc4440836661e91bfa6d95370f983c Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Wed, 8 Apr 2026 20:21:28 +0200 Subject: [PATCH 09/12] Harmony: Fix log format Remove leftover '$' signs, forgot how strings work in c#. Signed-off-by: Jakub Raczynski --- Source/RimWar/Harmony/HarmonyPatches.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/RimWar/Harmony/HarmonyPatches.cs b/Source/RimWar/Harmony/HarmonyPatches.cs index 0030861..2b1b44d 100644 --- a/Source/RimWar/Harmony/HarmonyPatches.cs +++ b/Source/RimWar/Harmony/HarmonyPatches.cs @@ -49,19 +49,19 @@ private void PatchHarmonyMethod(Type rimworldMethodType, foreach (Patch patch in info.Prefixes) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } foreach (Patch patch in info.Postfixes) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } foreach (Patch patch in info.Transpilers) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod ${0} also patches ${1}.", + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } } From b3b6a8ac2b43b894dabc648f09ac7269e3d57517 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Wed, 8 Apr 2026 20:25:33 +0200 Subject: [PATCH 10/12] Harmony; narrow down incompatibility log Original implementation of incompatible mods has not been true in all cases, as we were checking all methods but patching only one usually. This has been fixed by simple check which method is being parsed. Signed-off-by: Jakub Raczynski --- Source/RimWar/Harmony/HarmonyPatches.cs | 36 ++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Source/RimWar/Harmony/HarmonyPatches.cs b/Source/RimWar/Harmony/HarmonyPatches.cs index 2b1b44d..89dfd42 100644 --- a/Source/RimWar/Harmony/HarmonyPatches.cs +++ b/Source/RimWar/Harmony/HarmonyPatches.cs @@ -46,23 +46,29 @@ private void PatchHarmonyMethod(Type rimworldMethodType, MethodInfo targetMethod = AccessTools.Method(rimworldMethodType, rimworldMethodName, prms, null); Patches info = HarmonyLib.Harmony.GetPatchInfo(targetMethod); if (info != null) { - foreach (Patch patch in info.Prefixes) - { - if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", - patch.owner, targetMethod.Name)); + if (prefixMethod != null) { + foreach (Patch patch in info.Prefixes) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + patch.owner, targetMethod.Name)); + } } - foreach (Patch patch in info.Postfixes) - { - if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", - patch.owner, targetMethod.Name)); + if (postfixMethod != null) { + foreach (Patch patch in info.Postfixes) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + patch.owner, targetMethod.Name)); + } } - foreach (Patch patch in info.Transpilers) - { - if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", - patch.owner, targetMethod.Name)); + if (transpilerMethod != null) { + foreach (Patch patch in info.Transpilers) + { + if (patch.owner != ModId) + Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + patch.owner, targetMethod.Name)); + } } } From dd88f7a2894b7bbc5c28cf0ca8564d8b8e0aeefd Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Wed, 8 Apr 2026 21:00:23 +0200 Subject: [PATCH 11/12] Harmony: Downgrade mod incompatibility error to warning Frankly I would gladly leave it as error to be visible, but change it to warning, as patching will process further, even with incompatible stuff. Signed-off-by: Jakub Raczynski --- Source/RimWar/Harmony/HarmonyPatches.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/RimWar/Harmony/HarmonyPatches.cs b/Source/RimWar/Harmony/HarmonyPatches.cs index 89dfd42..09c20f3 100644 --- a/Source/RimWar/Harmony/HarmonyPatches.cs +++ b/Source/RimWar/Harmony/HarmonyPatches.cs @@ -50,7 +50,7 @@ private void PatchHarmonyMethod(Type rimworldMethodType, foreach (Patch patch in info.Prefixes) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + Log.Warning(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } } @@ -58,7 +58,7 @@ private void PatchHarmonyMethod(Type rimworldMethodType, foreach (Patch patch in info.Postfixes) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + Log.Warning(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } } @@ -66,7 +66,7 @@ private void PatchHarmonyMethod(Type rimworldMethodType, foreach (Patch patch in info.Transpilers) { if (patch.owner != ModId) - Log.Error(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", + Log.Warning(string.Format("[RimWar] Possible mod conflict detected: Mod {0} also patches {1}.", patch.owner, targetMethod.Name)); } } From 8b219f8c74a305921a2f7d7b9fb6ab4c94d54162 Mon Sep 17 00:00:00 2001 From: Jakub Raczynski Date: Thu, 9 Apr 2026 23:26:12 +0200 Subject: [PATCH 12/12] fix: Ignore temporary factions when checking if defeated Signed-off-by: Jakub Raczynski --- Source/RimWar/Planet/WorldComponent_PowerTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs index b582409..f1048da 100644 --- a/Source/RimWar/Planet/WorldComponent_PowerTracker.cs +++ b/Source/RimWar/Planet/WorldComponent_PowerTracker.cs @@ -745,7 +745,7 @@ private void CheckFactionDefeated() * have pretty check for these factions. * But need to fix this. */ - if (factionList[i].IsPlayer || factionList[i] == Faction.OfTradersGuild) + if (factionList[i].IsPlayer || factionList[i] == Faction.OfTradersGuild || factionList[i].temporary) continue; if (!Find.WorldObjects.AnyFactionSettlementOnRootSurface(factionList[i]))