mirror of
https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
synced 2024-08-15 00:43:27 +00:00
369 lines
No EOL
16 KiB
C#
369 lines
No EOL
16 KiB
C#
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")));
|
|
}
|
|
|
|
// 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<string> requiredGenitals, Pawn pawn, ref string failReason)
|
|
{
|
|
int handCount = 0;
|
|
bool pawnInBed = pawn.IsInBed(out Building bed);
|
|
|
|
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
|
if (hands != null)
|
|
{ handCount = hands.Count(); }
|
|
|
|
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<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
|
|
Pawn Target = pawn.GetSexReceiver();
|
|
|
|
foreach (Pawn participant in pawnsToAnimate)
|
|
{
|
|
int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp<CompBodyAnimator>());
|
|
DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine if a body addon is covered by apparel
|
|
/*public static bool BodyAddonCoveredByApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
|
{
|
|
foreach (ApparelGraphicRecord apparelGraphicRecord in pawn.Drawer.renderer.graphics.apparelGraphics)
|
|
{
|
|
Apparel apparel = apparelGraphicRecord.sourceApparel;
|
|
|
|
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)))
|
|
{ return true; }
|
|
}
|
|
|
|
return false;
|
|
}*/
|
|
|
|
public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
|
{
|
|
if (bodyAddon?.hiddenUnderApparelFor == null || bodyAddon?.hiddenUnderApparelTag == null)
|
|
{ return false; }
|
|
|
|
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
|
{
|
|
if (ApparelAnimationUtility.BodyAddonCoveredByApparel(apparel, bodyAddon))
|
|
{ return true; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Determine if a body addon should be drawn
|
|
public static bool CanDrawAddon(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
|
{
|
|
if (bodyAddon == null)
|
|
{ return false; }
|
|
|
|
if (pawn.RaceProps.Animal)
|
|
{ return true; }
|
|
|
|
Building_Bed building_Bed = pawn.CurrentBed();
|
|
|
|
if ((building_Bed == null ||
|
|
building_Bed.def.building.bed_showSleeperBody ||
|
|
bodyAddon.drawnInBed) && (bodyAddon.backstoryRequirement.NullOrEmpty() || pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement)))
|
|
{
|
|
if (!bodyAddon.drawnDesiccated)
|
|
{
|
|
Corpse corpse = pawn.Corpse;
|
|
if (corpse != null && corpse.GetRotStage() == RotStage.Dessicated)
|
|
{ return false; }
|
|
}
|
|
|
|
if (!bodyAddon.bodyPart.NullOrEmpty() &&
|
|
!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord bpr) => bpr.untranslatedCustomLabel == bodyAddon.bodyPart || bpr.def.defName == bodyAddon.bodyPart))
|
|
{
|
|
List<AlienPartGenerator.BodyAddonHediffGraphic> list = bodyAddon.hediffGraphics;
|
|
bool flag;
|
|
if (list == null)
|
|
{ flag = false; }
|
|
|
|
else
|
|
{ flag = list.Any((AlienPartGenerator.BodyAddonHediffGraphic bahg) => bahg.hediff == HediffDefOf.MissingBodyPart); }
|
|
|
|
if (!flag)
|
|
{ return false; }
|
|
}
|
|
|
|
if ((pawn.gender == Gender.Female) ? bodyAddon.drawForFemale : bodyAddon.drawForMale)
|
|
{
|
|
if (bodyAddon.bodyTypeRequirement.NullOrEmpty() || pawn.story.bodyType.ToString() == bodyAddon.bodyTypeRequirement)
|
|
{
|
|
bool renderClothes = true;
|
|
|
|
if (Find.WindowStack.currentlyDrawnWindow is Page_ConfigureStartingPawns)
|
|
{ renderClothes = (bool)AccessTools.Field(typeof(Page_ConfigureStartingPawns), "renderClothes").GetValue(Find.WindowStack.currentlyDrawnWindow); }
|
|
|
|
else
|
|
{ renderClothes = pawn.Drawer.renderer.graphics.apparelGraphics.Count > 0; }
|
|
|
|
bool conditionA = !BodyAddonCoveredByWornApparel(pawn, bodyAddon);
|
|
bool conditionB = !renderClothes;
|
|
bool conditionC = pawn.GetPosture() == PawnPosture.Standing;
|
|
bool conditionD = (pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround;
|
|
bool conditionE = pawn.GetPosture() == PawnPosture.LayingInBed && bodyAddon.drawnInBed;
|
|
|
|
return (conditionA || conditionB) && (conditionC || conditionD || conditionE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 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; }
|
|
|
|
// Try to draw apparel thrown on ground
|
|
if (ApparelSettings.clothesThrownOnGround)
|
|
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
|
|
|
|
// Get components
|
|
List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
|
|
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
|
|
CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
|
|
|
// Get available hands
|
|
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
|
int handsAvailableCount = hands.Count();
|
|
|
|
// 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<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset).ToList();
|
|
List<int> idxBodyAddons = sortedBodyAddons.Select(x => x.Value).ToList();
|
|
|
|
for (int idx = 0; idx < idxBodyAddons.Count; idx++)
|
|
{
|
|
int i = idxBodyAddons[idx];
|
|
AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
|
|
BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
|
|
|
|
bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
|
|
|
|
Graphic addonGraphic = alienComp.addonGraphics[i];
|
|
//DebugMode.Message(" Trying to draw " + addonGraphic.path);
|
|
|
|
Rot4 apparentRotation = rotation;
|
|
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
|
{ apparentRotation = alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing; }
|
|
|
|
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 offsets = bodyAddon.offsets.GetOffset(apparentRotation);
|
|
Vector3 vector2 = bodyTypeOffset + ((offsets != null) ? offsets.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 ((bodyPartRecord != null && (bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.GenitalsBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.ChestBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.AnusBPG))) ||
|
|
addonGraphic.path.ToLower().Contains("belly"))
|
|
{
|
|
vector2.y = (vector2.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 &&
|
|
BodyAddonCoveredByWornApparel(pawn, bodyAddon) == false &&
|
|
apparentRotation == Rot4.South)
|
|
{ vector2.y += 0.010f; }
|
|
}
|
|
|
|
// Otherwise use the standard offsets
|
|
else
|
|
{ vector2.y = 0.3f + vector2.y; }
|
|
|
|
if (!bodyAddon.inFrontOfBody)
|
|
{ vector2.y *= -1f; }
|
|
|
|
float bodyAddonAngle = bodyAddon.angle;
|
|
|
|
if (apparentRotation == Rot4.North)
|
|
{
|
|
if (bodyAddon.layerInvert)
|
|
{ vector2.y = -vector2.y; }
|
|
|
|
bodyAddonAngle = 0f;
|
|
}
|
|
|
|
if (apparentRotation == Rot4.East)
|
|
{
|
|
bodyAddonAngle = -bodyAddonAngle;
|
|
vector2.x = -vector2.x;
|
|
}
|
|
|
|
Quaternion addonRotation = quat;
|
|
Quaternion quatAdditional = Quaternion.identity;
|
|
|
|
float finalAngle = 0;
|
|
|
|
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
|
{
|
|
if (pawnAnimator.controlGenitalAngle && bodyAddon?.hediffGraphics != null && !bodyAddon.hediffGraphics.NullOrEmpty() && bodyAddon.hediffGraphics[0]?.path != null && (bodyAddon.hediffGraphics[0].path.Contains("Penis") || bodyAddon.hediffGraphics[0].path.Contains("penis")))
|
|
{
|
|
float bodyAngle = pawnAnimator.bodyAngle;
|
|
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
|
|
|
float anglePenis = AnimationSettings.controlGenitalRotation ? pawnAnimator.genitalAngle : 0f;
|
|
anglePenis = anglePenis < 0 ? 360 - (360 % anglePenis) : anglePenis;
|
|
quatAdditional = Quaternion.AngleAxis(angle: anglePenis, axis: Vector3.up);
|
|
|
|
finalAngle = bodyAngle + anglePenis;
|
|
}
|
|
|
|
else if (alignWithHead)
|
|
{
|
|
float headAngle = pawnAnimator.headAngle;
|
|
headAngle = headAngle < 0 ? 360 - (360 % headAngle) : headAngle;
|
|
addonRotation = Quaternion.AngleAxis(angle: headAngle, axis: Vector3.up);
|
|
|
|
finalAngle = pawnAnimator.bodyAngle + headAngle;
|
|
}
|
|
|
|
else
|
|
{
|
|
float bodyAngle = pawnAnimator.bodyAngle;
|
|
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
|
|
|
finalAngle = bodyAngle;
|
|
}
|
|
}
|
|
|
|
// Fixes 'leaning left' issue with Yayo's animations
|
|
else if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && (pawnAnimator == null || pawnAnimator.isAnimating == false))
|
|
{
|
|
float bodyAngle = addonRotation.eulerAngles.y;
|
|
bodyAngle = bodyAngle < 0 ? 360 - (360 % bodyAngle) : bodyAngle;
|
|
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
|
}
|
|
|
|
if (alignWithHead && bodyAddon.alignWithHead == false)
|
|
{ vector2 -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
|
|
|
|
Vector3 finalPosition = vector + (alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f);
|
|
|
|
// Draw the addon if visible
|
|
if (CanDrawAddon(pawn, bodyAddon))
|
|
{
|
|
GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
|
|
loc: finalPosition,
|
|
quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
|
|
mat: addonGraphic.MatAt(rot: apparentRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
|
|
}
|
|
|
|
// Draw hand over the body part if required
|
|
if (BasicSettings.showHands && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && handsAvailableCount > 0)
|
|
{
|
|
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
|
|
{ handsAvailableCount--; }
|
|
}
|
|
}
|
|
|
|
// Body addons are sometimes are not appropriately concealed by long hair, so re-draw the pawn's hair here
|
|
if (pawn.Drawer.renderer.graphics.headGraphic != null)
|
|
{
|
|
var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null);
|
|
|
|
Rot4 headFacing = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headFacing : rotation;
|
|
float headAngle = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.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 });
|
|
}
|
|
|
|
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)
|
|
*/
|
|
}
|
|
} |