From 24b08571b623d59ff2431ed91b7c7d845f6b9738 Mon Sep 17 00:00:00 2001 From: mszabo Date: Sat, 2 Nov 2024 05:11:28 +0100 Subject: [PATCH] Support arbitrary ammosets for siege artillery The vanilla logic around sieges is fairly inflexible -- it mostly assumes that only 81mm mortars are used, which effectively prevents using modded artillery in sieges without hacks like the "raider artillery" in our VFE:S patch that consumes the 81mm ammoset. So, rework our patches around sieges to support artillery pieces with arbitrary ammosets, and remove raider artillery. --- Defs/Ammo/Shell/155mmHowitzer.xml | 1 + .../Ammo_Security.xml | 12 -- .../NewRaiderArtillery.xml | 150 ------------------ .../ThingDefs_Manned.xml | 4 - .../CombatExtended/AmmoUtility.cs | 6 - .../Harmony/Harmony_LordToil_Siege.cs | 150 ++++++++++++------ .../Harmony/Harmony_ThingListGroupHelper.cs | 17 -- .../Harmony/Harmony_TurretGunUtility.cs | 109 +++++-------- 8 files changed, 144 insertions(+), 305 deletions(-) delete mode 100644 ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/NewRaiderArtillery.xml delete mode 100644 Source/CombatExtended/Harmony/Harmony_ThingListGroupHelper.cs diff --git a/Defs/Ammo/Shell/155mmHowitzer.xml b/Defs/Ammo/Shell/155mmHowitzer.xml index 553d3d57fb..be54ff53d2 100644 --- a/Defs/Ammo/Shell/155mmHowitzer.xml +++ b/Defs/Ammo/Shell/155mmHowitzer.xml @@ -125,6 +125,7 @@ Smoke Bullet_155mmHowitzerShell_Smoke + false diff --git a/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/Ammo_Security.xml b/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/Ammo_Security.xml index eb93a375cf..81fa8315f3 100644 --- a/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/Ammo_Security.xml +++ b/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/Ammo_Security.xml @@ -22,16 +22,4 @@ true - - - - AmmoSet_RaiderArtillery - - - Bullet_155mmHowitzerShell_HE - Bullet_155mmHowitzerShell_Incendiary - Bullet_81mmMortarShell_Tox - - - \ No newline at end of file diff --git a/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/NewRaiderArtillery.xml b/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/NewRaiderArtillery.xml deleted file mode 100644 index ce9ca84393..0000000000 --- a/ModPatches/Vanilla Furniture Expanded - Security/Defs/Vanilla Furniture Expanded - Security/NewRaiderArtillery.xml +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - Raider_Turret_Artillery - - A manned artillery that launches all kinds of shells. Capable of inflicting devastating damage to both enemy forces and structures, this military machinery excels at laying down fire support for soldiers in the field.\n\nCapable of targeting other areas of the world within a medium radius. - CombatExtended.Building_TurretGunCE - - Things/Building/Artillery/TurretArtillery_Base - Graphic_Single - (3,3) - - (0.38,0.2,0.56375,0.8) - Damage/Corner - - - (-0.13,-0.87,-0.1) - (0.5,0.4,1.05) - - - - 250 - 3600 - - Industrial - (0,0,-2) - true - (3,3) - - true - false - -
  • Metallic
  • -
    - 100 - - 180 - 9 - - - - Heavy - - false - Raider_Artillery_Weapon - 3 - -
  • Artillery_BaseDestroyer
  • -
    - 1 - 1 - - - -
  • MortarShells
  • -
    - -
  • Shell_Incendiary
  • -
  • Shell_Firefoam
  • -
  • Shell_AntigrainWarhead
  • -
    -
    -
    -
    - Things/Building/Artillery/TurretArtillery_MenuIcon - 0.9 - 5 - -
  • - True -
  • -
    - -
    - - - Raider_Artillery_Weapon - - Artillery cannon capable of long range bombardment. - - Things/Building/Artillery/TurretArtillery_Top - Graphic_Single - - - 0.5 - 8.5 - - -
  • Artillery_BaseDestroyer
  • -
  • TurretGun
  • -
    - -
  • - CombatExtended.Verb_ShootMortarCE - false - true - Bullet_155mmHowitzerShell_HE - 4 - 40 - 1000 - 1 - VFES_Shot_Artillery - 20 - 0.2 - - true - -
  • -
    - -
  • - -
  • 30
  • -
  • 50
  • -
  • 70
  • -
  • 90
  • - - -
  • - 1 - 12 - AmmoSet_RaiderArtillery -
  • -
    - - - - -
  • MortarShells
  • -
    -
    -
    - - - -
  • MortarShells
  • -
    - -
  • Shell_Firefoam
  • -
  • Shell_AntigrainWarhead
  • -
    -
    -
    -
    -
    - -
    \ No newline at end of file diff --git a/ModPatches/Vanilla Furniture Expanded - Security/Patches/Vanilla Furniture Expanded - Security/ThingDefs_Manned.xml b/ModPatches/Vanilla Furniture Expanded - Security/Patches/Vanilla Furniture Expanded - Security/ThingDefs_Manned.xml index 657d816ea8..9a188d0bc9 100644 --- a/ModPatches/Vanilla Furniture Expanded - Security/Patches/Vanilla Furniture Expanded - Security/ThingDefs_Manned.xml +++ b/ModPatches/Vanilla Furniture Expanded - Security/Patches/Vanilla Furniture Expanded - Security/ThingDefs_Manned.xml @@ -254,10 +254,6 @@ - - Defs/ThingDef[defName="VFES_Turret_Artillery"]/building/buildingTags - - Defs/ThingDef[defName="VFES_Turret_Artillery"]/building/turretBurstWarmupTime diff --git a/Source/CombatExtended/CombatExtended/AmmoUtility.cs b/Source/CombatExtended/CombatExtended/AmmoUtility.cs index 4d3b0e76cb..d0a0a77b24 100644 --- a/Source/CombatExtended/CombatExtended/AmmoUtility.cs +++ b/Source/CombatExtended/CombatExtended/AmmoUtility.cs @@ -126,12 +126,6 @@ public static string GetProjectileReadout(this ThingDef projectileDef, Thing wea /// public static float GetExplosionArmorPenetration(this CompProperties_ExplosiveCE props) => props.damageAmountBase * ExplosiveArmorPenetrationMultiplier; - public static bool IsShell(ThingDef def) - { - var ammo = ThingDefOf.Turret_Mortar.building.turretGunDef.GetCompProperties(); - return ammo?.ammoSet.ammoTypes.Any(l => l.ammo == def) ?? false; - } - public static bool IsAmmoSystemActive(AmmoDef def) { if (Controller.settings.EnableAmmoSystem) diff --git a/Source/CombatExtended/Harmony/Harmony_LordToil_Siege.cs b/Source/CombatExtended/Harmony/Harmony_LordToil_Siege.cs index 7200056022..b28f817985 100644 --- a/Source/CombatExtended/Harmony/Harmony_LordToil_Siege.cs +++ b/Source/CombatExtended/Harmony/Harmony_LordToil_Siege.cs @@ -5,69 +5,125 @@ using HarmonyLib; using RimWorld; using Verse; +using Verse.AI.Group; namespace CombatExtended.HarmonyCE { + /// + /// Transpile LordToil_Siege to support artillery pieces with different ammosets than the 81mm mortar. + /// [HarmonyPatch(typeof(LordToil_Siege), "LordToilTick")] internal static class Harmony_LordToil_Siege { - internal static IEnumerable Transpiler(IEnumerable instructions) + internal static IEnumerable Transpiler(IEnumerable instructions, + ILGenerator generator) { - var codes = new List(instructions); - var methodCustomCondition = typeof(Harmony_LordToil_Siege).GetMethod(nameof(CustomCondition), BindingFlags.Static | BindingFlags.Public); - var startIndex = -1; - var endIndex = -1; - MethodInfo targetMethod = AccessTools.Method(typeof(ThingDef), "get_IsShell"); // Start of the if condition - FieldInfo targetField = AccessTools.Field(typeof(DamageDef), "harmsHealth"); // End of the if condition - for (int i = 0; i < codes.Count; i++) - { - if (startIndex == -1) - { - // Find the start of the if statement - if (codes[i].opcode == OpCodes.Callvirt && codes[i].Calls(targetMethod)) - { - startIndex = i - 1; // We want to edit -1 instruction from the call of IsShell - } - } - else if (codes[i].opcode == OpCodes.Ldfld && codes[i].LoadsField(targetField)) - { - endIndex = i; - } - } - if (startIndex == -1 || endIndex == -1) - { - Log.Error("CombatExtended :: Harmony_LordToil_Siege couldn't find code block for patching"); - return codes; // Don't modify as we couldn't find the original code block - } - else - { - codes[startIndex] = new CodeInstruction(OpCodes.Call, methodCustomCondition); // Call the new method for evaluation - codes.RemoveRange(startIndex + 1, endIndex - startIndex); // Remove the default code - return codes; - } + var methodCustomCondition = typeof(Harmony_LordToil_Siege).GetMethod(nameof(IsValidShellType), + BindingFlags.Static | BindingFlags.Public); + var dropAdditionalShells = AccessTools.Method(typeof(Harmony_LordToil_Siege), nameof(DropAdditionalShells)); + + var isShellMethod = AccessTools.PropertyGetter(typeof(ThingDef), nameof(ThingDef.IsShell)); + var harmsHealthField = AccessTools.Field(typeof(DamageDef), nameof(DamageDef.harmsHealth)); + var tryFindRandomShellDefMethod = + AccessTools.Method(typeof(TurretGunUtility), nameof(TurretGunUtility.TryFindRandomShellDef)); + + var codeMatcher = new CodeMatcher(instructions, generator); + + var isShellPos = codeMatcher + .Start() + .MatchStartForward(CodeMatch.Calls(isShellMethod)) + .ThrowIfInvalid("CombatExtended :: Harmony_LordToil_Siege couldn't find call to IsShell") + .Pos; + + var harmsHealthPos = codeMatcher.MatchStartForward(CodeMatch.LoadsField(harmsHealthField)) + .ThrowIfInvalid("CombatExtended :: Harmony_LordToil_Siege couldn't find harmsHealth") + .Pos; + + // Consider all shell types usable by the siege when assessing how many are available to the siege + codeMatcher + .Advance(isShellPos - harmsHealthPos - 1) + .Insert( + CodeInstruction.LoadArgument(0), + new CodeInstruction(OpCodes.Call, methodCustomCondition) + ) + .RemoveInstructionsInRange(isShellPos + 1, harmsHealthPos + 2); + + var ifBlockStart = codeMatcher.Start() + .MatchStartForward(CodeMatch.Calls(tryFindRandomShellDefMethod)) + .ThrowIfInvalid("CombatExtended :: Harmony_LordToil_Siege couldn't find call to TryFindRandomShellDef") + .MatchEndBackwards(CodeMatch.Branches()) + .ThrowIfInvalid("CombatExtended :: Harmony_LordToil_Siege couldn't find start of enclosing if block") + .Pos; + + var ifBlockEndLabel = (Label)codeMatcher.Operand; + + // Drop shells for every type of artillery piece used by the siege + codeMatcher.MatchStartForward(new CodeMatch(instruction => instruction.labels.Contains(ifBlockEndLabel))) + .ThrowIfInvalid("CombatExtended :: Harmony_LordToil_Siege couldn't find end of enclosing if block") + .Insert( + CodeInstruction.LoadArgument(0), + new CodeInstruction(OpCodes.Call, dropAdditionalShells) + ) + .RemoveInstructionsInRange(ifBlockStart + 1, codeMatcher.Pos - 1); + + return codeMatcher.Instructions(); } - public static bool CustomCondition(Thing thing) + /// + /// Determine whether the given thing is a valid shell usable by this siege. + /// + /// The thing to check. + /// The siege. + /// true if at least one type of artillery piece taking part in the siege can use this shell, false otherwise. + public static bool IsValidShellType(Thing thing, LordToil_Siege siege) { - // Ensure check is the same here as from CombatExtended.HarmonyCE.Harmony_TurretGunUtility - var ammoDef = thing.def as AmmoDef; - if (ammoDef == null) + if (thing.def is AmmoDef { spawnAsSiegeAmmo: true } ammoDef) { - return false; + return UniqueMortarDefs(siege) + .SelectMany(def => def.building.turretGunDef.comps) + .OfType() + .SelectMany(props => props.ammoSet.ammoTypes) + .Any(ammoLink => ammoLink.ammo == ammoDef); } - // Ignore all non-shell defs. - if (ammoDef == null || !AmmoUtility.IsShell(ammoDef)) - { - return false; - } + return false; + } + + /// + /// Supply additional shells for each type of artillery piece taking part in this siege. + /// + /// The siege to resupply. + public static void DropAdditionalShells(LordToil_Siege siege) + { + Lord lord = siege.lord; - // Check if shell is blacklisted - if (!ammoDef.spawnAsSiegeAmmo) + foreach (var uniqueMortarDef in UniqueMortarDefs(siege)) { - return false; + var shellDef = TurretGunUtility.TryFindRandomShellDef( + uniqueMortarDef, + allowEMP: false, + allowToxGas: false, + mustHarmHealth: true, + lord.faction.def.techLevel, + allowAntigrainWarhead: false, + faction: lord.faction + ); + + if (shellDef != null) + { + siege.DropSupplies(shellDef, LordToil_Siege.ShellReplenishCount); + } } - return true; } + + /// + /// Get the unique artillery types taking part in this siege. + /// + /// The siege to get artillery types for. + /// Enumerable of unique artillery defs taking part in this siege. + private static IEnumerable UniqueMortarDefs(LordToil_Siege siege) => siege.lord.ownedBuildings + .Select(t => t.def) + .Where(def => def.building.buildingTags.Contains("Artillery")) + .Distinct(); } } diff --git a/Source/CombatExtended/Harmony/Harmony_ThingListGroupHelper.cs b/Source/CombatExtended/Harmony/Harmony_ThingListGroupHelper.cs deleted file mode 100644 index 0692338d76..0000000000 --- a/Source/CombatExtended/Harmony/Harmony_ThingListGroupHelper.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using Verse; - -namespace CombatExtended.HarmonyCE -{ - [HarmonyPatch(typeof(ThingListGroupHelper), "Includes")] - internal static class Harmony_ThingListGroupHelper - { - internal static IEnumerable Transpiler(IEnumerable instructions) - { - return instructions.MethodReplacer(typeof(ThingDef).GetMethod("get_IsShell"), - typeof(AmmoUtility).GetMethod(nameof(AmmoUtility.IsShell), BindingFlags.Public | BindingFlags.Static)); - } - } -} diff --git a/Source/CombatExtended/Harmony/Harmony_TurretGunUtility.cs b/Source/CombatExtended/Harmony/Harmony_TurretGunUtility.cs index 627366c629..382600831a 100644 --- a/Source/CombatExtended/Harmony/Harmony_TurretGunUtility.cs +++ b/Source/CombatExtended/Harmony/Harmony_TurretGunUtility.cs @@ -1,94 +1,65 @@ using System.Linq; using System.Collections.Generic; -using System.Collections; -using System.Reflection; using HarmonyLib; using RimWorld; using Verse; -// ReSharper disable InconsistentNaming -// ReSharper disable InlineOutVariableDeclaration -// ReSharper disable UsePatternMatching - namespace CombatExtended.HarmonyCE { - [HarmonyPatch] + /// + /// Transpile to support turrets other than the 81mm mortar. + /// + [HarmonyPatch(typeof(TurretGunUtility), nameof(TurretGunUtility.TryFindRandomShellDef))] public static class Harmony_TurretGunUtility { - const string className = "DisplayClass"; - const string methodName = ""; - - // This should be kept up to date with the check in CombatExtended.HarmonyCE.Harmony_LordToil_Siege - public static void Postfix(object __instance, ThingDef x, ref bool __result, bool ___allowEMP, float ___maxMarketValue) + public static bool Prefix( + ThingDef turret, + bool allowEMP, + bool allowToxGas, + TechLevel techLevel, + bool allowAntigrainWarhead, + ref ThingDef __result + ) { - // Ignore already true results. - if (__result) - { - return; - } - - var ammoDef = x as AmmoDef; - - // Ignore all non-shell defs. - if (ammoDef == null || !AmmoUtility.IsShell(ammoDef)) + if (!TurretGunUtility.NeedsShells(turret)) { - return; + __result = null; + return false; } - // Check if market value is within range. - if (___maxMarketValue >= 0.0f && ammoDef.BaseMarketValue > ___maxMarketValue) + // Fall back to the vanilla logic if we have no ammo configured for this turret (unpatched?) + var ammoUserProps = turret.building.turretGunDef.comps.OfType() + .FirstOrDefault(); + if (ammoUserProps == null) { - return; + return true; } - // Get the explosive damage def. - var explosiveDamageDef = ammoDef.GetCompProperties()?.explosiveDamageType ?? - ammoDef.GetCompProperties()?.explosiveDamageType; + IEnumerable potentialAmmoDefs = from ammoLink in ammoUserProps.ammoSet.ammoTypes + let ammoDef = ammoLink.ammo + where ammoDef.spawnAsSiegeAmmo + let projectileDef = ammoLink.projectile + let explosiveDamageDef = + projectileDef.GetCompProperties()?.explosiveDamageType ?? + projectileDef.GetCompProperties()?.explosiveDamageType + let projectileDamageDef = projectileDef.projectile.damageDef + where explosiveDamageDef != null || projectileDamageDef != null - // Get the projectile damage def via the mortar ammo set. - //var mortarAmmoSet = DefDatabase.GetNamed("AmmoSet_81mmMortarShell"); - var projectileDamageDef = ammoDef.projectile?.damageDef ?? CE_AmmoSetDefOf.AmmoSet_81mmMortarShell.ammoTypes.FirstOrDefault(t => t.ammo == ammoDef)?.projectile?.projectile?.damageDef; + // Only allow EMP or tox gas shells if explicitly allowed and relevant DLC is available + where allowEMP || (explosiveDamageDef != DamageDefOf.EMP && projectileDamageDef != DamageDefOf.EMP) + where (allowToxGas && ModsConfig.BiotechActive) || (explosiveDamageDef != DamageDefOf.ToxGas && + projectileDamageDef != DamageDefOf.ToxGas) - // Ignore shells that don't have damage defs. - if (explosiveDamageDef == null && projectileDamageDef == null) - { - return; - } + // No antigrain warheads + where allowAntigrainWarhead || ammoDef != ThingDefOf.Shell_AntigrainWarhead - // Ignore EMP if not allowed. - if (!___allowEMP && (explosiveDamageDef == DamageDefOf.EMP || projectileDamageDef == DamageDefOf.EMP)) - { - return; - } + // No higher tech shells than the tech level of the requesting faction + where techLevel == TechLevel.Undefined || ammoDef.techLevel <= techLevel + select ammoDef; - // Check if blacklisted - if (!ammoDef.spawnAsSiegeAmmo) - { - return; - } - - __result = true; - } - - public static MethodBase TargetMethod() - { - var classTargets = typeof(TurretGunUtility).GetNestedTypes(AccessTools.all) - .Where(x => x.Name.Contains(className)); - - if (!classTargets.Any()) - { - Log.Error("CombatExtended :: Harmony_TurretGunUtility couldn't find subclass with part `" + className + "`"); - } - - var methodTarget = classTargets.SelectMany(x => x.GetMethods(AccessTools.all)) - .FirstOrDefault(x => x.Name.Contains(methodName)); - - if (methodTarget == null) - { - Log.Error("CombatExtended :: Harmony_TurretGunUtility couldn't find method with part `" + methodName + "` in subclasses with part `" + className + "`"); - } + potentialAmmoDefs.TryRandomElement(out __result); - return methodTarget; + return false; } } }