From a106fd60f15edb12c556588784546e902db0c5dd Mon Sep 17 00:00:00 2001 From: Taleir of Deynai Date: Sun, 26 Feb 2023 16:51:23 -0800 Subject: [PATCH] Attempts to get the `DrawAddonsFinalHook` patch working. There's still something off about the offsets, but I'm not quite sure what. --- .../Patch_HumanoidAlienRaces.csproj | 3 +- .../Source/HarmonyPatch_AlienRace.cs | 247 +++++++++++++----- 2 files changed, 185 insertions(+), 65 deletions(-) diff --git a/Patch_HumanoidAlienRaces/Patch_HumanoidAlienRaces.csproj b/Patch_HumanoidAlienRaces/Patch_HumanoidAlienRaces.csproj index e34e96e..e495f73 100644 --- a/Patch_HumanoidAlienRaces/Patch_HumanoidAlienRaces.csproj +++ b/Patch_HumanoidAlienRaces/Patch_HumanoidAlienRaces.csproj @@ -1,4 +1,4 @@ - + @@ -12,6 +12,7 @@ v4.8 512 true + 9 false diff --git a/Patch_HumanoidAlienRaces/Source/HarmonyPatch_AlienRace.cs b/Patch_HumanoidAlienRaces/Source/HarmonyPatch_AlienRace.cs index 8ab0255..1d3e26c 100644 --- a/Patch_HumanoidAlienRaces/Source/HarmonyPatch_AlienRace.cs +++ b/Patch_HumanoidAlienRaces/Source/HarmonyPatch_AlienRace.cs @@ -6,48 +6,158 @@ using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; +using System.Runtime.CompilerServices; using UnityEngine; using Verse; using AlienRace; -namespace Rimworld_Animations { - - +namespace Rimworld_Animations +{ [StaticConstructorOnStartup] public static class HarmonyPatch_AlienRace { + static readonly Type AlienRace_HarmonyPatches = AccessTools.TypeByName("AlienRace.HarmonyPatches"); + + static readonly MethodInfo InnerDrawAddon; + static readonly FieldInfo AccessForPawn; + static readonly FieldInfo AccessForRotation; + static HarmonyPatch_AlienRace() { - (new Harmony("rjwanim")).Patch(AccessTools.Method(AccessTools.TypeByName("AlienRace.HarmonyPatches"), "DrawAddons"), - prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), "Prefix_AnimateHeadAddons"))); + AccessForPawn = null; + AccessForRotation = null; + InnerDrawAddon = AccessTools.FirstMethod(AlienRace_HarmonyPatches, (mi) => + { + if (mi.GetCustomAttribute() is null) return false; + if (mi.ReturnType != typeof(void)) return false; + if (!mi.Name.Contains("DrawAddon")) return false; + + var parameters = mi.GetParameters(); + if (parameters.Length != 4) return false; + if (parameters[0].ParameterType != typeof(AlienPartGenerator.BodyAddon)) return false; + if (parameters[1].ParameterType != typeof(Graphic)) return false; + if (parameters[2].ParameterType != typeof(Vector2)) return false; + return true; + }); + + if (InnerDrawAddon is null) + { + Log.Error("[rjwanim] Failed to apply Alien Race patches: could not find local `DrawAddon` method."); + return; + } + + // Extract the closure struct. This is passed with `ref`, so we have + // to also pull the element type to get rid of that. + var displayClassType = InnerDrawAddon.GetParameters()[3].ParameterType.GetElementType(); + + if (displayClassType is null) + { + Log.Error("[rjwanim] Failed to apply Alien Race patches: could not get type of `DrawAddon` closure."); + return; + } + + AccessForPawn = AccessTools.Field(displayClassType, "pawn"); + + if (AccessForPawn is null) + { + Log.Error("[rjwanim] Failed to apply Alien Race patches: could not find field `pawn` of closure."); + return; + } + + AccessForRotation = AccessTools.Field(displayClassType, "rotation"); + + if (AccessForRotation is null) + { + Log.Error("[rjwanim] Failed to apply Alien Race patches: could not find field `rotation` of closure."); + return; + } + + // Got access to everything. It should be safe to setup the patches. + var harmonyInstance = new Harmony("rjwanim"); + harmonyInstance.Patch( + InnerDrawAddon, + prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), nameof(Prefix_FixDrawAddonRotation))), + postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), nameof(Postfix_FixDrawAddonRotation))) + ); + harmonyInstance.Patch( + AccessTools.Method(AlienRace_HarmonyPatches, "DrawAddonsFinalHook"), + postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), nameof(Postfix_DrawAddonsFinalHook))) + ); } - /* todo: replace jank prefix with this - public static void Prefix_DrawAddonsFinalHook(ref Pawn pawn, ref AlienPartGenerator.BodyAddon addon, ref Rot4 rot, ref Graphic graphic, ref Vector3 offsetVector, ref float angle, ref Material mat) - { - CompBodyAnimator animator = pawn.TryGetComp(); + public static bool ShouldForceDrawForBody(Pawn pawn, AlienPartGenerator.BodyAddon addon) + { + if (pawn.def is not ThingDef_AlienRace alienProps) return false; - if (animator == null || !animator.isAnimating) - { - return; - } + if (alienProps.defName.Contains("Orassan") && addon.path.ToLower().Contains("tail")) + return true; - if(addon.alignWithHead || addon.drawnInBed) - { - rot = animator.headFacing; + return false; + } + + // The parameter `__3` is the compiler generated closure struct. + // Even though `__3` is a value type passed by reference, receiving it as + // an object boxes the underlying reference, so we don't need `ref` here. + // In fact, if you try to add it, RimWorld will crash outright! + public static void Prefix_FixDrawAddonRotation(AlienPartGenerator.BodyAddon ba, object __3, out Rot4 __state) + { + // Store the original rotation so we can restore it later. + __state = (Rot4)AccessForRotation.GetValue(__3); + if (ba is null) return; + + var pawn = (Pawn)AccessForPawn.GetValue(__3); + if (pawn.TryGetComp() is not { } animator) return; + if (!animator.isAnimating) return; + + var forceDrawForBody = ShouldForceDrawForBody(pawn, ba); + + // Set the rotation according to the animation. + if ((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead) + AccessForRotation.SetValue(__3, animator.headFacing); + else + AccessForRotation.SetValue(__3, animator.bodyFacing); + } + + public static void Postfix_FixDrawAddonRotation(object __3, Rot4 __state) + { + // Restore the original value, since we're in a loop and the next + // part may need the previous rotation. Just being safe. + AccessForRotation.SetValue(__3, __state); + } + + public static void Postfix_DrawAddonsFinalHook(Pawn pawn, AlienPartGenerator.BodyAddon addon, Rot4 rot, ref Vector3 offsetVector, ref float angle) + { + if (pawn.TryGetComp() is not { } animator) return; + if (!animator.isAnimating) return; + + var forceDrawForBody = ShouldForceDrawForBody(pawn, addon); + + if ((addon.drawnInBed && !forceDrawForBody) || addon.alignWithHead) + { angle = animator.headAngle; - offsetVector += animator.deltaPos + animator.bodyAngle * animator.headBob; - + offsetVector += animator.deltaPos + animator.headBob; } else - { - rot = animator.bodyFacing; - angle = animator.bodyAngle; + { + if (AnimationSettings.controlGenitalRotation && addon.path.ToLower().Contains("penis")) + angle = animator.genitalAngle; + else + angle = animator.bodyAngle; offsetVector += animator.deltaPos; - } - - } - */ + } + + if (rot == Rot4.North && addon.layerInvert) + { + offsetVector.y = -offsetVector.y; + } + + if (rot == Rot4.East) + { + angle *= -1f; + offsetVector.x = -offsetVector.x; + } + } + public static bool Prefix_AnimateHeadAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation) { @@ -64,14 +174,14 @@ namespace Rimworld_Animations { for (int i = 0; i < addons.Count; i++) { AlienPartGenerator.BodyAddon ba = addons[index: i]; - + if (!ba.CanDrawAddon(pawn: pawn)) continue; bool forceDrawForBody = false; if (alienProps.defName.Contains("Orassan") && ba.path.ToLower().Contains("tail")) - { + { forceDrawForBody = true; - } + } AlienPartGenerator.RotationOffset offset = ba.defaultOffsets.GetOffset((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing); Vector3 a = (offset != null) ? offset.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, pawn.story.headType) : Vector3.zero; AlienPartGenerator.RotationOffset offset2 = ba.offsets.GetOffset((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing); @@ -97,37 +207,46 @@ namespace Rimworld_Animations { if ((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead) { - + Quaternion addonRotation = Quaternion.AngleAxis(pawnAnimator.headAngle < 0 ? 360 - (360 % pawnAnimator.headAngle) : pawnAnimator.headAngle, Vector3.up); GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: pawnAnimator.headFacing), loc: vector + (ba.alignWithHead ? headOffset : headOffset - addonRotation * pawn.Drawer.renderer.BaseHeadOffsetAt(pawnAnimator.headFacing)) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f), quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * addonRotation, mat: addonGraphic.MatAt(rot: pawnAnimator.headFacing), renderFlags.FlagSet(PawnRenderFlags.DrawNow)); - + } else { Quaternion addonRotation; if (AnimationSettings.controlGenitalRotation && ba.path.ToLower().Contains("penis")) - { + { addonRotation = Quaternion.AngleAxis(pawnAnimator.genitalAngle, Vector3.up); } else - { + { addonRotation = Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up); } - + if (AnimationSettings.controlGenitalRotation && pawnAnimator.controlGenitalAngle && ba?.hediffGraphics != null && ba.hediffGraphics.Count != 0 && ba.hediffGraphics[0]?.path != null && (ba.hediffGraphics[0].path.Contains("Penis") || ba.hediffGraphics[0].path.Contains("penis"))) { - GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: rotation), loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f), - quat: Quaternion.AngleAxis(angle: pawnAnimator.genitalAngle, axis: Vector3.up), mat: addonGraphic.MatAt(rot: rotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow)); + GenDraw.DrawMeshNowOrLater( + mesh: addonGraphic.MeshAt(rot: rotation), + loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f), + quat: Quaternion.AngleAxis(angle: pawnAnimator.genitalAngle, axis: Vector3.up), + mat: addonGraphic.MatAt(rot: rotation), + drawNow: renderFlags.FlagSet(PawnRenderFlags.DrawNow) + ); } - else - { - GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: rotation), loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f), - quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * addonRotation, mat: addonGraphic.MatAt(rot: rotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow)); + { + GenDraw.DrawMeshNowOrLater( + mesh: addonGraphic.MeshAt(rot: rotation), + loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f), + quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * addonRotation, + mat: addonGraphic.MatAt(rot: rotation), + drawNow: renderFlags.FlagSet(PawnRenderFlags.DrawNow) + ); } } @@ -143,20 +262,20 @@ namespace Rimworld_Animations { /* - [HarmonyPatch(typeof(AlienRace.HarmonyPatches), "DrawAddons")] - public static class HarmonyPatch_AlienRace { + [HarmonyPatch(typeof(AlienRace.HarmonyPatches), "DrawAddons")] + public static class HarmonyPatch_AlienRace { public static void RenderHeadAddonInAnimation(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow, Graphic graphic, AlienPartGenerator.BodyAddon bodyAddon, Vector3 v, Vector3 headOffset, Pawn pawn, PawnRenderFlags renderFlags, Vector3 vector, Rot4 rotation) - { + { CompBodyAnimator pawnAnimator = pawn.TryGetComp(); AlienPartGenerator.AlienComp comp = pawn.GetComp(); if (pawnAnimator != null && pawnAnimator.isAnimating) - { + { if((bodyAddon.drawnInBed || bodyAddon.alignWithHead)) - { + { AlienPartGenerator.RotationOffset offset = bodyAddon.defaultOffsets.GetOffset(rotation); Vector3 a = (offset != null) ? offset.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero; @@ -184,7 +303,7 @@ namespace Rimworld_Animations { mat = graphic.MatAt(rot: pawnAnimator.headFacing); } else - { + { AlienPartGenerator.RotationOffset offset = bodyAddon.defaultOffsets.GetOffset(rotation); Vector3 a = (offset != null) ? offset.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero; @@ -215,17 +334,17 @@ namespace Rimworld_Animations { /* if (pawnAnimator != null && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator.isAnimating && (bodyAddon.drawnInBed || bodyAddon.alignWithHead)) - { + { if ((pawn.def as ThingDef_AlienRace).defName == "Alien_Orassan") - { + { orassan = true; if(bodyAddon.path.Contains("closed")) - { + { return; - } + } if (bodyAddon.bodyPart.Contains("ear")) @@ -239,10 +358,10 @@ namespace Rimworld_Animations { orassanv.y += 1f; if(bodyAddon.bodyPart.Contains("left")) - { + { orassanv.x += 0.03f; - } else - { + } else + { orassanv.x -= 0.03f; } @@ -256,7 +375,7 @@ namespace Rimworld_Animations { orassanv.x = 0.1f; } else - { + { orassanv.z -= 0.1f; orassanv.y += 1f; @@ -282,7 +401,7 @@ namespace Rimworld_Animations { } else - { + { } @@ -291,16 +410,16 @@ namespace Rimworld_Animations { public static IEnumerable Transpiler(IEnumerable instructions) - { + { List ins = instructions.ToList(); for (int i = 0; i < ins.Count; i++) - { + { Type[] type = new Type[] { typeof(Mesh), typeof(Vector3), typeof(Quaternion), typeof(Material), typeof(bool) }; if (ins[i].OperandIs(AccessTools.Method(typeof(GenDraw), "DrawMeshNowOrLater", type))) - { + { yield return new CodeInstruction(OpCodes.Ldloc, (object)7); //graphic yield return new CodeInstruction(OpCodes.Ldloc, (object)4); //bodyAddon @@ -313,28 +432,28 @@ namespace Rimworld_Animations { yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(HarmonyPatch_AlienRace), "RenderHeadAddonInAnimation")); - } + } else - { + { yield return ins[i]; } - } - } + } + } public static bool Prefix(PawnRenderFlags renderFlags, ref Vector3 vector, ref Vector3 headOffset, Pawn pawn, ref Quaternion quat, ref Rot4 rotation) { if(pawn == null) - { + { return true; } CompBodyAnimator anim = pawn.TryGetComp(); if(anim == null) - { + { return true; - } + } if (anim != null && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && anim.isAnimating) {