using HarmonyLib; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using RimWorld; using Verse; using AlienRace; using Rimworld_Animations; using rjw; namespace Rimworld_Animations_Patch { [StaticConstructorOnStartup] public static class HarmonyPatch_Rimworld_Animations { static HarmonyPatch_Rimworld_Animations() { (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "GenitalCheckForPawn"), postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "PostFix_AnimationUtility_GenitalCheckForPawn"))); (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "setAnchor", new Type[] { typeof(Thing) }), prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_HarmonyPatch_CompBodyAnimator_setAnchor"))); (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_JobDriver_SexBaseInitiator_Start"), "RerollAnimations"), postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_RerollAnimations"))); (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_AlienRace"), "Prefix_AnimateHeadAddons"), prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_DrawAddons"))); (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "StartAnimation"), postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_StartAnimation"))); (new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "tryFindAnimation"), prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_tryFindAnimation"))); } // Update hand count for all participants before finding an animation public static void Prefix_tryFindAnimation(ref List participants) { foreach (Pawn participant in participants) { CompPawnSexData comp = participant.TryGetComp(); if (comp == null) return; comp.UpdateBodyPartCountAndSize(); } } // Update hand animation def on anim start public static void Postfix_StartAnimation(CompBodyAnimator __instance) { CompPawnSexData comp = __instance.pawn.TryGetComp(); if (comp == null) return; comp.handAnimationDef = DefDatabase.AllDefs.FirstOrDefault(x => x.animationDef == __instance.pawn?.GetAnimationData()?.animationDef); } // Extend the animation selector's body part check to include hands and whether the pawn is in bed or not public static void PostFix_AnimationUtility_GenitalCheckForPawn(ref bool __result, List requiredGenitals, Pawn pawn, ref string failReason) { int? handCount = pawn.TryGetComp()?.GetNumberOfHands(); if (handCount.HasValue == false) { handCount = 0; } bool pawnInBed = pawn.IsInBed(out Building bed); if (requiredGenitals.NullOrEmpty()) { return; } if (requiredGenitals.Contains("OneHand") && handCount < 1) { failReason = "missing hand"; __result = false; } if (requiredGenitals.Contains("TwoHands") && handCount < 2) { failReason = "missing hand(s)"; __result = false; } if (requiredGenitals.Contains("Bed") && pawnInBed == false) { failReason = "pawn is not in bed"; __result = false; } if (requiredGenitals.Contains("NoBed") && pawnInBed) { failReason = "pawn is in bed"; __result = false; } } // Override CompBodyAnimator's anchors public static bool Prefix_HarmonyPatch_CompBodyAnimator_setAnchor(CompBodyAnimator __instance, Thing thing) { __instance.anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, thing); return false; } // Adds functionality to determine which apparel each actor should discard based on the animation they are running public static void Postfix_RerollAnimations(Pawn pawn) { AnimationDef anim = pawn.GetAnimationData()?.animationDef; if (anim != null) { DebugMode.Message("Running animation: " + anim.defName); List pawnsToAnimate = pawn.GetAllSexParticipants(); foreach (Pawn participant in pawnsToAnimate) { int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp()); DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored); } } } // Replacement patch for AlienRace to draw the body addons public static bool Prefix_DrawAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation) { if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible)) return false; // Get actor components and body addons List bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons; AlienPartGenerator.AlienComp alienComp = pawn.GetComp(); CompBodyAnimator animatorComp = pawn.TryGetComp(); CompPawnSexData sexDataComp = pawn.TryGetComp(); ActorAnimationData pawnAnimationData = pawn?.GetAnimationData(); // Try to draw apparel thrown on ground if (ApparelSettings.clothesThrownOnGround) { ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); } // Exit clauses if (BasicSettings.useLegacyAnimationSystem || AnimationPatchUtility.ShouldNotDrawAddonsForPawn(pawn) || sexDataComp == null) { return true; } // Get available hands int handsAvailableCount = sexDataComp.GetNumberOfHands(); // Sort addons by their layer offset, otherwise body parts will actualy be layered according to their position in the list // Note that sorting the addons directly seems to mess up relations between lists need by AlienRace var sortedBodyAddons = bodyAddons.Select((x, i) => new KeyValuePair(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset); IEnumerable idxBodyAddons = sortedBodyAddons.Select(x => x.Value); foreach(int i in idxBodyAddons) { // Get body addon components AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i]; Graphic addonGraphic = alienComp.addonGraphics[i]; BodyAddonData bodyAddonDatum = sexDataComp.GetBodyAddonData(bodyAddon, renderFlags.FlagSet(PawnRenderFlags.Portrait)); if (bodyAddonDatum == null) continue; // Can draw? bool canDraw = addonGraphic.path.Contains("featureless") == false && addonGraphic.path.Contains("Featureless") == false && bodyAddonDatum.CanDraw(); bool drawHand = BasicSettings.showHands && handsAvailableCount > 0 && renderFlags.FlagSet(PawnRenderFlags.Portrait) == false; if (canDraw == false && drawHand == false) { continue; } // Get body angle float bodyAngle = animatorComp?.isAnimating == true && renderFlags.FlagSet(PawnRenderFlags.Portrait) == false ? animatorComp.bodyAngle : quat.eulerAngles.y; bodyAngle = MathUtility.ClampAngle(bodyAngle); // Get the apparent rotation and body addon angle Rot4 apparentRotation = rotation; float bodyAddonAngle = MathUtility.ClampAngle(bodyAddon.angle); if (renderFlags.FlagSet(PawnRenderFlags.Portrait) == false && animatorComp?.isAnimating == true) { apparentRotation = bodyAddonDatum.alignsWithHead ? animatorComp.headFacing : animatorComp.bodyFacing; if (bodyAddonDatum.alignsWithHead) { bodyAngle = MathUtility.ClampAngle(animatorComp.headAngle); } if (animatorComp.controlGenitalAngle && addonGraphic.path.Contains("penis", StringComparison.OrdinalIgnoreCase)) { bodyAddonAngle = MathUtility.ClampAngle(bodyAddonAngle + (AnimationSettings.controlGenitalRotation ? MathUtility.ClampAngle(animatorComp.genitalAngle) : 0f)); } } float combinedAngle = MathUtility.ClampAngle(bodyAngle + bodyAddonAngle); Vector3 vector_ = vector; Vector3 headOffset_ = bodyAddonDatum.alignsWithHead ? headOffset : Vector3.zero; Vector3 bodyAddonOffset_ = bodyAddonDatum.GetOffset(apparentRotation).RotatedBy(angle: bodyAngle); if (pawn.ageTracker.Adult == false) { float bodySize = AnimationPatchUtility.GetBodySize(pawn); addonGraphic.drawSize = new Vector2(1.5f * bodySize, 1.5f * bodySize); // Doesn't seem to be a need to scale by body part draw size (re: Orassians) Vector2 multi = AnimationPatchUtility.GetRaceSpecificOffsetMultipliers(pawn, bodyAddon.bodyPart); bodyAddonOffset_.x *= bodySize * multi.x; bodyAddonOffset_.z *= bodySize * multi.y; } Vector3 bodyAddonPosition = vector_ + headOffset_ + bodyAddonOffset_; // Draw the addon if visible if (canDraw) { GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation), loc: bodyAddonPosition, quat: Quaternion.AngleAxis(angle: combinedAngle, axis: Vector3.up), mat: addonGraphic.MatAt(rot: apparentRotation), drawNow: renderFlags.FlagSet(PawnRenderFlags.DrawNow)); } // Draw hand over the body part if required if (drawHand) { float finalAngle = 0; if (HandAnimationUtility.TryToDrawHand(pawn, pawnAnimationData, addonGraphic.path, bodyAddonPosition, finalAngle, rotation, renderFlags)) { handsAvailableCount--; } } } // Body addons are sometimes are not appropriately concealed by long hair in portraits, so re-draw the pawn's hair here if (pawn.Drawer.renderer.graphics.headGraphic != null && renderFlags.FlagSet(PawnRenderFlags.Portrait) && BasicSettings.redrawHair) { var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null); Rot4 headFacing = animatorComp != null && animatorComp.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? animatorComp.headFacing : rotation; float headAngle = animatorComp != null && animatorComp.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? animatorComp.headAngle : quat.eulerAngles.y; RotDrawMode rotDrawMode = (RotDrawMode)AccessTools.Property(typeof(PawnRenderer), "CurRotDrawMode").GetValue(pawn.Drawer.renderer); methodInfo.Invoke(pawn.Drawer.renderer, new object[] { vector + new Vector3(0f, YOffset_Head, 0f), headOffset, headAngle, headFacing, headFacing, rotDrawMode, renderFlags, true }); } return false; } // List of potentially useful layer offsets //private const float YOffset_CarriedThingUnder = -0.0028957527f; //private const float YOffset_Align_Behind = 0.0028957527f; //private const float YOffset_Body = 0.008687258f; //private const float YOffset_Interval_Clothes = 0.0028957527f; //private const float YOffset_Shell = 0.02027027f; private const float YOffset_Head = 0.023166021f; private const float YOffset_OnHead = 0.028957527f; //private const float YOffset_PostHead = 0.03185328f; //private const float YOffset_Tattoo = 0.0014478763f; //private const float YOffset_WoundOverlays1 = 0.009687258f; //private const float YOffset_WoundOverlays2 = 0.022166021f; /* Details on the above Body = rootLoc + YOffset_Body; (~ 0.009) Tattoo = rootLoc + YOffset_Body + YOffset_Tattoo; (~ 0.010) BodyAddons (not protruding) = rootLoc + 0.011f; (~0.011) Body wounds (under clothes) = rootLoc + YOffset_WoundOverlays1; (~ 0.010) Apparel (not north) = rootLoc + YOffset_Shell; (~ 0.020) BodyAddons (protruding) = rootLoc + 0.011f + 0.010f; (~0.021) Apparel (north) = rootLoc + YOffset_Head; (~ 0.023) Body wounds (over clothes) = rootLoc + YOffset_WoundOverlays1 + YOffset_WoundOverlays2; (~ 0.03) Head (not north) = rootLoc + YOffset_Head (~ 0.023); Head (north) = rootLoc + YOffset_Shell; (~ 0.020) Face tattoo = rootLoc + YOffset_OnHead - YOffset_Tattoo; (~ 0.028) Head wounds (under clothes) = rootLoc + YOffset_OnHead; (~ 0.029) Hair = rootLoc + YOffset_OnHead; (~ 0.029) Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031) */ } }