using AlienRace; using AlienRace.ExtendedGraphics; using HarmonyLib; using RimWorld; using System.Collections.Generic; using System.Linq; using Verse; // The RevealingApparel mod extension allows us to add information to apparel that tells us if wearing an apparel item // should also cover the body parts introduced by RNW. This way mod authors can make an apparel item that covers a pawn's // torso but still draws their breasts or genitals. namespace RevealingApparel { // This is the mod extension that people will use to add revealing information to their apparel defs. public class ApparelRevealingExtension : DefModExtension { // A list of RevealingExtensionEntry items, which describe what body parts are revealed when a pawn is wearing this apparel item public List revealingBodyPartEntries = new List(); } // The entry class that describes what body parts are revealed public class RevealingExtensionEntry { // The path to the body part that is revealed. // Examples include "Breasts/FeaturelessLeft" or "Genitals/FeaturelessCrotch" public string revealingPath; // A list of pawn body types this entry applies to. // Examples include "Female" or "Thin" or "Hulk" public List revealingBodyTypes = new List(); } // We are going to postfix patch the VisibleUnderApparelOf check in HAR so we can make body parts visible if all of the apparel // covering it is marked as revealing [HarmonyPatch(typeof(AlienPartGenerator.BodyAddon), "VisibleUnderApparelOf")] class HarmonyPatch_RevealingApparel_VisibleUnderApparelOf { public static Pawn GetPawnFromWrapped(ExtendedGraphicsPawnWrapper pawn) { // The pawn wrapper doesn't expose the original Pawn as a public field, so we need to use reflection // to pull it out. return Traverse.Create(pawn).Property("WrappedPawn").GetValue(); } public static IEnumerable GetApparelCoveringPart(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon) { // Get a list of all of the apparel worn by this pawn return pawn.apparel?.WornApparel?.Where( // Where any of the body part groups this apparel covers apparel => apparel.def.apparel.bodyPartGroups.Any( // Hide the passed body addon bodyPartGroup => bodyAddon.hiddenUnderApparelFor.Contains(bodyPartGroup))); } public static bool IsApparelRevealingBodyPart(Apparel apparel, AlienPartGenerator.BodyAddon bodyAddon, BodyTypeDef bodyType) { // Get the revealing entries for this apparel item var revealingExtension = apparel.def.GetModExtension(); // And return if one of these entries matches the body part for this pawn body type return revealingExtension?.revealingBodyPartEntries.Any((entry) => { // Does this entry reveal the body part we are considering bool entryMatchesBodyPart = entry.revealingPath?.Contains(bodyAddon.GetPath()) ?? false; // Does this entry apply to the pawn's body shape bool entryMatchesPawnBody = entry.revealingBodyTypes?.Contains(bodyType) ?? false; // If this entry matches the part and applies to the pawn body type, then this apparel reveals this body part return entryMatchesBodyPart && entryMatchesPawnBody; }) ?? false; // If there are no revealing body part entries, then this apparel covers this body part } public static bool Postfix(bool __result, AlienPartGenerator.BodyAddon __instance, ExtendedGraphicsPawnWrapper pawn) { // If the original method returned false, we might still show it based on the revealing apparel entries if (__result == false) { // Grab the underlying pawn from the wrapped version we were passed var myPawn = GetPawnFromWrapped(pawn); // Reference the pawn body type. We will need this to know if revealing body part entries apply to this pawn var bodyType = myPawn.story.bodyType; // Get a list of all of the apparel worn by this pawn that covers this body part var apparelCoveringPartList = GetApparelCoveringPart(myPawn, __instance); // If no apparel is covering this part OR they are all revealing, then reveal this body part if (apparelCoveringPartList.Count() == 0 || apparelCoveringPartList.All(apparel => IsApparelRevealingBodyPart(apparel, __instance, bodyType))) { return true; } } // Else, return the original result return __result; } } }