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