diff --git a/Patch_CombatExtended/1.4/Patches/CE_Melee_Weebstick.xml b/Patch_CombatExtended/1.4/Patches/CE_Melee_Weebstick.xml
new file mode 100644
index 00000000..ddea554d
--- /dev/null
+++ b/Patch_CombatExtended/1.4/Patches/CE_Melee_Weebstick.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+ /Defs/ThingDef[defName="AM_Weebstick"]/tools
+
+
+
+
+
+ Stab
+
+ 25
+ 1.8
+ 0.9
+ 22
+ 29
+ Point
+
+
+
+
+ Cut
+
+ 28
+ 2
+ 0.50
+ 20
+ 27
+ Edge
+
+
+
+
+
+
+ /Defs/ThingDef[defName="AM_Weebstick"]/statBases
+
+ 15
+ 1.1
+
+
+
+
+ Defs/ThingDef[defName="AM_Weebstick"]/equippedStatOffsets
+
+ 0.8
+ 0.9
+ 0.55
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/AnimationMod.sln b/Source/AnimationMod.sln
index c2147123..a2b2aea5 100644
--- a/Source/AnimationMod.sln
+++ b/Source/AnimationMod.sln
@@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompatibilityReportGenerato
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CAI5000Patch", "CAI5000Patch\CAI5000Patch.csproj", "{0B91E675-3505-4587-94BB-6FEFC5ADB24D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CombatExtendedPatch", "CombatExtendedPatch\CombatExtendedPatch.csproj", "{BBB0D084-8A32-458E-8A7F-48E7D90F0489}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
v1.4|Any CPU = v1.4|Any CPU
@@ -39,6 +41,8 @@ Global
{67701E3B-9094-4CA2-9DD8-42EBAFCB9F4A}.v1.4|Any CPU.Build.0 = Debug|Any CPU
{0B91E675-3505-4587-94BB-6FEFC5ADB24D}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
{0B91E675-3505-4587-94BB-6FEFC5ADB24D}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
+ {BBB0D084-8A32-458E-8A7F-48E7D90F0489}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
+ {BBB0D084-8A32-458E-8A7F-48E7D90F0489}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Source/CombatExtendedPatch/CombatExtendedOutcomeWorker.cs b/Source/CombatExtendedPatch/CombatExtendedOutcomeWorker.cs
new file mode 100644
index 00000000..26a89ce0
--- /dev/null
+++ b/Source/CombatExtendedPatch/CombatExtendedOutcomeWorker.cs
@@ -0,0 +1,77 @@
+using AM.Outcome;
+using CombatExtended;
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace AM.CombatExtendedPatch;
+
+public sealed class CombatExtendedOutcomeWorker : IOutcomeWorker
+{
+ public IEnumerable GetMeleeAttacksFor(ThingWithComps weapon, Pawn pawn)
+ {
+ var comp = weapon?.GetComp();
+ if (comp == null)
+ yield break;
+
+ foreach (var verb in comp.AllVerbs)
+ {
+ if (!verb.IsMeleeAttack)
+ continue;
+
+ float dmg = GetDamage(weapon, verb, pawn);
+ float ap = GetPen(weapon, verb, pawn);
+
+ yield return new PossibleMeleeAttack
+ {
+ Damage = dmg,
+ ArmorPen = ap,
+ Pawn = pawn,
+ DamageDef = verb.GetDamageDef(),
+ Verb = verb,
+ Weapon = weapon
+ };
+ }
+ }
+
+ public float GetPen(ThingWithComps weapon, Verb verb, Pawn attacker)
+ {
+ var tool = verb.tool as ToolCE;
+ if (tool == null)
+ return verb.tool.armorPenetration * GetPenetrationFactor(weapon);
+
+ var isBlunt = verb.GetDamageDef()?.armorCategory?.armorRatingStat == StatDefOf.ArmorRating_Blunt;
+ if (isBlunt)
+ return tool.armorPenetrationBlunt * GetPenetrationFactor(weapon);
+ return tool.armorPenetrationSharp * GetPenetrationFactor(weapon);
+ }
+
+ public float GetDamage(ThingWithComps weapon, Verb verb, Pawn attacker)
+ => verb.verbProps.AdjustedMeleeDamageAmount(verb.tool, attacker, weapon, verb.HediffCompSource);
+
+ private float GetPenetrationFactor(Thing weapon)
+ => weapon?.GetStatValue(CE_StatDefOf.MeleePenetrationFactor) ?? 1f;
+
+ public float GetChanceToPenAprox(Pawn pawn, BodyPartRecord bodyPart, StatDef armorType, float armorPen)
+ {
+ // Get skin & hediff chance-to-pen.
+ float armor = pawn.GetStatValue(armorType);
+
+ if (pawn.apparel != null)
+ {
+ // Get apparel chance-to-pen.
+ foreach (var a in pawn.apparel.WornApparel)
+ {
+ if (!a.def.apparel.CoversBodyPart(bodyPart))
+ continue;
+
+ armor += a.GetStatValue(armorType);
+ }
+ }
+
+ // 75% of the required pen gives 0% pen chance, increasing to 100% at 100% required pen.
+ // Not perfect, but a reasonable approximation.
+ float rawPct = armor <= 0f ? 1f : armorPen / armor;
+ return OutcomeUtility.RemapClamped(0.75f, 1f, 0f, 1f, rawPct);
+ }
+}
\ No newline at end of file
diff --git a/Source/CombatExtendedPatch/CombatExtendedPatch.csproj b/Source/CombatExtendedPatch/CombatExtendedPatch.csproj
new file mode 100644
index 00000000..f3e84e9d
--- /dev/null
+++ b/Source/CombatExtendedPatch/CombatExtendedPatch.csproj
@@ -0,0 +1,54 @@
+
+
+
+ net472
+ Library
+ preview
+ false
+ true
+ false
+ false
+ v1.4
+ AM.CombatExtendedPatch
+ zz.AM.CombatExtendedPatch
+ disable
+ true
+ none
+
+
+
+
+
+
+
+
+ False
+ False
+ all
+
+
+
+
+
+
+ 1.4.3641
+
+
+
+
+ CombatExtended_14.dll
+ False
+ False
+ runtime
+
+
+
+
+
+ none
+ ..\..\Patch_CombatExtended\1.4\Assemblies\
+ true
+ TRACE;V14
+
+
+
diff --git a/Source/CombatExtendedPatch/CombatExtended_14.dll b/Source/CombatExtendedPatch/CombatExtended_14.dll
new file mode 100644
index 00000000..6cd91e20
Binary files /dev/null and b/Source/CombatExtendedPatch/CombatExtended_14.dll differ
diff --git a/Source/CombatExtendedPatch/PatchCore.cs b/Source/CombatExtendedPatch/PatchCore.cs
new file mode 100644
index 00000000..9bf3ded6
--- /dev/null
+++ b/Source/CombatExtendedPatch/PatchCore.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using Verse;
+
+namespace AM.CombatExtendedPatch;
+
+[UsedImplicitly]
+[HotSwapAll]
+public class PatchCore : Mod
+{
+ public static void Log(string msg)
+ {
+ Core.Log($"[CE Patch] {msg}");
+ }
+
+ public PatchCore(ModContentPack content) : base(content)
+ {
+ // Replace the vanilla outcome worker with the combat extended one,
+ // which uses the combat extended armor system.
+ OutcomeUtility.OutcomeWorker = new CombatExtendedOutcomeWorker();
+
+ Log("Loaded and applied CE patch.");
+ }
+}
\ No newline at end of file
diff --git a/Source/ThingGenerator/AnimationMod.csproj b/Source/ThingGenerator/AnimationMod.csproj
index bd7aa4d8..250a4966 100644
--- a/Source/ThingGenerator/AnimationMod.csproj
+++ b/Source/ThingGenerator/AnimationMod.csproj
@@ -1,4 +1,4 @@
-
+
diff --git a/Source/ThingGenerator/Core.cs b/Source/ThingGenerator/Core.cs
index 4e06a8d6..1890bc59 100644
--- a/Source/ThingGenerator/Core.cs
+++ b/Source/ThingGenerator/Core.cs
@@ -1,22 +1,22 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using AM.Retexture;
+using AM.AMSettings;
using AM.Patches;
+using AM.Retexture;
using AM.Tweaks;
+using GistAPI;
+using GistAPI.Models;
using HarmonyLib;
using ModRequestAPI;
using RimWorld;
-using UnityEngine;
-using Verse;
+using System;
+using System.Collections.Generic;
using System.Globalization;
-using System.Reflection;
-using AM.AMSettings;
using System.IO;
-using GistAPI.Models;
-using GistAPI;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using Verse;
namespace AM
{
@@ -135,7 +135,6 @@ public Core(ModContentPack content) : base(content)
{
AnimRenderer.DefaultCutout ??= new Material(ThingDefOf.AIPersonaCore.graphic.Shader);
AnimRenderer.DefaultTransparent ??= new Material(ShaderTypeDefOf.Transparent.Shader);
- //AnimRenderer.DefaultTransparent ??= new Material(DefDatabase.GetNamed("Mote").Shader);
});
AddLateLoadAction(false, "Checking for Simple Sidearms install...", CheckSimpleSidearms);
@@ -149,6 +148,7 @@ public Core(ModContentPack content) : base(content)
AddLateLoadAction(true, "Matching textures with mods...", PreCacheAllRetextures);
AddLateLoadAction(true, "Loading weapon tweak data...", LoadAllTweakData);
AddLateLoadAction(true, "Patch VBE", PatchVBE);
+ AddLateLoadAction(true, "Apply final patches", Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.PatchAll);
AddLateLoadEvents();
}
diff --git a/Source/ThingGenerator/Idle/IdleControllerComp.cs b/Source/ThingGenerator/Idle/IdleControllerComp.cs
index b94e1a86..922e1ac0 100644
--- a/Source/ThingGenerator/Idle/IdleControllerComp.cs
+++ b/Source/ThingGenerator/Idle/IdleControllerComp.cs
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using AM.AMSettings;
+using AM.Patches;
using AM.Processing;
using AM.Tweaks;
using JetBrains.Annotations;
using RimWorld;
using UnityEngine;
using Verse;
-using Patch = AM.Patches.Patch_Verb_MeleeAttackDamage_ApplyMeleeDamageToTarget;
namespace AM.Idle;
@@ -353,7 +353,7 @@ public void NotifyPawnDidMeleeAttack(Thing target, Verb_MeleeAttack verbUsed)
// Attempt to get an attack animation for current weapon and stance.
var rot = pawn.Rotation;
- bool didHit = target != null && Patch.lastTarget == target;
+ bool didHit = target != null && Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.lastTarget == target;
// Get list of attack animations.
var anims = tweak.GetAttackAnimations(rot);
@@ -383,7 +383,7 @@ public void NotifyPawnDidMeleeAttack(Thing target, Verb_MeleeAttack verbUsed)
pauseTicks = (int)Core.Settings.AttackPauseDuration;
}
- Patch.lastTarget = null;
+ Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.lastTarget = null;
// Play animation.
var args = new AnimationStartParameters(anim, pawn)
diff --git a/Source/ThingGenerator/Outcome/IOutcomeWorker.cs b/Source/ThingGenerator/Outcome/IOutcomeWorker.cs
index 15f88724..b905c4ae 100644
--- a/Source/ThingGenerator/Outcome/IOutcomeWorker.cs
+++ b/Source/ThingGenerator/Outcome/IOutcomeWorker.cs
@@ -9,4 +9,8 @@ public interface IOutcomeWorker
IEnumerable GetMeleeAttacksFor(ThingWithComps weapon, Pawn pawn);
float GetChanceToPenAprox(Pawn pawn, BodyPartRecord bodyPart, StatDef armorType, float armorPen);
+
+ float GetPen(ThingWithComps weapon, Verb verb, Pawn attacker);
+
+ float GetDamage(ThingWithComps weapon, Verb verb, Pawn attacker);
}
diff --git a/Source/ThingGenerator/Outcome/OutcomeUtility.cs b/Source/ThingGenerator/Outcome/OutcomeUtility.cs
index 5753d732..f0c20890 100644
--- a/Source/ThingGenerator/Outcome/OutcomeUtility.cs
+++ b/Source/ThingGenerator/Outcome/OutcomeUtility.cs
@@ -1,10 +1,10 @@
-using AM.Patches;
+using AM.Outcome;
+using AM.Patches;
using JetBrains.Annotations;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
-using AM.Outcome;
using UnityEngine;
using Verse;
@@ -33,7 +33,7 @@ public ref struct AdditionalArgs
public DamageDef DamageDef;
public BodyPartDef BodyPartDef;
public RulePackDef LogGenDef;
- public Thing Weapon;
+ public ThingWithComps Weapon;
public float TargetDamageAmount;
}
@@ -195,7 +195,7 @@ static void Log(string msg)
return outcome;
}
- private static float RemapClamped(float baseA, float baseB, float newA, float newB, float value)
+ public static float RemapClamped(float baseA, float baseB, float newA, float newB, float value)
{
float t = Mathf.InverseLerp(baseA, baseB, value);
return Mathf.Lerp(newA, newB, t);
@@ -214,11 +214,12 @@ private static void LogAllMeleeWeaponVerbs()
created.Add(ThingMaker.MakeThing(def) as ThingWithComps);
}
- static string VerbToString(Verb verb, Thing weapon = null)
+ static string VerbToString(Verb verb, ThingWithComps weapon = null)
{
- float dmg = verb.verbProps.AdjustedMeleeDamageAmount(verb.tool, null, weapon, verb.HediffCompSource);
- float ap = verb.verbProps.AdjustedArmorPenetration(verb.tool, null, weapon, verb.HediffCompSource);
- return $"{verb}: {verb.GetDamageDef()} (dmg: {dmg:F1}, ap: {ap:F1})";
+ float dmg = OutcomeWorker.GetDamage(weapon, verb, null);
+ float ap = OutcomeWorker.GetPen(weapon, verb, null);
+ var armorSt = verb.GetDamageDef()?.armorCategory?.armorRatingStat ?? StatDefOf.ArmorRating_Sharp;
+ return $"{verb}: {verb.GetDamageDef()} (dmg: {dmg:F2}, ap: {ap:F2}, armor: {armorSt})";
}
static string AllVerbs(ThingWithComps t)
@@ -289,13 +290,24 @@ bool WouldBeInvalidResult(HediffDef hediff, float dmg, BodyPartRecord bp)
for (int i = 0; i < 50; i++)
{
// TODO check does this correctly get the right verbs even if the melee weapon is a sidearm?
- var verb = attacker.meleeVerbs.TryGetMeleeVerb(pawn);
+ Verb verb;
+ int limit = 100;
+ do
+ {
+ verb = attacker.meleeVerbs.TryGetMeleeVerb(pawn);
+ if (limit-- == 0)
+ {
+ Core.Error("Failed to find random verb for weapon.");
+ break;
+ }
- float dmg = verb.verbProps.AdjustedMeleeDamageAmount(verb.tool, attacker, args.Weapon, verb.HediffCompSource);
+ } while (verb.EquipmentSource != args.Weapon);
+
+ float dmg = OutcomeWorker.GetPen(args.Weapon, verb, attacker);
if (dmg > dmgToDo)
dmg = dmgToDo;
dmgToDo -= dmg;
- float armorPenetration = verb.verbProps.AdjustedArmorPenetration(verb.tool, attacker, args.Weapon, verb.HediffCompSource);
+ float armorPenetration = OutcomeWorker.GetDamage(args.Weapon, verb, attacker);
if (debugLogExecutionOutcome)
Core.Log($"Using verb {verb} to hit for {dmg:F1} dmg with {armorPenetration:F2} pen. Rem: {dmgToDo}");
@@ -307,7 +319,9 @@ bool WouldBeInvalidResult(HediffDef hediff, float dmg, BodyPartRecord bp)
{
var part = pawn.health.hediffSet.GetRandomNotMissingPart(def, BodyPartHeight.Middle, BodyPartDepth.Outside);
- if (dmg < 1f) {
+ if (dmg < 1f)
+ {
+ Core.Warn($"Very low damage of {dmg:F3} with verb {verb}");
dmg = 1f;
def = DamageDefOf.Blunt;
}
@@ -329,10 +343,10 @@ bool WouldBeInvalidResult(HediffDef hediff, float dmg, BodyPartRecord bp)
var info = pawn.TakeDamage(damageInfo);
if (debugLogExecutionOutcome)
- Core.Log($"Hit {part} for {info.totalDamageDealt:F2}/{dmg:F2} dmg, mitigated");
+ Core.Log($"Hit {part.LabelCap} for {info.totalDamageDealt:F2}/{dmg:F2} dmg, mitigated");
totalDmgDone += info.totalDamageDealt;
if (pawn.Dead || pawn.Downed) {
- Core.Error($"Accidentally killed or downed {pawn} when attempting to just injure: tried to deal {dmg:F1} {def} dmg to {part}. Storyteller, difficulty, hediffs, or mods could have modified the damage to cause this.");
+ Core.Error($"Accidentally killed or downed {pawn} when attempting to just injure: tried to deal {dmg:F1} {def} dmg to {part.LabelCap}. Storyteller, difficulty, hediffs, or mods could have modified the damage to cause this.");
return false;
}
break;
diff --git a/Source/ThingGenerator/Outcome/VanillaOutcomeWorker.cs b/Source/ThingGenerator/Outcome/VanillaOutcomeWorker.cs
index 001ab549..10c0af2c 100644
--- a/Source/ThingGenerator/Outcome/VanillaOutcomeWorker.cs
+++ b/Source/ThingGenerator/Outcome/VanillaOutcomeWorker.cs
@@ -56,4 +56,10 @@ public float GetChanceToPenAprox(Pawn pawn, BodyPartRecord bodyPart, StatDef arm
return chance;
}
+
+ public float GetPen(ThingWithComps weapon, Verb verb, Pawn attacker) =>
+ verb.verbProps.AdjustedArmorPenetration(verb.tool, attacker, weapon, verb.HediffCompSource);
+
+ public float GetDamage(ThingWithComps weapon, Verb verb, Pawn attacker) =>
+ verb.verbProps.AdjustedMeleeDamageAmount(verb.tool, attacker, weapon, verb.HediffCompSource);
}
\ No newline at end of file
diff --git a/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttackDamage_ApplyMeleeDamageToTarget.cs b/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttackDamage_ApplyMeleeDamageToTarget.cs
deleted file mode 100644
index 773660dc..00000000
--- a/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttackDamage_ApplyMeleeDamageToTarget.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using HarmonyLib;
-using RimWorld;
-using Verse;
-
-namespace AM.Patches;
-
-[HarmonyPatch(typeof(Verb_MeleeAttackDamage), nameof(Verb_MeleeAttackDamage.ApplyMeleeDamageToTarget))]
-public static class Patch_Verb_MeleeAttackDamage_ApplyMeleeDamageToTarget
-{
- public static DamageWorker.DamageResult lastResult;
- public static Thing lastTarget;
-
- public static void Postfix(LocalTargetInfo target, DamageWorker.DamageResult __result)
- {
- lastTarget = target.Thing;
- lastResult = __result;
- }
-}
diff --git a/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.cs b/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.cs
new file mode 100644
index 00000000..47723134
--- /dev/null
+++ b/Source/ThingGenerator/Patches/Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget.cs
@@ -0,0 +1,64 @@
+using HarmonyLib;
+using RimWorld;
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+using Verse;
+
+namespace AM.Patches;
+
+// Patched manually below, see PatchAll
+public static class Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget
+{
+ public static DamageWorker.DamageResult lastResult;
+ public static Thing lastTarget;
+
+ private static readonly Type[] methodParams = new[] { typeof(LocalTargetInfo) };
+ private static readonly HarmonyMethod postfix = new HarmonyMethod(typeof(Patch_Verb_MeleeAttack_ApplyMeleeDamageToTarget), nameof(Postfix));
+ private const BindingFlags METHOD_FLAGS = BindingFlags.Public | BindingFlags.Instance;
+
+ public static void PatchAll()
+ {
+ var log = new StringBuilder();
+ var timer = Stopwatch.StartNew();
+ int count = 0;
+
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ foreach (var klass in asm.GetTypes())
+ {
+ if (klass.IsAbstract)
+ continue;
+
+ if (klass.BaseType != typeof(Verb_MeleeAttack))
+ continue;
+
+ try
+ {
+ var method = AccessTools.Method(klass, nameof(Verb_MeleeAttack.ApplyMeleeDamageToTarget), methodParams);
+ if (method == null)
+ throw new Exception($"Failed to find {nameof(Verb_MeleeAttack.ApplyMeleeDamageToTarget)} method in {klass.FullName}: failed to find method");
+
+ Core.Harmony.Patch(method, postfix: postfix);
+
+ count++;
+ log.Append(klass.FullName).Append(" from ").AppendLine(asm.GetName().Name);
+ }
+ catch (Exception e)
+ {
+ Core.Error($"Failed to patch {klass.FullName}'s {nameof(Verb_MeleeAttack.ApplyMeleeDamageToTarget)}:", e);
+ }
+ }
+ }
+
+ timer.Stop();
+ Core.Log($"Patched {count} classes that directly inherit from {nameof(Verb_MeleeAttack)} in {timer.Elapsed.TotalMilliseconds:F1} ms to detect hits:\n{log}");
+ }
+
+ public static void Postfix(LocalTargetInfo target, DamageWorker.DamageResult __result)
+ {
+ lastTarget = target.Thing;
+ lastResult = __result;
+ }
+}
diff --git a/loadfolders.xml b/loadfolders.xml
index 3ae6eb79..be675c1a 100644
--- a/loadfolders.xml
+++ b/loadfolders.xml
@@ -6,5 +6,6 @@
Patch_Lightsabers/1.4
Patch_AlienRaces/1.4
Patch_CAI5000/1.4
+ Patch_CombatExtended/1.4
\ No newline at end of file