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; + } + } + } }