using System; using System.Collections.Generic; using System.Linq; using Verse; using RimWorld; using AlienRace; using Rimworld_Animations; using UnityEngine; using HarmonyLib; namespace Rimworld_Animations_Patch { public class BodyAddonData { public AlienPartGenerator.BodyAddon bodyAddon; public BodyPartRecord bodyPartRecord; public List bodyAddonOffsets = new List(); public bool alignsWithHead = false; private Pawn pawn; private string bodyType; private PawnRenderFlags renderFlags; private bool canDraw = false; private bool bodyPartMissing = false; public BodyAddonData(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon, bool isPortrait = false) { this.pawn = pawn; this.bodyAddon = bodyAddon; if (isPortrait) { renderFlags |= PawnRenderFlags.Portrait; } bodyPartRecord = pawn?.def?.race?.body?.AllParts?.FirstOrDefault(x => x.def.defName == bodyAddon?.bodyPart || x.customLabel == bodyAddon?.bodyPart); alignsWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead)); GenerateOffsets(); UpdateVisibility(); } public void GenerateOffsets() { bodyType = pawn.story.bodyType.defName; bodyAddonOffsets.Clear(); int bodyAddonIndex = (pawn.def as ThingDef_AlienRace).alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList().IndexOf(bodyAddon); AlienPartGenerator.AlienComp alienComp = pawn.GetComp(); Graphic addonGraphic = alienComp.addonGraphics[bodyAddonIndex]; for (int i = 0; i < 4; i++) { Rot4 apparentRotation = new Rot4(i); // Get basic offset for body addon AlienPartGenerator.RotationOffset defaultOffsets = bodyAddon.defaultOffsets.GetOffset(apparentRotation); Vector3 bodyTypeOffset = (defaultOffsets != null) ? defaultOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero; AlienPartGenerator.RotationOffset rotationOffsets = bodyAddon.offsets.GetOffset(apparentRotation); Vector3 bodyAddonOffset = bodyTypeOffset + ((rotationOffsets != null) ? rotationOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero); // Offset private parts so that they render over tattoos but under apparel (rendering under tatoos looks weird) if (bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Chest" || bodyAddon.bodyPart == "Anus" || addonGraphic.path.ToLower().Contains("belly")) { bodyAddonOffset.y = (bodyAddonOffset.y + 0.40f) / 1000f + 0.012f; // Erected penises should be drawn over apparel if (pawn.RaceProps.Humanlike && addonGraphic.path.ToLower().Contains("penis") && addonGraphic.path.ToLower().Contains("flaccid") == false && apparentRotation == Rot4.South) { bodyAddonOffset.y += 0.010f; } } // Otherwise use the standard offsets else { bodyAddonOffset.y = 0.3f + bodyAddonOffset.y; } // Draw addons infront of body if (!bodyAddon.inFrontOfBody) { bodyAddonOffset.y *= -1f; } // Adjust for facing if (apparentRotation == Rot4.North) { if (bodyAddon.layerInvert) { bodyAddonOffset.y = -bodyAddonOffset.y; } } if (apparentRotation == Rot4.East) { bodyAddonOffset.x = -bodyAddonOffset.x; } // Adjustment for body addons attached to the head that are not marked as such if (alignsWithHead && bodyAddon.alignWithHead == false) { bodyAddonOffset -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); } // Done bodyAddonOffsets.Add(bodyAddonOffset); } } public Vector3 GetOffset(Rot4 facing) { if (pawn.story.bodyType.defName != bodyType) { GenerateOffsets(); } return bodyAddonOffsets[facing.AsInt]; } public bool CanDraw() { return bodyPartMissing == false && SitutationalVisibiltyCheck() && (pawn.Drawer.renderer.graphics.apparelGraphics.Any() == false || canDraw); } public bool SitutationalVisibiltyCheck() { if (pawn == null || bodyAddon == null) return false; if (pawn.CurrentBed()?.def.building.bed_showSleeperBody == false && bodyAddon.drawnInBed == false) { return false; } if (bodyAddon.backstoryRequirement.NullOrEmpty() == false && pawn.story?.AllBackstories?.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement) == false) { return false; } if (bodyAddon.drawnDesiccated == false && pawn.Corpse?.GetRotStage() == RotStage.Dessicated) { return false; } if (pawn.gender == Gender.Female && bodyAddon.drawForFemale == false || pawn.gender == Gender.Male && bodyAddon.drawForMale == false) { return false; } if (bodyAddon.bodyTypeRequirement.NullOrEmpty() == false && pawn.story?.bodyType.ToString() != bodyAddon.bodyTypeRequirement) { return false; } if ((pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround == false) { return false; } return true; } public void UpdateVisibility() { if (pawn == null || bodyAddon == null) return; canDraw = true; if (pawn.health?.hediffSet?.GetNotMissingParts()?.Contains(bodyPartRecord) == false) { bodyPartMissing = true; return; } foreach (Apparel apparel in pawn.apparel.WornApparel) { CompApparelVisibility comp = apparel?.TryGetComp(); if (comp == null) continue; LoadRimNudeData(comp); if (comp.isBeingWorn == false) continue; if (bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Anus" || bodyAddon.bodyPart == "Chest" || bodyAddon.hediffGraphics?.Any(x => x.path.NullOrEmpty() == false && x.path.ToLower().Contains("belly")) == true) { if ((bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Anus") && comp.coversGroin) { canDraw = false; return; }; if (bodyAddon.bodyPart == "Chest" && comp.coversChest) { canDraw = false; return; }; if (bodyAddon.hediffGraphics?.Any(x => x.path.NullOrEmpty() == false && x.path.ToLower().Contains("belly")) == true && comp.coversBelly) { canDraw = false; return; } } else { if (bodyAddon.hiddenUnderApparelFor?.Any(x => apparel?.def.apparel?.hatRenderedFrontOfFace == false && apparel.def.apparel?.bodyPartGroups?.Contains(x) == true) == true) { canDraw = false; return; }; if (bodyAddon.hiddenUnderApparelTag?.Any(x => apparel?.def.apparel?.hatRenderedFrontOfFace == false && apparel.def.apparel?.tags?.Contains(x) == true) == true) { canDraw = false; return; }; } } } public void LoadRimNudeData(CompApparelVisibility comp) { if (comp?.rimNudeDataStatus == RimNudeDataStatus.Unavailable) { return; } if (comp?.rimNudeDataStatus == RimNudeDataStatus.NotLoaded) { RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(comp?.apparel); if (rimNudeData == null) { comp.rimNudeDataStatus = RimNudeDataStatus.Unavailable; return; } comp.coversBelly = rimNudeData.coversBelly; comp.coversChest = rimNudeData.coversChest; comp.coversGroin = rimNudeData.coversGroin; comp.rimNudeDataStatus = RimNudeDataStatus.Loaded; } } } }