233 lines
11 KiB
C#
233 lines
11 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_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<Pawn> participants)
|
|
{
|
|
foreach (Pawn participant in participants)
|
|
{
|
|
CompPawnSexData comp = participant.TryGetComp<CompPawnSexData>();
|
|
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<CompPawnSexData>();
|
|
if (comp == null) return;
|
|
|
|
comp.handAnimationDef = DefDatabase<HandAnimationDef>.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<string> requiredGenitals, Pawn pawn, ref string failReason)
|
|
{
|
|
int? handCount = pawn.TryGetComp<CompPawnSexData>()?.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;
|
|
}
|
|
|
|
// 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<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons;
|
|
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
|
|
CompBodyAnimator animatorComp = pawn.TryGetComp<CompBodyAnimator>();
|
|
CompPawnSexData sexDataComp = pawn.TryGetComp<CompPawnSexData>();
|
|
ActorAnimationData pawnAnimationData = pawn?.GetAnimationData();
|
|
|
|
// Try to draw apparel thrown on ground
|
|
if (ApparelSettings.clothesThrownOnGround)
|
|
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
|
|
|
|
// Exit clauses
|
|
if (BasicSettings.useLegacyAnimationSystem || AnimationPatchUtility.ShouldNotAnimatePawn(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<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset);
|
|
IEnumerable<int> 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", StringComparison.OrdinalIgnoreCase) == 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)
|
|
*/
|
|
}
|
|
}
|