diff --git a/Languages/English/Keyed/Keys.xml b/Languages/English/Keyed/Keys.xml
index 471ece0cef..b5ed4a464e 100644
--- a/Languages/English/Keyed/Keys.xml
+++ b/Languages/English/Keyed/Keys.xml
@@ -122,10 +122,10 @@
°
m
Weapon penetration factor
- Skill factor
- Other factors
+ Skill factor
+ Other factors
Adjusted for weapon
- Penetration quality factor
+ Penetration quality factor
Damage per second
Damage variation
Final average damage
@@ -161,5 +161,9 @@
This weapon is yet patched for CE, while it will behave in vanilla way as fallback, please contact a patcher for compatibility patches.
Not patched for CE
+
+
+ Shield coverage
+ Body parts protected from ranged and melee attacks by the shield.
\ No newline at end of file
diff --git a/Source/CombatExtended/CombatExtended/Defs/ShieldDefExtension.cs b/Source/CombatExtended/CombatExtended/Defs/ShieldDefExtension.cs
index 0e22f414f8..5d9182e56c 100644
--- a/Source/CombatExtended/CombatExtended/Defs/ShieldDefExtension.cs
+++ b/Source/CombatExtended/CombatExtended/Defs/ShieldDefExtension.cs
@@ -38,5 +38,12 @@ public bool PartIsCoveredByShield(BodyPartRecord part, Pawn pawn)
}
return false;
}
+ public static string GetShieldProtectedAreas(BodyDef body, ThingDef thingDef)
+ {
+ return (from part in (from x in body.AllParts
+ where x.depth == BodyPartDepth.Outside && x.groups.Any((BodyPartGroupDef y) => thingDef.GetModExtension().shieldCoverage.Contains(y))
+ select x).Distinct()
+ select part.Label).ToCommaList(false, false).CapitalizeFirst();
+ }
}
}
diff --git a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs
index 378fe1870a..92d5c43ec3 100644
--- a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs
+++ b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs
@@ -8,6 +8,7 @@
using Verse;
using Verse.AI;
using Verse.Sound;
+using CombatExtended.HarmonyCE;
namespace CombatExtended
{
@@ -517,6 +518,7 @@ private void RebuildArmorCache(Dictionary armorCache, Sta
armorCache.Clear();
float naturalArmor = SelPawnForGear.GetStatValue(stat);
List wornApparel = SelPawnForGear.apparel?.WornApparel;
+ var shield = wornApparel.FirstOrDefault(x => x is Apparel_Shield);
foreach (BodyPartRecord part in SelPawnForGear.RaceProps.body.AllParts)
{
//TODO: 1.5 should be Neck
@@ -533,6 +535,14 @@ private void RebuildArmorCache(Dictionary armorCache, Sta
}
}
}
+ if (shield != null)
+ {
+ var shieldCoverage = shield.def?.GetModExtension()?.PartIsCoveredByShield(part, SelPawnForGear);
+ if (shieldCoverage == true)
+ {
+ armorValue += shield.GetStatValue(stat);
+ }
+ }
armorCache[part] = armorValue;
}
}
diff --git a/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs b/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs
index 2aede24fc1..a31200c1a3 100644
--- a/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs
+++ b/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs
@@ -36,7 +36,30 @@ public override bool AllowVerbCast(Verb verb)
ThingWithComps primary = Wearer.equipment?.Primary;
return primary == null || (primary.def.weaponTags?.Contains(OneHandedTag) ?? false);
}
-
+ public override IEnumerable SpecialDisplayStats()
+ {
+ foreach (StatDrawEntry statDrawEntry in base.SpecialDisplayStats())
+ {
+ yield return statDrawEntry;
+ }
+ RoyalTitleDef royalTitleDef = (from t in DefDatabase.AllDefsListForReading.SelectMany((FactionDef f) => f.RoyalTitlesAwardableInSeniorityOrderForReading)
+ where t.requiredApparel != null && t.requiredApparel.Any((ApparelRequirement req) => req.ApparelMeetsRequirement(this.def, false))
+ orderby t.seniority descending
+ select t).FirstOrDefault();
+ if (royalTitleDef != null)
+ {
+ yield return new StatDrawEntry(StatCategoryDefOf.Apparel, "Stat_Thing_Apparel_MaxSatisfiedTitle".Translate(), royalTitleDef.GetLabelCapForBothGenders(), "Stat_Thing_Apparel_MaxSatisfiedTitle_Desc".Translate(), 2752, null, new Dialog_InfoCard.Hyperlink[]
+ {
+ new Dialog_InfoCard.Hyperlink(royalTitleDef, -1)
+ }, false, false);
+ }
+ var shieldCoverage = this.def.GetModExtension()?.shieldCoverage;
+ if (shieldCoverage != null)
+ {
+ yield return new StatDrawEntry(StatCategoryDefOf.Apparel, "CE_Shield_Coverage".Translate(), ShieldDefExtension.GetShieldProtectedAreas(BodyDefOf.Human, this.def), "CE_Shield_Coverage_Desc".Translate(), 800, null);
+ }
+ yield break;
+ }
public override void DrawWornExtras()
{
if (Wearer == null || !Wearer.Spawned)
@@ -94,6 +117,7 @@ public override void DrawWornExtras()
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(vector, Quaternion.AngleAxis(num, Vector3.up), s);
Graphics.DrawMesh(MeshPool.plane10, matrix, mat, 0);
+
}
}
}
diff --git a/Source/CombatExtended/Harmony/Harmony_ThingDef.cs b/Source/CombatExtended/Harmony/Harmony_ThingDef.cs
index 3e17aa2e3a..9d0422bbe3 100644
--- a/Source/CombatExtended/Harmony/Harmony_ThingDef.cs
+++ b/Source/CombatExtended/Harmony/Harmony_ThingDef.cs
@@ -2,7 +2,10 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
+using System.Text;
using HarmonyLib;
+using Mono.Cecil.Cil;
using RimWorld;
using Verse;
using Verse.Noise;
@@ -140,4 +143,81 @@ public static void Postfix(ThingDef __instance)
}
}
}
+ [HarmonyPatch(typeof(ThingDef), "DescriptionDetailed", MethodType.Getter)]
+
+ internal static class ThingDef_DescriptionDetailed
+ {
+ private static StringBuilder AddShieldCover(ThingDef thingDef, StringBuilder stringBuilder)
+ {
+ if (thingDef.GetModExtension()?.shieldCoverage != null)
+ {
+ stringBuilder.Append(string.Format("{0}: {1}", "CE_Shield_Coverage".Translate(), ShieldDefExtension.GetShieldProtectedAreas(BodyDefOf.Human, thingDef)));
+ }
+ else
+ {
+ stringBuilder.Append(string.Format("{0}: {1}", "Covers".Translate(), thingDef.apparel.GetCoveredOuterPartsString(BodyDefOf.Human)));
+ }
+ return stringBuilder;
+ }
+ internal static IEnumerable Transpiler(IEnumerable instructions,
+ ILGenerator generator)
+ {
+ var code = new List(instructions);
+ int startIndex = -1;
+ int endIndex = -1;
+ bool foundCovers = false;
+
+ for (int i = 0; i < code.Count; i++)
+ {
+ if (code[i].opcode == OpCodes.Ldloc_0)
+ {
+ startIndex = i;
+
+ // Search for the next Pop, and check if "Covers" is in between
+ for (int j = i + 1; j < code.Count; j++)
+ {
+ if (code[j].opcode == OpCodes.Ldstr && code[j].operand as string == "Covers")
+ {
+ foundCovers = true;
+ }
+
+ if (code[j].opcode == OpCodes.Pop)
+ {
+ if (foundCovers)
+ {
+ endIndex = j;
+ break;
+ }
+ else
+ {
+ // If no "Covers" was found, reset startIndex and move to the next possible sequence
+ startIndex = -1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (endIndex > -1)
+ {
+ break;
+ }
+ }
+
+ // Remove the code between startIndex and endIndex if a valid range was found
+ if (startIndex > -1 && endIndex > -1)
+ {
+ code[startIndex].opcode = OpCodes.Nop;
+ //code[endIndex].opcode = OpCodes.Nop;
+ code.RemoveRange(startIndex + 1, endIndex - startIndex - 1);
+ code.Insert(startIndex + 1, new CodeInstruction(OpCodes.Ldarg_0));
+ code.Insert(startIndex + 2, new CodeInstruction(OpCodes.Ldloc_0));
+ code.Insert(startIndex + 3, new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ThingDef_DescriptionDetailed), "AddShieldCover", null, null)));
+ }
+ foreach (var c in code)
+ {
+ yield return c;
+ }
+ }
+ }
}