From a94e91822bf5f91ec805cbedd89b5bf20cfbf746 Mon Sep 17 00:00:00 2001 From: ViralReaction <136116069+ViralReaction@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:22:13 -0600 Subject: [PATCH 1/4] Adds information for shields to UI * Shows what the shield protects in the information tab * Shows what the shield protects in the Gear ITab when hovered over * Armor values in GearTab properly calculate shield armor --- Languages/English/Keyed/Keys.xml | 4 + .../CombatExtended/Defs/ShieldDefExtension.cs | 7 ++ .../CombatExtended/Loadouts/ITab_Inventory.cs | 10 +++ .../CombatExtended/Things/Apparel_Shield.cs | 26 +++++- .../Harmony/Harmony_ThingDef.cs | 80 +++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Languages/English/Keyed/Keys.xml b/Languages/English/Keyed/Keys.xml index 471ece0cef..0ec88643b2 100644 --- a/Languages/English/Keyed/Keys.xml +++ b/Languages/English/Keyed/Keys.xml @@ -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 Protects + Shields protect parts of the body from ranged and melee attacks. \ 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..8073e25321 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) + { + 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..2eff7a479c 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..4c6a5fe01b 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; + } + } + } } From 2081ac07b671f8827ed29979ada30e94083ddc11 Mon Sep 17 00:00:00 2001 From: ViralReaction <136116069+ViralReaction@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:15:23 -0600 Subject: [PATCH 2/4] Housekeeping --- .../CombatExtended/Loadouts/ITab_Inventory.cs | 4 ++-- .../CombatExtended/Things/Apparel_Shield.cs | 2 +- Source/CombatExtended/Harmony/Harmony_ThingDef.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs index 8073e25321..265ce3350e 100644 --- a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs +++ b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs @@ -518,7 +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); + var shield = wornApparel.FirstOrDefault(x => x is Apparel_Shield); foreach (BodyPartRecord part in SelPawnForGear.RaceProps.body.AllParts) { //TODO: 1.5 should be Neck @@ -538,7 +538,7 @@ private void RebuildArmorCache(Dictionary armorCache, Sta if (shield != null) { var shieldCoverage = shield.def.GetModExtension().PartIsCoveredByShield(part, SelPawnForGear); - if(shieldCoverage) + if (shieldCoverage) { armorValue += shield.GetStatValue(stat); } diff --git a/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs b/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs index 2eff7a479c..a31200c1a3 100644 --- a/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs +++ b/Source/CombatExtended/CombatExtended/Things/Apparel_Shield.cs @@ -57,7 +57,7 @@ orderby t.seniority descending 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() diff --git a/Source/CombatExtended/Harmony/Harmony_ThingDef.cs b/Source/CombatExtended/Harmony/Harmony_ThingDef.cs index 4c6a5fe01b..9d0422bbe3 100644 --- a/Source/CombatExtended/Harmony/Harmony_ThingDef.cs +++ b/Source/CombatExtended/Harmony/Harmony_ThingDef.cs @@ -209,10 +209,10 @@ internal static IEnumerable Transpiler(IEnumerable Date: Fri, 6 Sep 2024 01:11:52 -0400 Subject: [PATCH 3/4] Reivse keys --- Languages/English/Keyed/Keys.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Languages/English/Keyed/Keys.xml b/Languages/English/Keyed/Keys.xml index 0ec88643b2..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 @@ -163,7 +163,7 @@ Not patched for CE - Shield Protects - Shields protect parts of the body from ranged and melee attacks. + Shield coverage + Body parts protected from ranged and melee attacks by the shield. \ No newline at end of file From 4d13ca0854b6918ea6e2c19604f3c9bdad1479c4 Mon Sep 17 00:00:00 2001 From: ViralReaction <136116069+ViralReaction@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:30:07 -0600 Subject: [PATCH 4/4] Add null check * Adds null checks for the ITab Inventory when it comes to shield mod extension --- .../CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs index 265ce3350e..92d5c43ec3 100644 --- a/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs +++ b/Source/CombatExtended/CombatExtended/Loadouts/ITab_Inventory.cs @@ -537,8 +537,8 @@ private void RebuildArmorCache(Dictionary armorCache, Sta } if (shield != null) { - var shieldCoverage = shield.def.GetModExtension().PartIsCoveredByShield(part, SelPawnForGear); - if (shieldCoverage) + var shieldCoverage = shield.def?.GetModExtension()?.PartIsCoveredByShield(part, SelPawnForGear); + if (shieldCoverage == true) { armorValue += shield.GetStatValue(stat); }