diff --git a/Defs/Jobs.xml b/Defs/Jobs.xml index 97ae1715..76987b62 100644 --- a/Defs/Jobs.xml +++ b/Defs/Jobs.xml @@ -47,7 +47,7 @@ false true false - false + true false true false diff --git a/Source/AMRetextureSupport/AMRetextureSupport.csproj b/Source/AMRetextureSupport/AMRetextureSupport.csproj index d384917f..feb2b0c9 100644 --- a/Source/AMRetextureSupport/AMRetextureSupport.csproj +++ b/Source/AMRetextureSupport/AMRetextureSupport.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/AlienRacesPatch/AlienRacesPatch.csproj b/Source/AlienRacesPatch/AlienRacesPatch.csproj index 36d97e45..009d98cf 100644 --- a/Source/AlienRacesPatch/AlienRacesPatch.csproj +++ b/Source/AlienRacesPatch/AlienRacesPatch.csproj @@ -13,7 +13,7 @@ - + diff --git a/Source/AnimationMod.sln b/Source/AnimationMod.sln index 579d805a..c7a658a3 100644 --- a/Source/AnimationMod.sln +++ b/Source/AnimationMod.sln @@ -30,6 +30,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patches", "Patches", "{7C1C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TacticowlPatch", "TacticowlPatch\TacticowlPatch.csproj", "{DEC2223C-6DD4-48EF-9438-8F4E97B6C542}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FacialAnimationPatch", "FacialAnimationPatch\FacialAnimationPatch.csproj", "{B5AC984E-8B51-426B-9F54-CC85006449F1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,10 @@ Global {DEC2223C-6DD4-48EF-9438-8F4E97B6C542}.Debug|Any CPU.Build.0 = Debug|Any CPU {DEC2223C-6DD4-48EF-9438-8F4E97B6C542}.Release|Any CPU.ActiveCfg = Release|Any CPU {DEC2223C-6DD4-48EF-9438-8F4E97B6C542}.Release|Any CPU.Build.0 = Release|Any CPU + {B5AC984E-8B51-426B-9F54-CC85006449F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5AC984E-8B51-426B-9F54-CC85006449F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5AC984E-8B51-426B-9F54-CC85006449F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5AC984E-8B51-426B-9F54-CC85006449F1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,6 +104,7 @@ Global {06C481CC-E8D0-46D6-B02B-127DD9E3A1AA} = {3549402C-9759-4ABC-938C-FC4F801C156F} {7B881968-1527-40FC-9FB3-85A5A01BEE88} = {7C1CE274-810E-467E-B8EF-4AB2A83E17F9} {DEC2223C-6DD4-48EF-9438-8F4E97B6C542} = {7C1CE274-810E-467E-B8EF-4AB2A83E17F9} + {B5AC984E-8B51-426B-9F54-CC85006449F1} = {7C1CE274-810E-467E-B8EF-4AB2A83E17F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1478B48A-1088-4408-9427-E3B4EADB515C} diff --git a/Source/AnimationMod/AnimRenderer.cs b/Source/AnimationMod/AnimRenderer.cs index a4738a98..016c03a8 100644 --- a/Source/AnimationMod/AnimRenderer.cs +++ b/Source/AnimationMod/AnimRenderer.cs @@ -27,8 +27,8 @@ public class AnimRenderer : IExposable public static readonly char[] Alphabet = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' }; public static readonly List PostLoadPendingAnimators = new List(); - public static event Action PrePawnSpecialRender; - public static event Action PostPawnSpecialRender; + public static Action PrePawnSpecialRender; + public static Action PostPawnSpecialRender; public static Material DefaultCutout, DefaultTransparent; public static IReadOnlyList ActiveRenderers => activeRenderers; public static IReadOnlyCollection CapturedPawns => pawnToRenderer.Keys; @@ -909,15 +909,16 @@ protected void DrawPawns(Action labelDraw = null) { // Regular pawn render. Patch_PawnRenderer_RenderPawnAt.AllowNext = true; - Patch_PawnRenderer_RenderPawnInternal.AllowNext = true; - Patch_PawnRenderer_RenderPawnInternal.DoNotModify = true; // Don't use animation position/rotation. - Patch_PawnRenderer_RenderPawnInternal.NextDrawMode = Patch_PawnRenderer_RenderPawnInternal.DrawMode.Full; - PrePawnSpecialRender?.Invoke(pawn, this); + Patch_PawnRenderer_RenderPawnAt.DoNotModify = true; // Don't use animation position/rotation. + Patch_PawnRenderer_RenderPawnAt.NextDrawMode = Patch_PawnRenderer_RenderPawnAt.DrawMode.Full; + Patch_PawnUtility_IsInvisible.IsRendering = true; + PrePawnSpecialRender?.Invoke(pawn, this, Map); pawn.DrawNowAt(pawn.DrawPosHeld ?? pawn.DrawPos); - PostPawnSpecialRender?.Invoke(pawn, this); - Patch_PawnRenderer_RenderPawnInternal.DoNotModify = false; + PostPawnSpecialRender?.Invoke(pawn, this, Map); + Patch_PawnRenderer_RenderPawnAt.DoNotModify = false; + Patch_PawnUtility_IsInvisible.IsRendering = false; // Draw label. Vector3 drawPos2 = pawn.DrawPos; @@ -959,24 +960,26 @@ protected void DrawPawns(Action labelDraw = null) // Head rotation needs to be sent manually because the head // does not have a direction curve to sample. - Patch_PawnRenderer_RenderPawnInternal.HeadRotation = dir; // Copy body facing direction. + Patch_PawnRenderer_RenderPawnAt.HeadRotation = dir; // Copy body facing direction. } // Render pawn in custom position using patches. Patch_PawnRenderer_RenderPawnAt.AllowNext = true; - Patch_PawnRenderer_RenderPawnInternal.AllowNext = true; - Patch_PawnRenderer_RenderPawnInternal.NextDrawMode = !isBeheaded - ? Patch_PawnRenderer_RenderPawnInternal.DrawMode.Full - : i == 0 ? Patch_PawnRenderer_RenderPawnInternal.DrawMode.BodyOnly : Patch_PawnRenderer_RenderPawnInternal.DrawMode.HeadOnly; + Patch_PawnRenderer_RenderPawnAt.NextDrawMode = !isBeheaded + ? Patch_PawnRenderer_RenderPawnAt.DrawMode.Full + : i == 0 ? Patch_PawnRenderer_RenderPawnAt.DrawMode.BodyOnly : Patch_PawnRenderer_RenderPawnAt.DrawMode.HeadOnly; Patch_PawnRenderer_DrawShadowInternal.Suppress = suppressShadow; // In 1.4 shadow rendering is baked into RenderPawnAt and may need to be prevented. - PrePawnSpecialRender?.Invoke(pawn, this); + Patch_PawnUtility_IsInvisible.IsRendering = true; + + PrePawnSpecialRender?.Invoke(pawn, this, Map); pawn.Drawer.renderer.RenderPawnAt(pos, dir, true); // This direction here is not the final one. - PostPawnSpecialRender?.Invoke(pawn, this); + PostPawnSpecialRender?.Invoke(pawn, this, Map); Patch_PawnRenderer_DrawShadowInternal.Suppress = false; + Patch_PawnUtility_IsInvisible.IsRendering = false; } // Render shadow. diff --git a/Source/AnimationMod/AnimationMod.csproj b/Source/AnimationMod/AnimationMod.csproj index a8c3c8e3..8d8624ae 100644 --- a/Source/AnimationMod/AnimationMod.csproj +++ b/Source/AnimationMod/AnimationMod.csproj @@ -26,14 +26,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/AnimationMod/AutoDuel/AutoFriendlyDuelMapComp.cs b/Source/AnimationMod/AutoDuel/AutoFriendlyDuelMapComp.cs index e26f46e1..641ef47e 100644 --- a/Source/AnimationMod/AutoDuel/AutoFriendlyDuelMapComp.cs +++ b/Source/AnimationMod/AutoDuel/AutoFriendlyDuelMapComp.cs @@ -155,7 +155,15 @@ public static bool CanPawnDuel(Pawn pawn) return false; // Check pawn is in recreation and not doing anything majorly important. - if (pawn.timetable.CurrentAssignment != TimeAssignmentDefOf.Joy || !(pawn.CurJobDef?.playerInterruptible ?? true)) + bool currentTimetableAllowsJob = pawn.timetable.CurrentAssignment.allowJoy; + bool currentTimetableIsSleep = pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Sleep; + if (currentTimetableIsSleep || !currentTimetableAllowsJob) + return false; + + bool canInterruptJob = pawn.CurJobDef?.playerInterruptible ?? true; + bool isJobPlayerForced = pawn.CurJob?.playerForced ?? false; + + if (!canInterruptJob || isJobPlayerForced) return false; return true; diff --git a/Source/AnimationMod/AutoDuel/JoyGiver_FriendlyDuel.cs b/Source/AnimationMod/AutoDuel/JoyGiver_FriendlyDuel.cs index 4c257ba2..5f4a79b2 100644 --- a/Source/AnimationMod/AutoDuel/JoyGiver_FriendlyDuel.cs +++ b/Source/AnimationMod/AutoDuel/JoyGiver_FriendlyDuel.cs @@ -27,12 +27,12 @@ public override Job TryGiveJob(Pawn pawn) if (!AutoFriendlyDuelMapComp.CanPawnDuel(pawn)) return null; - // Try get a duel partner. + // Try to get a duel partner. var partner = comp.TryGetRandomDuelPartner(pawn); if (partner == null) return null; - // Try get a duel spot for us two: + // Try to get a duel spot for us two: var spot = comp.TryGetBestDuelSpotFor(pawn, partner); if (spot == null) return null; diff --git a/Source/AnimationMod/GameComp.cs b/Source/AnimationMod/GameComp.cs index 89eb5135..0950912e 100644 --- a/Source/AnimationMod/GameComp.cs +++ b/Source/AnimationMod/GameComp.cs @@ -113,7 +113,6 @@ public override void GameComponentTick() GrabUtility.Tick(); Patch_Corpse_DrawAt.Tick(); - Patch_PawnRenderer_LayingFacing.Tick(); const float DT = 1 / 60f; diff --git a/Source/AnimationMod/Grappling/GrappleFlyer.cs b/Source/AnimationMod/Grappling/GrappleFlyer.cs index de457165..b6465ba0 100644 --- a/Source/AnimationMod/Grappling/GrappleFlyer.cs +++ b/Source/AnimationMod/Grappling/GrappleFlyer.cs @@ -89,7 +89,7 @@ public override void SpawnSetup(Map map, bool respawningAfterLoad) ticksFlightTime = Mathf.Max(2, (int)(ticksFlightTime / (speed * Core.Settings.GrappleSpeed))); } - private void RecomputePosition() + private new void RecomputePosition() { if (this.positionLastComputedTick == this.ticksFlying) { @@ -158,7 +158,7 @@ public static void DrawBoundTexture(Pawn pawn, Vector3 drawLoc, Color ropeColor) Graphics.DrawMesh(MeshPool.plane10, trs, mat, 0, null, 0, mpb); } - private void DrawShadow(Vector3 drawLoc, float height) + private new void DrawShadow(Vector3 drawLoc, float height) { Material shadowMaterial = this.ShadowMaterial; if (shadowMaterial == null) diff --git a/Source/AnimationMod/Heads/HeadInstance.cs b/Source/AnimationMod/Heads/HeadInstance.cs index fedc995a..6d69354d 100644 --- a/Source/AnimationMod/Heads/HeadInstance.cs +++ b/Source/AnimationMod/Heads/HeadInstance.cs @@ -33,19 +33,24 @@ public bool Render() return true; //Render pawn in custom position using patches. - Patch_PawnRenderer_RenderPawnInternal.NextDrawMode = Patch_PawnRenderer_RenderPawnInternal.DrawMode.HeadStandalone; - Patch_PawnRenderer_RenderPawnInternal.HeadRotation = Direction; - Patch_PawnRenderer_RenderPawnInternal.StandaloneHeadRotation = Rotation; + Patch_PawnRenderer_RenderPawnAt.NextDrawMode = Patch_PawnRenderer_RenderPawnAt.DrawMode.HeadStandalone; + Patch_PawnRenderer_RenderPawnAt.HeadRotation = Direction; + Patch_PawnRenderer_RenderPawnAt.StandaloneHeadAngle = Rotation; + Patch_PawnRenderer_RenderPawnAt.StandaloneHeadPosition = Position; + Patch_PawnRenderer_RenderPawnAt.AllowNext = true; Patch_PawnRenderer_DrawShadowInternal.Suppress = true; // In 1.4 shadow rendering is baked into RenderPawnAt and may need to be prevented. - Patch_PawnRenderer_RenderPawnInternal.AllowNext = true; try { + AnimRenderer.PrePawnSpecialRender?.Invoke(Pawn, null, Map); + Pawn.Drawer.renderer.RenderPawnAt(Position, Direction, true); + + AnimRenderer.PostPawnSpecialRender?.Invoke(Pawn, null, Map); } finally { - Patch_PawnRenderer_RenderPawnInternal.NextDrawMode = Patch_PawnRenderer_RenderPawnInternal.DrawMode.Full; + Patch_PawnRenderer_RenderPawnAt.NextDrawMode = Patch_PawnRenderer_RenderPawnAt.DrawMode.Full; Patch_PawnRenderer_DrawShadowInternal.Suppress = false; } return true; diff --git a/Source/AnimationMod/Outcome/OutcomeUtility.cs b/Source/AnimationMod/Outcome/OutcomeUtility.cs index 4c783181..7e4f660b 100644 --- a/Source/AnimationMod/Outcome/OutcomeUtility.cs +++ b/Source/AnimationMod/Outcome/OutcomeUtility.cs @@ -501,8 +501,7 @@ private static bool Kill(Pawn killer, Pawn pawn, in AdditionalArgs args) { // Do corpse interpolation - interpolates the corpse to the correct position, after the animated position. Patch_Corpse_DrawAt.Interpolators[pawn.Corpse] = new CorpseInterpolate(pawn.Corpse, ss.GetWorldPosition()); - - Patch_PawnRenderer_LayingFacing.OverrideRotations[pawn] = ss.GetWorldDirection(); + Patch_Corpse_DrawAt.OverrideRotations[pawn] = ss.GetWorldDirection(); } else if (!isDeathless) { diff --git a/Source/AnimationMod/Patches/Patch_Corpse_DrawAt.cs b/Source/AnimationMod/Patches/Patch_Corpse_DrawAt.cs index d0f52d5c..9835d707 100644 --- a/Source/AnimationMod/Patches/Patch_Corpse_DrawAt.cs +++ b/Source/AnimationMod/Patches/Patch_Corpse_DrawAt.cs @@ -7,37 +7,50 @@ namespace AM.Patches; - -[HarmonyPatch(typeof(ThingWithComps), nameof(ThingWithComps.DrawAt))] // Corpse DrawAt was removed in 1.5 +[HarmonyPatch(typeof(PawnRenderer), nameof(PawnRenderer.ParallelPreRenderPawnAt))] // Corpse DrawAt was removed in 1.5 public static class Patch_Corpse_DrawAt { public static readonly Dictionary Interpolators = new Dictionary(); + public static readonly Dictionary OverrideRotations = new Dictionary(); public static void Tick() { if(GenTicks.TicksGame % (60 * 30) == 0) + { Interpolators.RemoveAll(p => !p.Key.Spawned); + OverrideRotations.RemoveAll(p => !p.Key.SpawnedOrAnyParentSpawned); + } } - [HarmonyPriority(Priority.First)] - private static void Prefix(ThingWithComps __instance, ref Vector3 drawLoc) + [HarmonyPriority(Priority.Last)] + private static void Prefix(PawnRenderer __instance, ref Vector3 drawLoc, ref Rot4? rotOverride) { - if (__instance is not Corpse corpse) + var corpse = __instance.pawn.ParentHolder as Corpse; + + if (corpse == null) return; - DoOffsetLogic(corpse, ref drawLoc); + DoOffsetLogic(corpse, ref drawLoc, ref rotOverride); } - private static void DoOffsetLogic(Corpse __instance, ref Vector3 drawLoc) + private static void DoOffsetLogic(Corpse corpse, ref Vector3 drawLoc, ref Rot4? rotOverride) { + // Override facing direction of corpse: + if (OverrideRotations.TryGetValue(corpse.InnerPawn, out var foundRot)) + { + rotOverride = foundRot; + } + + // Override position and rotation of corpse... + if (Core.Settings.CorpseOffsetMode == CorpseOffsetMode.None) return; - if (!Interpolators.TryGetValue(__instance, out var found)) + if (!Interpolators.TryGetValue(corpse, out var found)) return; if (!found.Update(ref drawLoc)) - Interpolators.Remove(__instance); + Interpolators.Remove(corpse); } } @@ -53,6 +66,7 @@ public class CorpseInterpolate public CorpseInterpolate(Corpse corpse, Vector3 startPos) { TargetPosition = corpse.DrawPos; + startPos.y = TargetPosition.y; InitialOffset = startPos - TargetPosition; CurrentPosition = startPos; } diff --git a/Source/AnimationMod/Patches/Patch_HediffSet_HasHead.cs b/Source/AnimationMod/Patches/Patch_HediffSet_HasHead.cs new file mode 100644 index 00000000..b2b863b0 --- /dev/null +++ b/Source/AnimationMod/Patches/Patch_HediffSet_HasHead.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using Verse; + +namespace AM.Patches; + +[HarmonyPatch(typeof(HediffSet), nameof(HediffSet.HasHead), MethodType.Getter)] +public static class Patch_HediffSet_HasHead +{ + public static bool? ForcedHasHeadValue; + + [HarmonyPriority(Priority.First)] + public static bool Prefix(ref bool __result) + { + if (ForcedHasHeadValue != null) + { + __result = ForcedHasHeadValue.Value; + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Source/AnimationMod/Patches/Patch_PawnRenderer_LayingFacing.cs b/Source/AnimationMod/Patches/Patch_PawnRenderer_LayingFacing.cs deleted file mode 100644 index a6be1a15..00000000 --- a/Source/AnimationMod/Patches/Patch_PawnRenderer_LayingFacing.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using HarmonyLib; -using Verse; - -namespace AM.Patches; - -/// -/// Only used to override corpse direction. -/// -[HarmonyPatch(typeof(PawnRenderer), nameof(PawnRenderer.LayingFacing))] -public static class Patch_PawnRenderer_LayingFacing -{ - public static readonly Dictionary OverrideRotations = new Dictionary(); - - public static void Tick() - { - if (GenTicks.TicksGame % (60 * 30) == 0) - OverrideRotations.RemoveAll(p => !p.Key.SpawnedOrAnyParentSpawned); - } - - static void Postfix(Pawn ___pawn, ref Rot4 __result) - { - if (!___pawn.Dead) - return; - - if (!OverrideRotations.TryGetValue(___pawn, out var found)) - return; - - __result = found; - } -} \ No newline at end of file diff --git a/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnAt.cs b/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnAt.cs index 90015703..2b09b5be 100644 --- a/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnAt.cs +++ b/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnAt.cs @@ -1,11 +1,13 @@ using AM.Grappling; using HarmonyLib; +using RimWorld; using UnityEngine; using Verse; namespace AM.Patches; /// +/// This is the 'main' patch that actually gets pawns rendering in animations. /// When a pawn is being animated, RenderPawnAt needs to be modified: /// first, the private field 'results' needs to be modified in order to ensure that RenderPawnDynamic is called. /// Next, call with the arguments that I want in order to modify how the pawn will actually be displayed. @@ -14,6 +16,11 @@ namespace AM.Patches; public static class Patch_PawnRenderer_RenderPawnAt { public static bool AllowNext; + public static bool DoNotModify = false; + public static DrawMode NextDrawMode = DrawMode.Full; + public static Rot4 HeadRotation; // Only used when NextDrawMode is HeadOnly or HeadStandalone. + public static float StandaloneHeadAngle; + public static Vector3 StandaloneHeadPosition; [HarmonyPriority(Priority.First)] public static bool Prefix(Pawn ___pawn, PawnRenderTree ___renderTree, ref PawnRenderer.PreRenderResults ___results, ref PawnRenderer.PreRenderResults? __state) @@ -23,11 +30,16 @@ public static bool Prefix(Pawn ___pawn, PawnRenderTree ___renderTree, ref PawnRe // Draw the ropes that bind the pawn when being grappled. DrawGrappleRopeIfRequired(___pawn); - // Try to get an active animator for this pawn. - var animator = PatchMaster.GetAnimator(___pawn); - if (animator == null) + // Standalone heads don't have or need animator. + AnimRenderer animator = null; + if (NextDrawMode != DrawMode.HeadStandalone) { - return true; + // Try to get an active animator for this pawn. + animator = PatchMaster.GetAnimator(___pawn); + if (animator == null) + { + return true; + } } // Ok, the pawn is in an active animation, but we want to suppress the regular draw call @@ -57,8 +69,19 @@ public static bool Prefix(Pawn ___pawn, PawnRenderTree ___renderTree, ref PawnRe // Most importantly, need to call ParallelPreRender on the renderTree to actually // set up all the matrices and whatnot that gets used when renderTree draw is called. + + // Because of the way that the stump and head renderer work, it can be necessary to force the IsHead property + // to return a certain value when it suits us. + if (NextDrawMode == DrawMode.HeadStandalone) + Patch_HediffSet_HasHead.ForcedHasHeadValue = true; + + // Force RenderTree to recalculate with the new parameters. + ___renderTree.SetDirty(); + ___renderTree.EnsureInitialized(___results.parms.flags); ___renderTree.ParallelPreDraw(___results.parms); + Patch_HediffSet_HasHead.ForcedHasHeadValue = null; + // Do run the regular RenderPawnAt method now. return true; } @@ -83,31 +106,97 @@ private static void DrawGrappleRopeIfRequired(Pawn pawn) } } - public static void MakeDrawArgs(AnimRenderer animator, Pawn pawn, ref PawnDrawParms parms) + private static void MakeDrawArgs(AnimRenderer animator, Pawn pawn, ref PawnDrawParms parms) { - // Try to get the pawn snapshot. - var part = animator.GetPawnBody(pawn); - var snapshot = animator.GetSnapshot(part); + // During certain special animations, the pawn is being animated by the regular draw is still + // used, such as the punt animation. + if (DoNotModify) + { + // However, still remove the invisible flag as it is not wanted. + parms.flags &= ~PawnRenderFlags.Invisible; + return; + } - // Dead pawns should not be animated, but make sure that it is not dead just in case. - parms.dead = false; + // Standalone heads get their own method. + if (NextDrawMode == DrawMode.HeadStandalone) + { + // It seems that pawn is not populated when the pawn is dead? + parms.pawn = pawn; + MakeDrawArgsForStandaloneHead(ref parms); + return; + } + + // Try to get the pawn body or head snapshot, depending on the current mode. + var part = NextDrawMode == DrawMode.HeadOnly + ? animator.GetPawnHead(pawn) + : animator.GetPawnBody(pawn); + var snapshot = animator.GetSnapshot(part); // Debug: - parms.tint = Color.magenta; + //parms.tint = Color.magenta; // Must be standing. - parms.posture = RimWorld.PawnPosture.Standing; + parms.posture = PawnPosture.Standing; // Remove invisible flag. parms.flags &= ~PawnRenderFlags.Invisible; // Set facing direction: - parms.facing = snapshot.GetWorldDirection(); - + var facing = NextDrawMode == DrawMode.HeadOnly + ? HeadRotation + : snapshot.GetWorldDirection(); + parms.facing = facing; + // New transform matrix calculation: Vector3 worldPos = snapshot.GetWorldPosition(); float worldAngle = snapshot.GetWorldRotation(); Vector3 scale = snapshot.LocalScale; // Just use local part as scale, not currently used anyway but might be useful in future. parms.matrix = Matrix4x4.TRS(worldPos + pawn.ageTracker.CurLifeStage.bodyDrawOffset, Quaternion.Euler(0, worldAngle, 0), scale); + + // Some additional flags depending on the rendering mode: + switch (NextDrawMode) + { + case DrawMode.BodyOnly: + // When rendering a headless body, add a head stump. + // Note: do not skip the head using a flag, because that causes the stump to not be rendered. + parms.flags |= PawnRenderFlags.HeadStump; + + // TODO known issue: head stump does not render because the render tree node checks for + // the presence of a head before drawing the stump, regardless of the flag. Fix! + + break; + + case DrawMode.HeadOnly: + // Don't render the body. + parms.skipFlags |= AM_DefOf.Body; + break; + } + } + + private static void MakeDrawArgsForStandaloneHead(ref PawnDrawParms parms) + { + parms.skipFlags |= AM_DefOf.Body; // Don't draw the body, just the head. + parms.posture = PawnPosture.Standing; // Don't use the wrong mesh set. + parms.flipHead = false; // Don't flip the head round. + //parms.dead = false; + parms.tint = Color.white; // Don't tint transparent. Idk why it gets set to (0, 0, 0, 0) upon death, but it does... + parms.rotDrawMode = RotDrawMode.Fresh; // Force the head to not rot. + StandaloneHeadPosition.y = AltitudeLayer.Building.AltitudeFor(); // Force the head to be put on the ground, should be done elsewhere, but I'm lazy. + + // Always render headgear and clothes, do not render head stump (head stump prevents head from drawing). + parms.flags |= PawnRenderFlags.Headgear | PawnRenderFlags.Clothes; + parms.flags &= ~PawnRenderFlags.HeadStump; + + // Set correct position and angle for head. + parms.facing = HeadRotation; + parms.matrix = Matrix4x4.TRS(StandaloneHeadPosition, Quaternion.Euler(0, StandaloneHeadAngle, 0), Vector3.one); + } + + public enum DrawMode + { + Full, + BodyOnly, + HeadOnly, + HeadStandalone } } diff --git a/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnInternal.cs b/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnInternal.cs deleted file mode 100644 index 7d6a72ff..00000000 --- a/Source/AnimationMod/Patches/Patch_PawnRenderer_RenderPawnInternal.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using HarmonyLib; -using RimWorld; -using UnityEngine; -using Verse; - -namespace AM.Patches; - -/// -/// Overrides various parameters of the pawn rendering, specifically direction (north, east etc.) -/// and body angle. Driven by the active animation. -/// Also used to render severed heads. -/// -//[HarmonyPatch(typeof(PawnRenderer), nameof(PawnRenderer.RenderPawnInternal))] -static class Patch_PawnRenderer_RenderPawnInternal -{ - /* - * Steps to ensure that I can render exactly how I want: - */ - - public static bool AllowNext; - public static bool DoNotModify = false; - public static DrawMode NextDrawMode = DrawMode.Full; - public static Rot4 HeadRotation; // Only used when NextDrawMode is HeadOnly or HeadStandalone. - - public static float StandaloneHeadRotation; - - public enum DrawMode - { - Full, - BodyOnly, - HeadOnly, - HeadStandalone - } - - [HarmonyPriority(Priority.Last)] // As late as possible. We want to be the last to modify results. - [HarmonyAfter("com.yayo.yayoAni")] // Go away. - [HarmonyBefore("rimworld.Nals.FacialAnimation")] // Must go before facial animation otherwise the face gets fucky. - private static bool Prefix(PawnRenderer __instance, Pawn ___pawn, ref PawnDrawParms parms) - { - //parms.matrix - - __instance.renderTree.ParallelPreDraw(parms); - - return true; - - // Do not modify when the result will be stored in cache. - if (parms.Cache) - return true; - - float angle = parms.matrix.rotation.eulerAngles.y; - float oldAngle = angle; - - bool renderBody = !parms.skipFlags.HasFlag(AM_DefOf.Body); - - ref Rot4 bodyFacing = ref parms.facing; - ref PawnRenderFlags flags = ref parms.flags; - - if (AllowNext) - { - //parms.matrix *= Matrix4x4.Scale(0.5f * Vector3.one); - //__instance.renderTree.SetDirty(); - //__instance.EnsureGraphicsInitialized(); - //parms.coveredInFoam = true; - AllowNext = false; - return true; - } - - - return false; - - bool result = ModifyRenderData(___pawn, ref bodyFacing, ref angle, ref flags, ref renderBody); - - if (renderBody) - parms.skipFlags |= AM_DefOf.Body; - else - parms.skipFlags &= ~AM_DefOf.Body; - - float delta = oldAngle - angle; - if (Math.Abs(delta) > 0.001f) - { - parms.matrix *= Matrix4x4.Rotate(Quaternion.Euler(45, delta, 0)); - } - - return result; - } - - public static bool ModifyRenderData(Pawn ___pawn, ref Rot4 bodyFacing, ref float angle, ref PawnRenderFlags flags, ref bool renderBody) - { - // Do not affect portrait rendering: - if (flags.HasFlag(PawnRenderFlags.Portrait)) - return true; - - // Standalone head (i.e. dropped head on ground after animation) gets a custom method that does things slightly differently. - if (NextDrawMode == DrawMode.HeadStandalone) - return RenderStandaloneHeadMode(ref bodyFacing, ref flags, ref angle, ref renderBody); - - // Get the animator for this pawn. - var anim = PatchMaster.GetAnimator(___pawn); - if (anim != null) - { - if (!DoNotModify) - { - //var part = NextDrawMode == DrawMode.HeadOnly ? anim.GetPawnHead(___pawn) : anim.GetPawnBody(___pawn); - //var snapshot = anim.GetSnapshot(part); - //angle = snapshot.GetWorldRotation(); - - angle = 5; - bodyFacing = Rot4.East; - renderBody = false; - - //bodyFacing = NextDrawMode == DrawMode.HeadOnly ? HeadRotation : snapshot.GetWorldDirection(); - - //switch (NextDrawMode) - //{ - // case DrawMode.BodyOnly: - // // Render head stump, do not render head gear. - // flags |= PawnRenderFlags.HeadStump; - // flags &= ~PawnRenderFlags.Headgear; - // break; - - // case DrawMode.HeadOnly: - // // Do not render body. - // renderBody = false; - // break; - //} - } - - if (!AllowNext) - return false; - } - - AllowNext = false; - return true; - } - - private static bool RenderStandaloneHeadMode(ref Rot4 bodyFacing, ref PawnRenderFlags flags, ref float angle, ref bool renderBody) - { - // Add headgear, remove head stump. - flags |= PawnRenderFlags.Headgear | PawnRenderFlags.DrawNow; - flags &= ~PawnRenderFlags.HeadStump; - - angle = StandaloneHeadRotation; - bodyFacing = HeadRotation; - renderBody = false; - return true; - } -} diff --git a/Source/AnimationMod/Patches/Patch_PawnUtility_IsInvisible.cs b/Source/AnimationMod/Patches/Patch_PawnUtility_IsInvisible.cs index a626ab15..2e0c0c27 100644 --- a/Source/AnimationMod/Patches/Patch_PawnUtility_IsInvisible.cs +++ b/Source/AnimationMod/Patches/Patch_PawnUtility_IsInvisible.cs @@ -16,6 +16,8 @@ namespace AM.Patches; [HarmonyPatch(typeof(InvisibilityUtility), nameof(InvisibilityUtility.IsPsychologicallyInvisible))] public static class Patch_PawnUtility_IsInvisible { + public static bool IsRendering; + [HarmonyPriority(Priority.First)] public static bool Prefix(Pawn pawn, ref bool __result) { @@ -23,7 +25,7 @@ public static bool Prefix(Pawn pawn, ref bool __result) return true; var anim = PatchMaster.GetAnimator(pawn); - if (anim != null) + if (anim != null && !IsRendering) { __result = true; return false; diff --git a/Source/CAI5000Patch/CAI5000Patch.csproj b/Source/CAI5000Patch/CAI5000Patch.csproj index 562a1483..b0f4a007 100644 --- a/Source/CAI5000Patch/CAI5000Patch.csproj +++ b/Source/CAI5000Patch/CAI5000Patch.csproj @@ -21,14 +21,14 @@ False all - + runtime all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/CAI5000Patch/FixCustomRenderInAnimator.cs b/Source/CAI5000Patch/FixCustomRenderInAnimator.cs index 3faeb147..766d60d8 100644 --- a/Source/CAI5000Patch/FixCustomRenderInAnimator.cs +++ b/Source/CAI5000Patch/FixCustomRenderInAnimator.cs @@ -17,10 +17,8 @@ namespace AM.CAI5000Patch; */ public static class FixCustomRenderInAnimator { - public static void PreCustomPawnRender(Pawn pawn, AnimRenderer anim) + public static void PreCustomPawnRender(Pawn pawn, AnimRenderer anim, Map map) { - var map = anim.Map; - // This is CAI code: if (Pawn_Patch.fogThings == null || Pawn_Patch.fogThings.map != map) { @@ -28,7 +26,7 @@ public static void PreCustomPawnRender(Pawn pawn, AnimRenderer anim) } } - public static void PostCustomPawnRender(Pawn pawn, AnimRenderer anim) + public static void PostCustomPawnRender(Pawn pawn, AnimRenderer anim, Map map) { // CAI always sets fogThings to null after the map has rendered each frame. // I do not know why, probably to make sure it can be GC'd once the player exits to the main menu. diff --git a/Source/CombatExtendedPatch/CombatExtendedPatch.csproj b/Source/CombatExtendedPatch/CombatExtendedPatch.csproj index d49ea916..8f592b76 100644 --- a/Source/CombatExtendedPatch/CombatExtendedPatch.csproj +++ b/Source/CombatExtendedPatch/CombatExtendedPatch.csproj @@ -16,7 +16,7 @@ - + runtime @@ -28,7 +28,7 @@ False all - + diff --git a/Source/FacialAnimationPatch/FacialAnimationPatch - Backup.csproj b/Source/FacialAnimationPatch/FacialAnimationPatch - Backup.csproj new file mode 100644 index 00000000..38dda25e --- /dev/null +++ b/Source/FacialAnimationPatch/FacialAnimationPatch - Backup.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Source/FacialAnimationPatch/FacialAnimationPatch.csproj b/Source/FacialAnimationPatch/FacialAnimationPatch.csproj new file mode 100644 index 00000000..257a6836 --- /dev/null +++ b/Source/FacialAnimationPatch/FacialAnimationPatch.csproj @@ -0,0 +1,51 @@ + + + + net48 + Library + false + false + preview + false + false + AM.FacialAnimationPatch + AM.FacialAnimationPatch + + + + + + runtime + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + False + False + all + + + + + + refs\FacialAnimation.dll + False + False + runtime + + + + + none + ..\..\Patch_FacialAnimation\$(RimworldVersion)\Assemblies\ + true + TRACE + + diff --git a/Source/FacialAnimationPatch/PatchCore.cs b/Source/FacialAnimationPatch/PatchCore.cs new file mode 100644 index 00000000..47e313be --- /dev/null +++ b/Source/FacialAnimationPatch/PatchCore.cs @@ -0,0 +1,23 @@ +using HarmonyLib; +using JetBrains.Annotations; +using Verse; + +namespace AM.FacialAnimationPatch; + +[UsedImplicitly] +[HotSwapAll] +public sealed class PatchCore : Mod +{ + public static void Log(string msg) + { + Core.Log($"[Facial Animation Patch] {msg}"); + } + + public PatchCore(ModContentPack content) : base(content) + { + var harmony = new Harmony(content.Name); + harmony.PatchAll(); + + Log("Successfully loaded Facial Animation patch."); + } +} \ No newline at end of file diff --git a/Source/FacialAnimationPatch/Patch_DrawFaceGraphicsComp_CompRenderNodes.cs b/Source/FacialAnimationPatch/Patch_DrawFaceGraphicsComp_CompRenderNodes.cs new file mode 100644 index 00000000..16c0a69a --- /dev/null +++ b/Source/FacialAnimationPatch/Patch_DrawFaceGraphicsComp_CompRenderNodes.cs @@ -0,0 +1,44 @@ +using AM.Patches; +using FacialAnimation; +using HarmonyLib; + +namespace AM.FacialAnimationPatch; + +/// +/// DrawFaceGraphicsComp.CompRenderNodes draws all of the facial animation stuff, including a replacement head. +/// The pawns are considered invisible inside animations, and facial animation will use the 'invisible' +/// shader if it detects this. +/// This patch makes it so that during the facial animation rendering, the patch to make pawns be considered invisible is disabled. +/// +[HarmonyPatch(typeof(DrawFaceGraphicsComp), nameof(DrawFaceGraphicsComp.CompRenderNodes))] +public static class Patch_DrawFaceGraphicsComp_CompRenderNodes +{ + private static void Prefix(DrawFaceGraphicsComp __instance, ref bool __state) + { + __state = false; + + // Don't bother checking if invisible pawns are not enabled. + if (!Core.Settings.AllowInvisiblePawns) + return; + + var pawn = __instance.pawn; + if (pawn == null) + return; + + var animator = pawn.TryGetAnimator(); + if (animator != null) + { + __state = true; + __instance.SetDirty(); + Patch_PawnUtility_IsInvisible.IsRendering = true; + } + } + + private static void Postfix(bool __state) + { + if (__state) + { + Patch_PawnUtility_IsInvisible.IsRendering = false; + } + } +} \ No newline at end of file diff --git a/Source/FacialAnimationPatch/refs/FacialAnimation.dll b/Source/FacialAnimationPatch/refs/FacialAnimation.dll new file mode 100644 index 00000000..76264684 Binary files /dev/null and b/Source/FacialAnimationPatch/refs/FacialAnimation.dll differ diff --git a/Source/LightsaberPatch/LightsaberPatch.csproj b/Source/LightsaberPatch/LightsaberPatch.csproj index bbf91bc2..55b10a0e 100644 --- a/Source/LightsaberPatch/LightsaberPatch.csproj +++ b/Source/LightsaberPatch/LightsaberPatch.csproj @@ -13,7 +13,7 @@ - + runtime @@ -21,7 +21,7 @@ False all - + diff --git a/Source/ModRequestAPI/ModRequestAPI.csproj b/Source/ModRequestAPI/ModRequestAPI.csproj index 60f3d390..8737ecad 100644 --- a/Source/ModRequestAPI/ModRequestAPI.csproj +++ b/Source/ModRequestAPI/ModRequestAPI.csproj @@ -11,7 +11,7 @@ - + diff --git a/Source/PerformanceOptimizerPatch/PerformanceOptimizerPatch.csproj b/Source/PerformanceOptimizerPatch/PerformanceOptimizerPatch.csproj index ba34e89c..6200b194 100644 --- a/Source/PerformanceOptimizerPatch/PerformanceOptimizerPatch.csproj +++ b/Source/PerformanceOptimizerPatch/PerformanceOptimizerPatch.csproj @@ -16,7 +16,7 @@ - + runtime @@ -29,7 +29,7 @@ all - + diff --git a/Source/TacticowlPatch/TacticowlPatch.csproj b/Source/TacticowlPatch/TacticowlPatch.csproj index 77e71ccf..b6299fed 100644 --- a/Source/TacticowlPatch/TacticowlPatch.csproj +++ b/Source/TacticowlPatch/TacticowlPatch.csproj @@ -21,7 +21,7 @@ False all - +