rimworld-animations-patch_m.../Source/Scripts/Patches/HarmonyPatch_Rimworld_Anima...

219 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 true;
// 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 = 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 bodyAddonPosition = vector + (bodyAddonDatum.alignsWithHead ? headOffset : Vector3.zero) + bodyAddonDatum.GetOffset(apparentRotation).RotatedBy(angle: bodyAngle);
// Draw the addon if visible
if (canDraw)
{
addonGraphic.drawSize = AnimationPatchUtility.DetermineDrawSize(pawn, alienComp, bodyAddon, bodyAddonDatum, renderFlags.FlagSet(PawnRenderFlags.Portrait));
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)
*/
}
}