code:
using RimWorld;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations {
    public class CompThingAnimator : ThingComp
    {

        public override void CompTick()
        {
            //todo: If item is held by pawn, and pawn is doing thingcomp animation,
            //animate thingcomp; see CompPowerPlantWind for how thingcomps are animated
            return;
        }
    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public abstract class BaseExtendedAnimatorAnchor : IExposable
    {
        public BaseExtendedAnimatorAnchor() { }

        public virtual void ExposeData() { }
        public abstract Vector3 getDrawPos();

        public string GetUniqueLoadID()
        {
            throw new NotImplementedException();
        }
    }
}

repo_name: Nothingforfree/rimworld

license: unknown


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class ExtendedAnimatorAnchor_Thing : BaseExtendedAnimatorAnchor
    {

        private Thing thing;

        public ExtendedAnimatorAnchor_Thing() : base() { }

        public ExtendedAnimatorAnchor_Thing(Thing thing) : base()
        {
            this.thing = thing;
        }

        public override Vector3 getDrawPos()
        {
            //x and z position, regular altitude for pawns
            return new Vector3(thing.DrawPos.x, AltitudeLayer.Pawn.AltitudeFor(), thing.DrawPos.z);
        }

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_References.Look<Thing>(ref this.thing, "animations_anchor_thing", false);
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class ExtendedAnimatorAnchor_Vector3 : BaseExtendedAnimatorAnchor
    {

        public ExtendedAnimatorAnchor_Vector3() : base() { }

        private Vector3 position;
        public ExtendedAnimatorAnchor_Vector3(Vector3 position) : base()
        {
            //default to altitude for layer for y
            this.position = new Vector3(position.x, AltitudeLayer.Pawn.AltitudeFor(), position.z);
        }

        public override Vector3 getDrawPos()
        {
            return position;
        }

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Values.Look<Vector3>(ref position, "animations_anchor_position", Vector3.zero);
        }
    }
}


code:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    [DefOf]
    public static class AnimationDefOf
    {
        static AnimationDefOf()
        {
            DefOfHelper.EnsureInitializedInCtor(typeof(AnimationDefOf));
        }

        public static AnimationDef TestAnimation1;
        public static AnimationDef TestAnimation2;
    }
}


code:
using System.Collections.Generic;
using Verse;
using RimWorld;
using UnityEngine;
using System.Windows;

namespace Rimworld_Animations {
    class MainTabWindow_OffsetConfigure : MainTabWindow
    {

        public override Vector2 RequestedTabSize => new Vector2(505, 500);
        public override void DoWindowContents(Rect inRect) {

            Rect position = new Rect(inRect.x, inRect.y, inRect.width, inRect.height);

            Listing_Standard listingStandard = new Listing_Standard();
            listingStandard.Begin(position);

            listingStandard.Label("RimAnims_AnimManager".Translate());

            listingStandard.GapLine();

            if (Find.Selector.SingleSelectedThing is Pawn curPawn
                && curPawn.TryGetComp<CompExtendedAnimator>(out CompExtendedAnimator extendedAnimator)
                && extendedAnimator.IsAnimating)
            {
                //Pawn info about their body, race
                Vector3 offsetPosition = extendedAnimator.Offset != null ? (Vector3)extendedAnimator.Offset : Vector3.zero;
                int offsetRotation = extendedAnimator.Rotation != null ? (int)extendedAnimator.Rotation : 0;

                string pawnDef = curPawn.def.defName;
                string bodyTypeDef = (curPawn.story?.bodyType != null) ? curPawn.story.bodyType.ToString() : "None";
                string genderDef = curPawn.gender.ToString();
                string currentAnimation = extendedAnimator.CurrentAnimation != null ? extendedAnimator.CurrentAnimation.defName : "None";

                listingStandard.Label(curPawn.Name + ": " + curPawn.def.defName + ", " + bodyTypeDef + ", " + genderDef + ",  Animation: " + currentAnimation);

                if (curPawn.def.defName == "Human")
                {
                    listingStandard.Label("RimAnims_Warning".Translate());
                }

                float posX = offsetPosition.x, posY = offsetPosition.y, posZ = offsetPosition.z;
                int rot = offsetRotation;

                float.TryParse(listingStandard.TextEntryLabeled("X: ", posX.ToString()), out posX);
                posX = listingStandard.Slider(posX, -2, 2);

                float.TryParse(listingStandard.TextEntryLabeled("Y: ", offsetPosition.y.ToString()), out posY);
                posY = listingStandard.Slider(posY, -2, 2);

                float.TryParse(listingStandard.TextEntryLabeled("Z: ", posZ.ToString()), out posZ);
                posZ = listingStandard.Slider(posZ, -2, 2);

                int.TryParse(listingStandard.TextEntryLabeled("Rotation: ", rot.ToString()), out rot);
                rot = (int)listingStandard.Slider(rot, -180, 180);

                listingStandard.GapLine();
                Vector3 newOffsetVector = new Vector3(posX, posY, posZ);

                string offset = "<li>";
                offset += bodyTypeDef != "None" ? "<bodyType>" + bodyTypeDef + "</bodyType>" : "";
                offset += newOffsetVector != Vector3.zero ? "<offset>(" + posX + ", " + posY + ", " + posZ + ")</offset>" : "";
                offset += rot != 0 ? "<rotation>" + rot + "</rotation>" : "";
                offset += "</li>";

                listingStandard.Label("Appropriate Offset value for " + currentAnimation + ", " + pawnDef + ", " + bodyTypeDef + ", " + genderDef + ": ");
                listingStandard.Label(offset);

                if (listingStandard.ButtonText("RimAnims_CopyToClipboard".Translate()))
                {

                    GUIUtility.systemCopyBuffer = offset;

                }

                listingStandard.Label("RimAnims_ShareSettings".Translate());

                extendedAnimator.Offset = newOffsetVector;
                extendedAnimator.Rotation = rot;

            }

            else
            {
                listingStandard.Label("Select a pawn currently in an animation to change their offsets");
            }

            listingStandard.End();

        }
    }
}

/**
 if (curPawn.TryGetComp<CompExtendedAnimator> animator) {

                    /*
        CompBodyAnimator compBodyAnimator = curPawn.TryGetComp<CompBodyAnimator>();
        AnimationDef def = compBodyAnimator.CurrentAnimation;
        int ActorIndex = compBodyAnimator.ActorIndex;
        float offsetX = 0, offsetZ = 0, rotation = 0;

        string bodyTypeDef = (curPawn.story?.bodyType != null) ? curPawn.story.bodyType.ToString() : "";

        if (AnimationSettings.offsets.ContainsKey(def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex)) {
            offsetX = AnimationSettings.offsets[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex].x;
            offsetZ = AnimationSettings.offsets[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex].y;
        } else {
            AnimationSettings.offsets.Add(def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex, new Vector2(0, 0));
        }

        if (AnimationSettings.rotation.ContainsKey(def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex)) {
            rotation = AnimationSettings.rotation[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex];
        }
        else {
            AnimationSettings.rotation.Add(def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex, 0);
        }

        listingStandard.Label("Name: " + curPawn.Name + " Race: " + curPawn.def.defName + " Actor Index: " + curPawn.TryGetComp<CompBodyAnimator>().ActorIndex + " Body Type (if any): " + bodyTypeDef + " Animation: " + def.label + (curPawn.TryGetComp<CompBodyAnimator>().Mirror ? " mirrored" : ""));

        if(curPawn.def.defName == "Human") {
            listingStandard.Label("Warning--You generally don't want to change human offsets, only alien offsets");
        }

        float.TryParse(listingStandard.TextEntryLabeled("X Offset: ", offsetX.ToString()), out offsetX);
        offsetX = listingStandard.Slider(offsetX, -2, 2);

        float.TryParse(listingStandard.TextEntryLabeled("Z Offset: ", offsetZ.ToString()), out offsetZ);
        offsetZ = listingStandard.Slider(offsetZ, -2, 2);

        float.TryParse(listingStandard.TextEntryLabeled("Rotation: ", rotation.ToString()), out rotation);
        rotation = listingStandard.Slider(rotation, -180, 180);

        if(listingStandard.ButtonText("Reset All")) {
            offsetX = 0;
            offsetZ = 0;
            rotation = 0;
        }

        listingStandard.GapLine();

        if(listingStandard.ButtonText("Shift Actors")) {

            if(AnimationSettings.debugMode) {
                Log.Message("Shifting actors in animation...");
            }

            for(int i = 0; i < curPawn.TryGetComp<CompBodyAnimator>().actorsInCurrentAnimation.Count; i++) {

                Pawn actor = curPawn.TryGetComp<CompBodyAnimator>().actorsInCurrentAnimation[i];

                actor.TryGetComp<CompBodyAnimator>()?.shiftActorPositionAndRestartAnimation();

                //reset the clock time of every pawn in animation
                if(actor.jobs.curDriver is rjw.JobDriver_Sex) {
                    (actor.jobs.curDriver as rjw.JobDriver_Sex).ticks_left = def.animationTimeTicks;
                    (actor.jobs.curDriver as rjw.JobDriver_Sex).ticksLeftThisToil = def.animationTimeTicks;
                    (actor.jobs.curDriver as rjw.JobDriver_Sex).duration = def.animationTimeTicks;
                }

            }

        }

        if (offsetX != AnimationSettings.offsets[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex].x || offsetZ != AnimationSettings.offsets[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex].y) {
            AnimationSettings.offsets[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex] = new Vector2(offsetX, offsetZ);

        }

        if(rotation != AnimationSettings.rotation[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex]) {
            AnimationSettings.rotation[def.defName + curPawn.def.defName + bodyTypeDef + ActorIndex] = rotation;
        }

    }

}
                    */


code:
using RimWorld;
using Verse;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations {

    [DefOf]
    public static class OffsetMainButtonDefOf {

        public static MainButtonDef OffsetManager;

        static OffsetMainButtonDefOf() {
            DefOfHelper.EnsureInitializedInCtor(typeof(OffsetMainButtonDefOf));
        }

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using RimWorld.Planet;
using Verse;

namespace Rimworld_Animations {
    public class WorldComponent_UpdateMainTab : WorldComponent {

        public WorldComponent_UpdateMainTab(World world) : base(world) {

        }

        public override void FinalizeInit() {
            base.FinalizeInit();
            OffsetMainButtonDefOf.OffsetManager.buttonVisible = RJWAnimationSettings.offsetTab;
        }

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using HarmonyLib;
using System.Reflection;

namespace Rimworld_Animations {

    [StaticConstructorOnStartup]
    public static class Harmony_PatchAll {

        static Harmony_PatchAll() {

            Harmony val = new Harmony("rjwanim");
            val.PatchAll(Assembly.GetExecutingAssembly());

        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using rjw;
using HarmonyLib;
using Verse;
using RimWorld;
using Verse.AI;

namespace Rimworld_Animations {
    /*
    [HarmonyPatch(typeof(WorkGiver_Sex), "JobOnThing")]
    public static class HarmonyPatch_WorkGiverSex {

        public static bool Prefix(ref Job __result, ref Thing t) {

            Building_Bed bed = RestUtility.CurrentBed(t as Pawn);
            if (bed == null) {
                return false;
            }
            __result = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("JoinInBedAnimation", true), t as Pawn, bed);
            return false;

        }

    }

    */
}


code:
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using RimWorld;
using Verse;
using rjw;
using Verse.AI;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(Bed_Utility), "in_same_bed")]
    public static class HarmonyPatch_JobDriver_InSameBedPatch
    {

        public static bool Prefix(Pawn partner, ref bool __result)
        {

            if(partner != null && partner.CurJobDef == xxx.casual_sex)
            {
                __result = true;
                return false;
            }

            return true;
        }
    }

    [HarmonyPatch(typeof(JobDriver_JoinInBed), "MakeNewToils")]
    public static class HarmonyPatch_JobDriver_JoinInBed
    {

        public static void Postfix(JobDriver_JoinInBed __instance, ref IEnumerable<Toil> __result)
        {

            var toils = __result.ToList();

            Toil goToPawnInBed = Toils_Goto.GotoThing(__instance.iTarget, PathEndMode.OnCell);
            goToPawnInBed.FailOn(() => !RestUtility.InBed(__instance.Partner) && __instance.Partner.CurJobDef != xxx.gettin_loved && !Bed_Utility.in_same_bed(__instance.Partner, __instance.pawn));

            toils[1] = goToPawnInBed;

            Toil startPartnerSex = new Toil();
            startPartnerSex.initAction = delegate {

                if (!(__instance.Partner.jobs.curDriver is JobDriver_SexBaseReciever)) // allows threesomes
                {
                    Job gettinLovedJob = JobMaker.MakeJob(xxx.gettin_loved, __instance.pawn, __instance.Bed); // new gettin loved toil that wakes up the pawn goes here
                    __instance.Partner.jobs.jobQueue.EnqueueFirst(gettinLovedJob);
                    __instance.Partner.jobs.EndCurrentJob(JobCondition.InterruptForced);
                }

            };

            toils[2] = startPartnerSex;

            __result = toils.AsEnumerable();
        }
    }
}


code:
using HarmonyLib;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_Masturbate), "SetupDurationTicks")]
    public class HarmonyPatch_JobDriver_Masturbate
    {
        public static void Postfix(JobDriver_Masturbate __instance)
        {
            //prevent early stoppage of masturbate jobdriver during animation
            __instance.duration = 10000000;
            __instance.ticks_left = __instance.duration;

        }
    }
}


code:
using HarmonyLib;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(JobDriver_Sex), "setup_ticks")]
    public class HarmonyPatch_JobDriver_Sex
    {
        public static void Postfix(JobDriver_Sex __instance)
        {
            if (!RJWAnimationSettings.hearts)
            {
                __instance.ticks_between_hearts = int.MaxValue;
            }

        }
    }

    [HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
    public class HarmonyPatch_JobDriver_Sex2
    {
        public static void Postfix(JobDriver_Sex __instance, Pawn pawn)
        {
            //if neverending sex and pawn doesn't have an animation,
            if (__instance.neverendingsex
                && __instance is JobDriver_SexBaseReciever receiverJobDriver
                && !pawn.TryGetComp<CompExtendedAnimator>().IsAnimating)
            {

                //start a new animation for all the pawns paired with receiver job driver
                List<Pawn> participants = receiverJobDriver.parteners.Append(pawn).ToList();
                GroupAnimationDef animation = AnimationUtility.FindGroupAnimation(participants, out int reorder);
                if (animation != null)
                {
                    Thing anchor = (Thing)__instance.Bed ?? pawn;
                    AnimationUtility.StartGroupAnimation(participants, animation, reorder, anchor);
                }

            }

        }
    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using RimWorld;
using Verse;
using rjw;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
    static class HarmonyPatch_JobDriver_SexBaseInitiator_Start
    {
        public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
        {

            Pawn pawn = __instance.pawn;
            Pawn partner = __instance.Target as Pawn;

            List<Pawn> participants =
                (partner?.jobs?.curDriver as JobDriver_SexBaseReciever)?.parteners.Append(partner).ToList() ?? // list of all participants including partner
                ((__instance is JobDriver_Masturbate) ?  new List<Pawn>() { pawn } : null); //solo pawn for masturbation anim

            GroupAnimationDef groupAnimation = AnimationUtility.FindGroupAnimation(participants, out int reorder);

            if (groupAnimation != null)
            {
                Thing anchor = (Thing)__instance.Bed ?? partner;

                AnimationUtility.StartGroupAnimation(participants, groupAnimation, reorder, anchor);
                int animTicks = AnimationUtility.GetAnimationLength(pawn);

                foreach (Pawn participant in participants)
                {
                    if (RJWAnimationSettings.debugMode)
                    {
                        Log.Message("Participant: " + participant.Name);
                        Log.Message("JobDriver: " + participant.CurJobDef.defName);
                    }

                    //null ref check for pawns that might have lost their jobs or become null for some reason
                    if (participant?.jobs?.curDriver is JobDriver_Sex participantJobDriver)
                    {
                        participantJobDriver.ticks_left = animTicks;
                        participantJobDriver.sex_ticks = animTicks;
                        participantJobDriver.orgasmStartTick = animTicks;
                        participantJobDriver.duration = animTicks;
                    }
                }
            }

            else
            {

                //backup check for if masturbation doesn't have anim
                //reset duration and ticks_left to the regular RJW values
                //because of HarmonyPatch_JobDriver_Masturbate setting the values large to prevent early stoppage
                foreach (Pawn participant in participants)
                {
                    if (participant?.jobs?.curDriver is JobDriver_Sex participantJobDriver)
                    {
                        participantJobDriver.duration = (int)(xxx.is_frustrated(participant) ? (2500f * Rand.Range(0.2f, 0.7f)) : (2500f * Rand.Range(0.2f, 0.4f)));
                        participantJobDriver.ticks_left = participantJobDriver.duration;

                    }

                }
            }

        }

        static IEnumerable<String> NonSexActRulePackDefNames = new String[]
        {
            "MutualHandholdingRP",
            "MutualMakeoutRP",
        };

        public static bool NonSexualAct(JobDriver_SexBaseInitiator sexBaseInitiator)
        {
            if (NonSexActRulePackDefNames.Contains(sexBaseInitiator.Sexprops.rulePack))
            {
                return true;
            }
            return false;
        }
    }

    [HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
    static class HarmonyPatch_JobDriver_SexBaseInitiator_End
    {

        public static void Prefix(ref JobDriver_SexBaseInitiator __instance)
        {
            //stop pawn animating
            AnimationUtility.StopGroupAnimation(__instance.pawn);

            //stop partner animating
            if (__instance.Partner is Pawn partner)
            {
                AnimationUtility.StopGroupAnimation(partner);
            }

            //stop partner's other partners (threesome pawns) animating
            //added null ref checks for instances when pawns get nulled or lose their jobs
            if (__instance.Partner?.jobs?.curDriver is JobDriver_SexBaseReciever partnerReceiverJob)
            {
                foreach (Pawn pawn in partnerReceiverJob.parteners)
                {
                    if (pawn != null) AnimationUtility.StopGroupAnimation(pawn);
                }
            }
        }
    }
}


code:
using HarmonyLib;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_Sex), "Animate")]
    public class HarmonyPatch_Animate
    {
        public static bool Prefix(ref JobDriver_Sex __instance, ref Pawn pawn, ref Thing target)
        {
			//remove all bumping stuff in animations; keep draw nude code
			__instance.RotatePawns(pawn, __instance.Partner);
			if (target != null)
			{
				Pawn pawn2 = target as Pawn;

				if (pawn2 != null && !__instance.Sexprops.isRapist)
				{
					// if not (pawn has root node and rootnode is animating)
					if (!(pawn2?.Drawer?.renderer?.renderTree?.rootNode is PawnRenderNode rootNode
					&& (rootNode.AnimationWorker is AnimationWorker_KeyframesExtended || rootNode.children.Any(x => x.AnimationWorker is AnimationWorker_KeyframesExtended))))
                    {
						//play bumpin anim
						pawn.Drawer.Notify_MeleeAttackOn(target);
					}

				}

				if (!__instance.isEndytophile)
				{
					SexUtility.DrawNude(pawn, false);
					if (pawn2 != null)
					{
						SexUtility.DrawNude(pawn2, false);
						return false;
					}
				}
			}
			else if (!__instance.isEndytophile)
			{
				SexUtility.DrawNude(pawn, false);
			}

			return false;
        }
    }
}


code:
using HarmonyLib;
using rjw;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_Sex), "PlaySexSound")]
    class HarmonyPatch_PlaySexSounds
    {
        public static bool Prefix(JobDriver_Sex __instance)
        {
            return false;
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using Verse.AI;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
    public class HarmonyPatch_SexTick
    {
        public static bool Prefix(JobDriver_Sex __instance, Pawn pawn, Thing target)
        {

			if ((target is Pawn) &&
				!(
				(target as Pawn)?.jobs?.curDriver is JobDriver_SexBaseReciever
				&&
				((target as Pawn).jobs.curDriver as JobDriver_SexBaseReciever).parteners.Any()
				&&
				((target as Pawn).jobs.curDriver as JobDriver_SexBaseReciever).parteners[0] == pawn))
            {

				__instance.ticks_left--;
                __instance.sex_ticks--;
                __instance.Orgasm();

				if (pawn.IsHashIntervalTick(__instance.ticks_between_thrusts))
				{
					__instance.ChangePsyfocus(pawn, target);
					__instance.Animate(pawn, target);
					__instance.PlaySexSound();
					if (!__instance.Sexprops.isRape)
					{
						pawn.GainComfortFromCellIfPossible(false);
						if (target is Pawn)
						{
							(target as Pawn).GainComfortFromCellIfPossible(false);
						}
					}
					if(!__instance.isEndytophile)
                    {
						SexUtility.DrawNude(pawn, false);
                    }
				}

				return false;
            }

            return true;
        }

    }

}


code:
using HarmonyLib;
using RimWorld;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using Verse.AI;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_SexBaseRecieverLoved), "MakeSexToil")]
    public class HarmonyPatch_JobDriver_SexBaseReceiverLoved
    {
        public static void Postfix(JobDriver_SexBaseRecieverLoved __instance, ref Toil __result)
        {
            //added for sudden end of jobdriver
            __result.AddFinishAction(delegate {
                AnimationUtility.StopGroupAnimation(__instance.pawn);
            });
        }

        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> codeInstructions)
        {

            var ins = codeInstructions.ToList();
            for (int i = 0; i < ins.Count; i++)
            {
                if (i < ins.Count && ins[i].opcode == OpCodes.Call && ins[i].OperandIs(AccessTools.DeclaredMethod(typeof(Toils_LayDown), "LayDown")))
                {

                    ins[i].operand = AccessTools.DeclaredMethod(typeof(HarmonyPatch_JobDriver_SexBaseReceiverLoved), "DoNotLayDown");
                    yield return ins[i];

                }

                else
                {
                    yield return ins[i];
                }
            }

        }

        public static Toil DoNotLayDown(TargetIndex bedOrRestSpotIndex, bool hasBed, bool lookForOtherJobs, bool canSleep = true, bool gainRestAndHealth = true, PawnPosture noBedLayingPosture = PawnPosture.LayingMask, bool deathrest = false)
        {
            return new Toil();
        }
    }
}


code:
using HarmonyLib;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse.AI;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(JobDriver_SexBaseRecieverRaped), "MakeNewToils")]
    public class HarmonyPatch_JobDriver_SexBaseReceiverRaped
    {
        /*
         * Doesn't work; ienumerables are read-only, can't modify toil
         * would need to harmonypatch; stopped partner animating in sexbaseinitiator instead
         *
        public static void Postfix(JobDriver_SexBaseRecieverRaped __instance, ref IEnumerable<Toil> __result)
        {
            //added for sudden end of jobdriver
            __result.Last().AddFinishAction(delegate {
                AnimationUtility.StopGroupAnimation(__instance.pawn);
            });
        }

        */
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using UnityEngine;
using RimWorld;

namespace Rimworld_Animations {

    public class RJWAnimationSettings : ModSettings {

        public static bool orgasmQuiver, rapeShiver, soundOverride = true, hearts = true, controlGenitalRotation = false,
            PlayAnimForNonsexualActs = true;

        //probably move this setting to a different mod menu if moving rjw parts of code
        public static bool playVoices = true, playHumanlikeVoicesAsDefault = true;
        public static float floatRangeInRenderTreeMenu = 1f;

        public static bool offsetTab = false, debugMode = false;
        public static float shiverIntensity = 2f;

        public override void ExposeData() {

            base.ExposeData();
            Scribe_Values.Look(ref playVoices, "RJWAnimations_PlayVoices", true);
            Scribe_Values.Look(ref playHumanlikeVoicesAsDefault, "RJWAnimations-playHumanlikeVoicesAsDefault", true);
            Scribe_Values.Look(ref debugMode, "RJWAnimations-AnimsDebugMode", false);
            Scribe_Values.Look(ref offsetTab, "RJWAnimations-EnableOffsetTab", false);
            Scribe_Values.Look(ref controlGenitalRotation, "RJWAnimations-controlGenitalRotation", false);
            Scribe_Values.Look(ref orgasmQuiver, "RJWAnimations-orgasmQuiver");
            Scribe_Values.Look(ref rapeShiver, "RJWAnimations-rapeShiver");
            Scribe_Values.Look(ref hearts, "RJWAnimation-heartsOnLovin");
            Scribe_Values.Look(ref PlayAnimForNonsexualActs, "RJWAnims-PlayAnimForNonsexualActs");
            Scribe_Values.Look(ref soundOverride, "RJWAnimations-rjwAnimSoundOverride", true);
            Scribe_Values.Look(ref shiverIntensity, "RJWAnimations-shiverIntensity", 2f);
            Scribe_Values.Look(ref floatRangeInRenderTreeMenu, "RJWAnimations-FloatRangeRenderMenu", 1f);
            //todo: save offsetsByDefName

        }

    }

    public class RJW_Animations : Mod {

        public RJW_Animations(ModContentPack content) : base(content) {
            GetSettings<RJWAnimationSettings>();

        }

        public override void DoSettingsWindowContents(Rect inRect) {

            Listing_Standard listingStandard = new Listing_Standard();
            listingStandard.Begin(inRect);

            listingStandard.CheckboxLabeled("RimAnim_SoundOverride".Translate(), ref RJWAnimationSettings.soundOverride);
            listingStandard.CheckboxLabeled("RimAnim_GenitalRotation".Translate(), ref RJWAnimationSettings.controlGenitalRotation);
            listingStandard.CheckboxLabeled("RimAnim_OrgasmQuiver".Translate(), ref RJWAnimationSettings.orgasmQuiver);
            listingStandard.CheckboxLabeled("RimAnim_RapeShiver".Translate(), ref RJWAnimationSettings.rapeShiver);
            listingStandard.CheckboxLabeled("RimAnim_HeartsDuringLovin".Translate(), ref RJWAnimationSettings.hearts);
            listingStandard.CheckboxLabeled("RimAnim_PlayNonsexual".Translate(), ref RJWAnimationSettings.PlayAnimForNonsexualActs);
            listingStandard.CheckboxLabeled("RimAnim_AnimManagerTab".Translate(), ref RJWAnimationSettings.offsetTab);
            listingStandard.CheckboxLabeled("RimAnim_Voices".Translate(), ref RJWAnimationSettings.playVoices);
            if (RJWAnimationSettings.playVoices)
            {
                listingStandard.CheckboxLabeled("RimAnim_HumanlikeVoicesDefault".Translate(), ref RJWAnimationSettings.playHumanlikeVoicesAsDefault);
            }

            listingStandard.Label("RimAnim_ShiverIntensity".Translate() + RJWAnimationSettings.shiverIntensity);
            RJWAnimationSettings.shiverIntensity = listingStandard.Slider(RJWAnimationSettings.shiverIntensity, 0.0f, 12f);

            listingStandard.Label("RimAnim_FloatRangeRenderTree".Translate() + RJWAnimationSettings.floatRangeInRenderTreeMenu);
            RJWAnimationSettings.floatRangeInRenderTreeMenu = listingStandard.Slider(RJWAnimationSettings.floatRangeInRenderTreeMenu, 0.1f, 12f);

            listingStandard.CheckboxLabeled("RimAnim_DebugMode".Translate(), ref RJWAnimationSettings.debugMode);

            listingStandard.End();
            base.DoSettingsWindowContents(inRect);
        }

        public override void WriteSettings() {
            base.WriteSettings();
            OffsetMainButtonDefOf.OffsetManager.buttonVisible = RJWAnimationSettings.offsetTab;

        }

        public override string SettingsCategory() {
            return "RimAnim_ModSettings".Translate();
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(Dialog_DebugRenderTree), "RightRect")]
    public static class HarmonyPatch_Dialog_DebugRenderTree
    {

        static MethodInfo replaceFloatRangeMethod = SymbolExtensions.GetMethodInfo(() => HarmonyPatch_Dialog_DebugRenderTree.ReplaceFloatValueRange());

        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            var codes = new List<CodeInstruction>(instructions);

            for (int i = 0; i < codes.Count; i++)
            {
                //increase granularity of x and z sliders to be 0.01 instead
                if (codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.05f)
                {
                    codes[i].operand = 0.001f;
                    codes[i - 8].opcode = OpCodes.Call;
                    codes[i - 8].operand = replaceFloatRangeMethod;

                }

            }

            return codes.AsEnumerable();

        }

        public static FloatRange ReplaceFloatValueRange()
        {
            return new FloatRange(-RJWAnimationSettings.floatRangeInRenderTreeMenu, RJWAnimationSettings.floatRangeInRenderTreeMenu);
        }

    }
}


code:
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    // Head Rotation Code - Textures
    // it's fine to just edit each AppendRequests individually
    // because they all the parms are passed down to each child node recursively
    [HarmonyPatch(typeof(PawnRenderNode), "AppendRequests")]
    public static class HarmonyPatch_PawnRenderNode
    {
        //if rendernodetag is head, update PawnDrawParms so that head, and all children, are rotated for anim
        public static bool Prefix(ref PawnRenderNode __instance, ref PawnDrawParms parms)
        {
            if (__instance.AnimationWorker is AnimationWorker_KeyframesExtended extendedAnimWorker)
            {

                if (parms.Portrait) return true;

                // ADJUST FACING get rotated textures
                // compare the previous tick to the current tick; if the current tick rotation is different, recache
                parms.facing = extendedAnimWorker.facingAtTick(__instance.tree.AnimationTick);

                //INVIS IF ANIM CALLS FOR IT
                //replace maybe?

                //cheaper call now comparing prev tick to cur tick

                //not necessary because of new rendernodeworker hiding props now
                //nvm, keep it because you can hide head and body too, if need be
                return extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick);

            }

            return true;

        }
    }
    /*
     * no longer needed; taken care of by graphic variants
     *
    // For changing texture path of thing to variant
    [HarmonyPatch(typeof(PawnRenderNode), "TexPathFor")]
    public static class HarmonyPatch_PawnRenderNode2
    {
        public static void Postfix(ref PawnRenderNode __instance, ref string __result)
        {
            if (__instance.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)
            {
                __result += animWorker.TexPathVariantAtTick(__instance.tree.AnimationTick);
            }
        }
    }

    */
}


code:
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(PawnRenderNodeWorker), "CanDrawNow")]
    public class HarmonyPatch_PawnRenderTreeWorker
    {
        public static bool Prefix(PawnRenderNode node, ref bool __result)
        {
            //switching to this system so that head or body can be hidden separate from other nodes
            //(hide head but not addons, etc)
            //in case someone wanted to do that

            if (node.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)
            {
                if (!animWorker.visibleAtTick(node.tree.AnimationTick))
                {
                    __result = false;
                    return false;
                }

                //visible when animating
                return true;
            }

            return true;
        }

    }

}


code:
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(PawnRenderTree), "TryGetMatrix")]
    public class HarmonyPatch_PawnRenderTree
    {
        public static bool Prefix(PawnRenderTree __instance, Dictionary<PawnRenderNodeTagDef, PawnRenderNode> ___nodesByTag, PawnRenderNode node, ref PawnDrawParms parms, ref Matrix4x4 matrix, ref bool __result)
        {
            /*
             * Facing offsets fix
             */
            //find lowest parent that is animating, or nothing if not animating

            //don't do anything if portrait
            if (parms.Portrait) return true;

            PawnRenderNode animatingNode = node;
            while (animatingNode != null
                && !(animatingNode.AnimationWorker is AnimationWorker_KeyframesExtended))
            {
                animatingNode = animatingNode.parent;
            }

            //if animating parent node found,
            if (animatingNode?.AnimationWorker is AnimationWorker_KeyframesExtended animatingNodeAnimationWorker)
            {
                //change parm to facing to animate correctly
                parms.facing = animatingNodeAnimationWorker.facingAtTick(__instance.AnimationTick);
            }

            /*
             * Set Render Node to absolute position
             */
            if (node.Props is PawnRenderNodeProperties_GraphicVariants graphicVariantProp
                && graphicVariantProp.absoluteTransform)
            {
                matrix = parms.matrix;

                //absolute transform -- just use the node's transform, not its ancestors
                node.GetTransform(parms, out Vector3 offset, out Vector3 pivot, out Quaternion quaternion, out Vector3 scale);

                if (offset != Vector3.zero) matrix *= Matrix4x4.Translate(offset);
                if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(pivot);
                if (quaternion != Quaternion.identity) matrix *= Matrix4x4.Rotate(quaternion);
                if (scale != Vector3.one) matrix *= Matrix4x4.Scale(scale);
                if (pivot != Vector3.zero) matrix *= Matrix4x4.Translate(scale).inverse;

                float num = node.Worker.AltitudeFor(node, parms);
                if (num != 0f)
                {
                    matrix *= Matrix4x4.Translate(Vector3.up * num);
                }

                __result = true;
                return false;

            }

            return true;

        }

    }

    //recaching
    //done here because changing parms causes recaching anyway, so might as well do it here
    [HarmonyPatch(typeof(PawnRenderTree), "AdjustParms")]
    public class HarmonyPatch_PawnRenderTree2
    {
        public static void Prefix(PawnRenderTree __instance, ref PawnDrawParms parms)
        {

            int animationTick = __instance.AnimationTick;

            if (__instance.rootNode.AnimationWorker is AnimationWorker_KeyframesExtended rootAnimWorkerExtended)
            {
                //recache during facing turn
                if (rootAnimWorkerExtended.shouldRecache(animationTick))
                {
                    __instance.rootNode.requestRecache = true;
                    return;
                }
            }

            foreach (PawnRenderNode node in __instance.rootNode.children)
            {
                if (node.AnimationWorker is AnimationWorker_KeyframesExtended animWorkerExtended)
                {
                    //recache during flicker on/off
                    if (animWorkerExtended.shouldRecache(animationTick))
                    {
                        node.requestRecache = true;
                        return;
                    }
                }
            }
        }
    }
}


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(PawnRenderer), "BodyAngle")]
    public class HarmonyPatch_PawnRenderer
    {
        public static bool Prefix(ref Pawn ___pawn, ref float __result)
        {
            //set body angle to zero, for when downed
            if (___pawn?.Drawer?.renderer?.renderTree?.rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended)
            {
                __result = 0;
                return false;

            }

            return true;

        }
    }

    [HarmonyPatch(typeof(PawnRenderer), "GetBodyPos")]
    public class HarmonyPatch_PawnRenderer2
    {
        //patch so that pawns appear at the same altitude layer, at layer Pawn
        public static void Postfix(PawnRenderer __instance, ref Vector3 __result)
        {

            if (__instance.renderTree?.rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended
                || (__instance.renderTree?.rootNode?.children is PawnRenderNode[] childNodes && childNodes.Any(x => x.AnimationWorker is AnimationWorker_KeyframesExtended)))
            {
                __result.y = AltitudeLayer.Pawn.AltitudeFor();
            }

        }
    }

}


code:
using HarmonyLib;
using rjw;
using UnityEngine;
using Verse;

namespace Rimworld_Animations {

    [HarmonyPatch(typeof(Pawn_DrawTracker), "DrawPos", MethodType.Getter)]
    public static class HarmonyPatch_Pawn_DrawTracker {

        //switch to postfix to get pawn original height first
        public static void Postfix(ref Pawn ___pawn, ref Vector3 __result) {

            //align pos on top of partner, position, etc., based on animatoranchor
            if (___pawn.TryGetComp<CompExtendedAnimator>() is CompExtendedAnimator animator)
            {
                if (animator.IsAnchored)
                {
                    Vector3 anchor = animator.getAnchor();
                    __result.x = anchor.x;
                    __result.z = anchor.z;
                }

            }

        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using RimWorld;
using Verse;

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(Thing), "DrawAt")]
    public static class HarmonyPatch_Thing
    {
        /*
        public static bool Prefix(Thing __instance)
        {
            CompThingAnimator thingAnimator = __instance.TryGetComp<CompThingAnimator>();
            if (thingAnimator != null && thingAnimator.isAnimating)
            {
                thingAnimator.AnimateThing(__instance);
                return false;

            }

            return true;

        }

        */
    }
}


code:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeProperties_BodyTypeVariants : PawnRenderNodeProperties_GraphicVariants
    {

        public List<TexPathVariants_BodyType> bodyTypeVariantsDef;

    }

    public class TexPathVariants_BodyType
    {

        public BodyTypeDef bodyType;
        public TexPathVariantsDef texPathVariantsDef;

    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations
{
    public class PawnRenderNodeWorker_BodyTypeVariants : PawnRenderNodeWorker_GraphicVariants
    {
        //same functionality as graphicvariants worker
        //just here for readability

    }
}


code:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNode_BodyTypeVariants : PawnRenderNode_GraphicVariants
    {

        private BodyTypeDef bodyType;
        protected new PawnRenderNodeProperties_BodyTypeVariants props;

        public PawnRenderNode_BodyTypeVariants(Pawn pawn, PawnRenderNodeProperties props, PawnRenderTree tree) : base(pawn, props, tree)
        {

            this.props = (PawnRenderNodeProperties_BodyTypeVariants)props;

        }

        protected Dictionary<int, Graphic> GraphicBodyTypeVariantsFor(Pawn pawn)
        {

            if (props.bodyTypeVariantsDef == null)
            {
                Log.Error("[Anims] Error: Tried to use BodyTypeVariants node, but bodyTypeVariants weren't given");
                return null;
            }

            //for each different hediff-based texpathvariants,
            foreach (TexPathVariants_BodyType texPathVariant_BodyType in props.bodyTypeVariantsDef)
            {
                if (pawn.story?.bodyType == texPathVariant_BodyType.bodyType)
                {
                    //return that specific variant
                    bodyType = texPathVariant_BodyType.bodyType;
                    return GenerateVariants(pawn, texPathVariant_BodyType.texPathVariantsDef);

                }

            }

            return null;

        }

        protected override void EnsureMaterialsInitialized()
        {
            if (variants == null
                || this.tree.pawn.story?.bodyType != bodyType)
            variants = GraphicBodyTypeVariantsFor(this.tree.pawn);

            //call this in case variants wasn't set, and there is no graphic bodytype variants appropriate; it'll set variants based on default
            base.EnsureMaterialsInitialized();
        }

    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeProperties_GraphicHediffSeverityVariants : PawnRenderNodeProperties_GraphicVariants
    {

        public BodyPartDef bodyPart = null;
        public List<HediffWithSeverity> hediffSeverityVariants;

    }

    public class HediffWithSeverity
    {
        public HediffDef hediff;
        public List<TexPathVariants_Severity> severityVariants;
    }

    public class TexPathVariants_Severity
    {
        public int severity;
        public TexPathVariantsDef texPathVariantsDef;

    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations
{
    public class PawnRenderNodeWorker_GraphicHediffSeverityVariants : PawnRenderNodeWorker_GraphicVariants
    {
        //same functionality as graphicvariants worker
        //just here for readability

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNode_GraphicHediffSeverityVariants : PawnRenderNode_GraphicVariants
    {

        protected HediffDef hediffWithSeverity;
        protected float curSeverity;

        protected new PawnRenderNodeProperties_GraphicHediffSeverityVariants props;
        private HediffDef curHediff;

        public PawnRenderNode_GraphicHediffSeverityVariants(Pawn pawn, PawnRenderNodeProperties props, PawnRenderTree tree) : base(pawn, props, tree)
        {

            this.props = (PawnRenderNodeProperties_GraphicHediffSeverityVariants)props;

        }

        protected Dictionary<int, Graphic> GraphicHediffSeverityVariantsFor(Pawn pawn)
        {

            if (props.hediffSeverityVariants == null)
            {
                Log.Error("[Anims] Error: Tried to use GraphicBodyPartHediffSeverityVariants node, but hediffSeverityVariants weren't given");
            }

            Hediff idealHediff = null;
            HediffWithSeverity idealHediffSeverity = null;

            if (props.bodyPart == null)
            {
                //search the entire body for the hediff because no bodypart was set
                for (int i = 0; i < props.hediffSeverityVariants.Count; i++)
                {
                    idealHediff = pawn.health.hediffSet.hediffs.Find((Hediff hediffWithSeverity) =>
                        hediffWithSeverity.def == props.hediffSeverityVariants[i].hediff);

                    if (idealHediff != null)
                    {
                        //get the ideal hediff severity variants, to iterate through and find the right one for the severity
                        idealHediffSeverity = props.hediffSeverityVariants[i];
                        break;

                    }

                }
            }

            else
            {
                //search for a hediff with a specific body part

                for (int i = 0; i < props.hediffSeverityVariants.Count; i++)
                {
                    //right hediff with the right hediff and right body part

                    idealHediff = pawn.health.hediffSet.hediffs.Find((Hediff hediffWithSeverity) =>
                        hediffWithSeverity.def == props.hediffSeverityVariants[i].hediff
                        && hediffWithSeverity.Part.def == props.bodyPart);

                    if (idealHediff != null) {

                        //get the ideal hediff severity variants, to iterate through and find the right one for the severity
                        idealHediffSeverity = props.hediffSeverityVariants[i];
                        break;
                    }
                }
            }

            if (idealHediff != null)
            {
                //set severity so that recache when this is different
                curSeverity = idealHediff.Severity;

                //look for the right severity-based texpathvariants
                TexPathVariants_Severity texPathVariants_Severity = idealHediffSeverity.severityVariants.Find((TexPathVariants_Severity texPathVariants) =>
                    texPathVariants.severity < idealHediff.Severity);

                //if null, assume value is really too small
                if (texPathVariants_Severity == null)
                {
                    //return largest value
                    return GenerateVariants(pawn, idealHediffSeverity.severityVariants.First().texPathVariantsDef);
                }

                //return right severity variants
                return GenerateVariants(pawn, texPathVariants_Severity.texPathVariantsDef);

            }

            //there is no graphic hediff variants appropriate
            curHediff = null;
            return null;

        }

        protected override void EnsureMaterialsInitialized()
        {
            //if pawn no longer has the hediff,
            if (variants == null ||
                !(this.tree.pawn.health?.hediffSet?.hediffs is List<Hediff> hediffs
                && hediffs.Any((Hediff hediff) => hediff.def == curHediff && hediff.Severity == curSeverity)))
            {
                //do graphicvariantsfor
                variants = GraphicHediffSeverityVariantsFor(this.tree.pawn);
            }

            //call this in case variants wasn't set, and there is no graphic hediff variants appropriate; it'll set variants based on default
            base.EnsureMaterialsInitialized();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeProperties_GraphicHediffVariants : PawnRenderNodeProperties_GraphicVariants
    {

        public List<TexPathVariants_Hediff> hediffVariants;

    }

    public class TexPathVariants_Hediff
    {

        public List<HediffDef> hediffs;
        public TexPathVariantsDef texPathVariantsDef;

    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations
{
    public class PawnRenderNodeWorker_GraphicHediffVariants : PawnRenderNodeWorker_GraphicVariants
    {
        //same functionality as graphicvariants worker
        //just here for readability

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNode_GraphicHediffVariants : PawnRenderNode_GraphicVariants
    {

        protected new PawnRenderNodeProperties_GraphicHediffVariants props;
        private HediffDef curHediff;

        public PawnRenderNode_GraphicHediffVariants(Pawn pawn, PawnRenderNodeProperties props, PawnRenderTree tree) : base(pawn, props, tree)
        {

            this.props = (PawnRenderNodeProperties_GraphicHediffVariants)props;

        }

        protected Dictionary<int, Graphic> GraphicHediffVariantsFor(Pawn pawn)
        {

            if (props.hediffVariants == null)
            {
                Log.Error("[Anims] Error: Tried to use GraphicHediffVariants node, but hediffVariants weren't given");
                return null;
            }

            //for each different hediff-based texpathvariants,
            foreach (TexPathVariants_Hediff texPathVariant_Hediff in props.hediffVariants)
            {
                //for all the hediffs corresponding to that texpathvariant,
                foreach (HediffDef hediffDef in texPathVariant_Hediff.hediffs)
                {
                    //if the pawn has that hediff,
                    if (pawn?.health?.hediffSet?.hediffs is List<Hediff> pawnHediffs && pawnHediffs.Any((Hediff hediff) => hediff.def == hediffDef))
                    {
                        //return that specific variant
                        curHediff = hediffDef;
                        return GenerateVariants(pawn, texPathVariant_Hediff.texPathVariantsDef);

                    }
                }
            }

            //there is no graphic hediff variants appropriate
            curHediff = null;
            return null;

        }

        protected override void EnsureMaterialsInitialized()
        {
            //if pawn no longer has the hediff,
            if (variants == null ||
                !(this.tree.pawn.health?.hediffSet?.hediffs is List<Hediff> hediffs
                && hediffs.Any((Hediff hediff) => hediff.def == curHediff)))
            {
                //do graphicvariantsfor
                variants = GraphicHediffVariantsFor(this.tree.pawn);
            }
            //call this in case variants wasn't set, and there is no graphic hediff variants appropriate; it'll set variants based on default
            base.EnsureMaterialsInitialized();
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeProperties_GraphicVariants : PawnRenderNodeProperties
    {

        public AnimationOffsetDef propOffsetDef = null;
        public TexPathVariantsDef texPathVariantsDef = null;
        public bool absoluteTransform = false;

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeWorker_GraphicVariants : PawnRenderNodeWorker
    {

        public override bool CanDrawNow(PawnRenderNode node, PawnDrawParms parms)
        {
            if (!base.CanDrawNow(node, parms)) return false;

            if (parms.Portrait) return false;

            //don't draw if not visible at tick
            if (node.AnimationWorker is AnimationWorker_KeyframesExtended extendedAnimator)
            {
                return extendedAnimator.visibleAtTick(node.tree.AnimationTick);
            }

            //don't draw at all if not animating
            return false;
        }
        protected override Material GetMaterial(PawnRenderNode node, PawnDrawParms parms)
        {

            //if node is animating, and is a graphic variant type of node
            //and node is one with graphic variants
            //and texpathvariant is set
            if ((node.AnimationWorker is AnimationWorker_KeyframesExtended extendedAnimWorker)
                && (node is PawnRenderNode_GraphicVariants nodeWithGraphicVariants)
                && extendedAnimWorker.TexPathVariantAtTick(node.tree.AnimationTick) != null)
            {
                Material materialVariant = GetMaterialVariant(nodeWithGraphicVariants, parms, (int)extendedAnimWorker.TexPathVariantAtTick(node.tree.AnimationTick));

                if (materialVariant != null) {
                    return materialVariant;
                }

            }

            //otherwise return original texture
            return base.GetMaterial(node, parms);
        }

        public virtual Material GetMaterialVariant(PawnRenderNode_GraphicVariants node, PawnDrawParms parms, int variant)
        {
            Material material = node.getGraphicVariant(variant)?.NodeGetMat(parms);

            if (material == null) return null;

            if (!parms.Portrait && parms.flags.FlagSet(PawnRenderFlags.Invisible))
            {
                material = InvisibilityMatPool.GetInvisibleMat(material);
            }

            return material;

        }

        public override Vector3 OffsetFor(PawnRenderNode node, PawnDrawParms parms, out Vector3 pivot)
        {
            Vector3 regularOffsets = base.OffsetFor(node, parms, out pivot);

            if ((node.Props as PawnRenderNodeProperties_GraphicVariants)?.propOffsetDef?.offsets is List<BaseAnimationOffset> offsets)
            {
                foreach (BaseAnimationOffset offset in offsets)
                {

                    if (offset.appliesToPawn(node.tree.pawn))
                    {

                        //modify offset of prop for animationOffset position
                        regularOffsets += offset.getOffset(node.tree.pawn) ?? Vector3.zero;
                        return regularOffsets;

                    }
                }
            }

            //unmodified; no offsets found
            return regularOffsets;
        }

        public override Vector3 ScaleFor(PawnRenderNode node, PawnDrawParms parms)
        {
            Vector3 regularScale = base.ScaleFor(node, parms);

            if ((node.Props as PawnRenderNodeProperties_GraphicVariants)?.propOffsetDef?.offsets is List<BaseAnimationOffset> offsets)
            {
                foreach (BaseAnimationOffset offset in offsets)
                {

                    if (offset.appliesToPawn(node.tree.pawn))
                    {

                        //modify scale of prop for animationOffset position
                        regularScale = regularScale.MultipliedBy(offset.getScale(node.tree.pawn) ?? Vector3.one);
                        return regularScale;

                    }
                }
            }

            return regularScale;
        }

        public override Quaternion RotationFor(PawnRenderNode node, PawnDrawParms parms)
        {
            Quaternion rotation = base.RotationFor(node, parms);

            if ((node.Props as PawnRenderNodeProperties_GraphicVariants)?.propOffsetDef?.offsets is List<BaseAnimationOffset> offsets)
            {
                foreach (BaseAnimationOffset offset in offsets)
                {
                    if (offset.appliesToPawn(node.tree.pawn))
                    {
                        //modify offset of prop for animationOffset rotation
                        rotation *= Quaternion.AngleAxis(offset.getRotation(node.tree.pawn) ?? 0, Vector3.up);
                        return rotation;

                    }
                }
            }

            //unmodified; no rotation offsets found
            return rotation;

        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNode_GraphicVariants : PawnRenderNode
    {

        protected new PawnRenderNodeProperties_GraphicVariants props;
        protected Graphic missingTextureGraphic;
        protected Dictionary<int, Graphic> variants;

        public Graphic getGraphicVariant(int variant)
        {
            if (variants == null || !variants.ContainsKey(variant))
            {
                return missingTextureGraphic;
            }

            return variants[variant];
        }

        public PawnRenderNode_GraphicVariants(Pawn pawn, PawnRenderNodeProperties props, PawnRenderTree tree) : base(pawn, props, tree) {

            this.props = (PawnRenderNodeProperties_GraphicVariants)props;

        }

        protected virtual Dictionary<int, Graphic> GraphicVariantsFor(Pawn pawn)
        {

            if (props.texPathVariantsDef == null)
            {
                return null;
            }

            return GenerateVariants(pawn, props.texPathVariantsDef);

        }

        protected override void EnsureMaterialsInitialized()
        {

            if (variants == null)
            {
                variants = GraphicVariantsFor(this.tree.pawn);
            }
            if (missingTextureGraphic == null)
            {
                missingTextureGraphic = GenerateMissingTextureGraphic();
            }

            base.EnsureMaterialsInitialized();
        }

        //used by all, including base classes, to create texPathVariants for pawn
        protected Dictionary<int, Graphic> GenerateVariants(Pawn pawn, TexPathVariantsDef texPathVariants)
        {

            Dictionary<int, Graphic> variantGraphics = new Dictionary<int, Graphic>();

            //for each graphic variant
            for (int i = 0; i < texPathVariants.variants.Count; i++)
            {

                //get new graphic
                Graphic variant = GraphicDatabase.Get<Graphic_Multi>(texPathVariants.variants[i], this.ShaderFor(pawn), Vector2.one, this.ColorFor(pawn));

                //add it to the variants dictionary; i + 1 for easier readability in logs
                variantGraphics.Add(i + 1, variant);

            }

            return variantGraphics;

        }

        protected Graphic GenerateMissingTextureGraphic()
        {
            return GraphicDatabase.Get<Graphic_Multi>("AnimationProps/MissingTexture/MissingTexture");
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class TexPathVariantsDef : Def
    {

        public List<string> variants;

    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderSubWorker_ChangeOffset : PawnRenderSubWorker
    {

        public override void TransformOffset(PawnRenderNode node, PawnDrawParms parms, ref Vector3 offset, ref Vector3 pivot)
        {
            if (parms.Portrait)
                return;

            if (node.AnimationWorker is AnimationWorker_KeyframesExtended
                && node.tree.pawn.TryGetComp<CompExtendedAnimator>(out CompExtendedAnimator extendedAnimator))
            {
                Vector3? pawnOffset = extendedAnimator.Offset;
                if (pawnOffset != null)
                {
                    offset += (Vector3)pawnOffset;
                }

            }
        }

        public override void TransformRotation(PawnRenderNode node, PawnDrawParms parms, ref Quaternion rotation)
        {
            if (node.AnimationWorker is AnimationWorker_KeyframesExtended
                && node.tree.pawn.TryGetComp<CompExtendedAnimator>(out CompExtendedAnimator extendedAnimator))
            {
                int? pawnRotation = extendedAnimator.Rotation;
                if (pawnRotation != null)
                {
                    Quaternion additionalRotation = Quaternion.AngleAxis((int)pawnRotation, Vector3.up);
                    rotation *= additionalRotation;
                }

            }

            base.TransformRotation(node, parms, ref rotation);
        }

    }
}


code:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderSubWorker_HideWhenAnimating : PawnRenderSubWorker
    {

        public override void EditMaterial(PawnRenderNode node, PawnDrawParms parms, ref Material material)
        {
            if (node.tree.pawn.def != ThingDefOf.Human) return;

            if (node.tree.rootNode.AnimationWorker is AnimationWorker_KeyframesExtended
                || node.tree.rootNode.children.Any(x => x.AnimationWorker is AnimationWorker_KeyframesExtended))
            {
                material.color = Color.clear;
                material.shader = ShaderTypeDefOf.Transparent.Shader;
            }
        }

        public override void TransformLayer(PawnRenderNode node, PawnDrawParms parms, ref float layer)
        {

            base.TransformLayer(node, parms, ref layer);

            if (node.tree.pawn.def != ThingDefOf.Human) return;
            layer -= 1000;
        }

    }

}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using rjw.Modules.Interactions.Helpers;
using rjw.Modules.Interactions.Objects;
using UnityEngine;
using Verse;
using Verse.AI;
using rjw.Modules.Interactions.Enums;

namespace Rimworld_Animations {
    public static class AnimationUtility {

        public static void StartAnimation(List<Pawn> participants)
        {
            participants[0].Drawer.renderer.SetAnimation(AnimationDefOf.TestAnimation1);
            participants[1].Drawer.renderer.SetAnimation(AnimationDefOf.TestAnimation2);
        }

        //startgroupanimator with anchor
        //don't anchor to self if anchor is self
        public static void StartGroupAnimation(List<Pawn> participants, GroupAnimationDef groupAnimationDef, int reorder, Thing anchor)
        {

            int seed = GenTicks.TicksGame;

            for (int i = 0; i < participants.Count; i++)
            {

                groupAnimationDef.GetOffset(i, participants[i], out Vector3? position, out int? rotation, reorder);

                if (anchor is Pawn pawn && pawn == participants[i])
                {

                    List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
                    participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn, position, rotation);
                }
                else
                {
                    //each participant gets their own unique extendedanimatoranchor, important for scribe_deep saving
                    List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
                    BaseExtendedAnimatorAnchor animatorAnchor = new ExtendedAnimatorAnchor_Thing(anchor);

                    if (RJWAnimationSettings.debugMode)
                    {
                        Log.Message("Now playing animation: " + groupAnimationDef.defName + " Actor Shift: " + reorder);
                    }

                    participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn, position, rotation, animatorAnchor);
                }
            }
        }

        //startgroupanimation without anchor; just play where standing
        public static void StartGroupAnimation(List<Pawn> participants, GroupAnimationDef groupAnimationDef, int reorder)
        {
            int seed = GenTicks.TicksGame;

            for (int i = 0; i < participants.Count; i++)
            {
                List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
                groupAnimationDef.GetOffset(i, participants[i], out Vector3? position, out int? rotation, reorder);
                participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn, position, rotation);
            }
        }

        public static void StopGroupAnimation(List<Pawn> participants)
        {
            foreach(Pawn pawn in participants)
            {
                pawn.TryGetComp<CompExtendedAnimator>()?.StopAnimating();
            }
        }

        public static void StopGroupAnimation(Pawn participant)
        {
            participant.TryGetComp<CompExtendedAnimator>()?.StopAnimating();
        }

        public static GroupAnimationDef FindGroupAnimation(List<Pawn> participants, out int reorder)
        {
            // go through each context in each GroupAnimationDef
            // if you find one where it returns canAnimationBeUsed (and reorders),
            // return that animation

            //find all, reorder randomly, then find max priority context
            DefDatabase<GroupAnimationDef>.AllDefsListForReading
                .FindAll((GroupAnimationDef x) => x.canAnimationBeUsed(participants))
                .OrderBy(_ => Rand.Int)
                .TryMaxBy((GroupAnimationDef x) => x.Priority(participants), out GroupAnimationDef result);

            reorder = result != null ? result.Reorder(participants) : 0;
            return result;

        }

        public static int GetAnimationLength(Pawn pawn)
        {
            return pawn.TryGetComp<CompExtendedAnimator>().AnimationLength;
        }

    }
}


code:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class VoiceDef : Def
    {

        public List<ThingDef> races = new List<ThingDef>();
        public Gender gender = Gender.None;
        public List<TraitDef> traits = new List<TraitDef>();
        public bool takesPriority = false;
        public float randomChanceFactor = 1;

        public Dictionary<VoiceTagDef, SoundDef> sounds = new Dictionary<VoiceTagDef, SoundDef>();

        public bool VoiceFitsPawn(Pawn pawn)
        {

            //doesn't match any of the races
            if (!races.Exists(x => x == pawn.def)) return false;

            //doesn't match gender
            if (gender != Gender.None && pawn.gender != gender) return false;

            //if traits list is not empty, and pawn doesn't have any of the designated traits, doesn't match
            if (!traits.Empty() && !traits.Any(trait => pawn.story.traits.HasTrait(trait))) return false;

            return true;

        }

    }
}


code:
using RimWorld;
using rjw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations
{
    [DefOf]
    public static class VoiceDefOf
    {
        static VoiceDefOf()
        {
            DefOfHelper.EnsureInitializedInCtor(typeof(VoiceDefOf));
        }

        public static VoiceDef Voice_HumanMale;
        public static VoiceDef Voice_HumanFemale;
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class VoiceTagDef : Def
    {

        public float probability = 1;

    }
}


code:
<?xml version="1.0" encoding="utf-8"?>

<ModMetaData>
	<name>Rimworld Animations 2.0</name>
	<author>C0ffee</author>
  <url>https://gitgud.io/c0ffeeeeeeee/rimworld-animations</url>
	<supportedVersions>

1.5

	</supportedVersions>
	<packageId>c0ffee.rimworld.animations</packageId>
	<modVersion>2.0.1</modVersion>
	<modDependencies>

			<packageId>brrainz.harmony</packageId>
			<displayName>Harmony</displayName>
			<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
			<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>

			<packageId>UnlimitedHugs.HugsLib</packageId>
			<displayName>HugsLib</displayName>
			<downloadUrl>https://github.com/UnlimitedHugs/RimworldHugsLib/releases/latest</downloadUrl>
			<steamWorkshopUrl>steam://url/CommunityFilePage/818773962</steamWorkshopUrl>

			<packageId>rim.job.world</packageId>
			<displayName>RimJobWorld</displayName>
			<downloadUrl>https://www.loverslab.com/topic/110270-mod-rimjobworld/</downloadUrl>

	</modDependencies>
	<loadAfter>

UnlimitedHugs.HugsLib

brrainz.harmony

erdelf.humanoidalienraces

nals.facialanimation

com.yayo.yayoAni

	</loadAfter>
	<loadBefore>
	</loadBefore>
	<incompatibleWith>
	</incompatibleWith>
	<description>
    Rimworld Animations 2.0! Hurray!

    Questions or bugs?
    Chat with me on the forums: https://www.loverslab.com/topic/140386-rjw-animations/
    Or on the rjw discord: https://discord.gg/CXwHhv8

  </description>
</ModMetaData>


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
  <identifier>Rimworld-Animations</identifier>
  <version>2.0.1</version>
</Manifest>


code:
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>

	<!-- Mod menu -->
	<RimAnim_ModSettings>RJW Animation Settings</RimAnim_ModSettings>
	<RimAnim_SoundOverride>启用声音覆盖</RimAnim_SoundOverride>
	<RimAnim_GenitalRotation>控制生殖器旋转</RimAnim_GenitalRotation>
	<RimAnim_OrgasmQuiver>启用高潮颤抖</RimAnim_OrgasmQuiver>
	<RimAnim_RapeShiver>启用强奸颤动</RimAnim_RapeShiver>
	<RimAnim_HeartsDuringLovin>性爱时启用心形</RimAnim_HeartsDuringLovin>
	<RimAnim_PlayNonsexual>播放非性行为的动画 (牵手、亲热)</RimAnim_PlayNonsexual>
	<RimAnim_AnimManagerTab>启用动画管理器选项卡</RimAnim_AnimManagerTab>
	<RimAnim_ShiverIntensity>颤抖/颤动强度 (默认为 2): </RimAnim_ShiverIntensity>
	<RimAnim_Voices>在动画期间播放语音</RimAnim_Voices>
	<RimAnim_HumanlikeVoicesDefault>如果没有找到外星种族的声音，则默认播放人类的声音</RimAnim_HumanlikeVoicesDefault>
	<RimAnim_DebugMode>调试模式</RimAnim_DebugMode>
	<RimAnim_FloatRangeRenderTree>调整渲染树偏移表单的浮动范围:</RimAnim_FloatRangeRenderTree>

	<!-- Main Tab Window -->
	<RimAnims_AnimManager>Animation Manager</RimAnims_AnimManager>
	<RimAnims_Warning>警告--一般情况下，你不想更改人类偏移值，只想更改外星种族偏移值或动物偏移值</RimAnims_Warning>
	<RimAnims_CopyToClipboard>将偏移值复制到剪贴板</RimAnims_CopyToClipboard>
	<RimAnims_ShareSettings>将偏移值粘贴到 OffsetDef 中，或在 Discord 中共享</RimAnims_ShareSettings>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>

	<!-- Mod menu -->
	<RimAnim_ModSettings>RJW Animation Settings</RimAnim_ModSettings>
	<RimAnim_SoundOverride>啟用聲音覆蓋</RimAnim_SoundOverride>
	<RimAnim_GenitalRotation>控制生殖器旋轉</RimAnim_GenitalRotation>
	<RimAnim_OrgasmQuiver>啟用高潮顫抖</RimAnim_OrgasmQuiver>
	<RimAnim_RapeShiver>啟用強姦顫動</RimAnim_RapeShiver>
	<RimAnim_HeartsDuringLovin>性愛時啟用心形</RimAnim_HeartsDuringLovin>
	<RimAnim_PlayNonsexual>播放非性行為的動畫 (牽手、親熱)</RimAnim_PlayNonsexual>
	<RimAnim_AnimManagerTab>啟用動畫管理器選項卡</RimAnim_AnimManagerTab>
	<RimAnim_ShiverIntensity>顫抖/顫動強度 (預設為 2): </RimAnim_ShiverIntensity>
	<RimAnim_Voices>在動畫期間播放語音</RimAnim_Voices>
	<RimAnim_HumanlikeVoicesDefault>如果沒有找到外星種族的聲音，則默認播放人類的聲音</RimAnim_HumanlikeVoicesDefault>
	<RimAnim_DebugMode>除錯模式</RimAnim_DebugMode>
	<RimAnim_FloatRangeRenderTree>調整渲染樹偏移表單的浮動範圍:</RimAnim_FloatRangeRenderTree>

	<!-- Main Tab Window -->
	<RimAnims_AnimManager>Animation Manager</RimAnims_AnimManager>
	<RimAnims_Warning>警告--一般情況下，你不想更改人類偏移值，只想更改外星種族偏移值或動物偏移值</RimAnims_Warning>
	<RimAnims_CopyToClipboard>將偏移值複製到剪貼板</RimAnims_CopyToClipboard>
	<RimAnims_ShareSettings>將偏移值黏貼到 OffsetDef 中，或在 Discord 中共享</RimAnims_ShareSettings>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>

	<!-- Mod menu -->
	<RimAnim_ModSettings>RJW Animation Settings</RimAnim_ModSettings>
	<RimAnim_SoundOverride>Enable Sound Override</RimAnim_SoundOverride>
	<RimAnim_GenitalRotation>Control Genital Rotation</RimAnim_GenitalRotation>
	<RimAnim_OrgasmQuiver>Enable Orgasm Quiver</RimAnim_OrgasmQuiver>
	<RimAnim_RapeShiver>Enable Rape Shiver</RimAnim_RapeShiver>
	<RimAnim_HeartsDuringLovin>Enable hearts during lovin'</RimAnim_HeartsDuringLovin>
	<RimAnim_PlayNonsexual>Play animation for nonsexual acts (handholding, makeout)</RimAnim_PlayNonsexual>
	<RimAnim_AnimManagerTab>Enable Animation Manager Tab</RimAnim_AnimManagerTab>
	<RimAnim_ShiverIntensity>Shiver/Quiver Intensity (default 2): </RimAnim_ShiverIntensity>
	<RimAnim_Voices>Play voices during animations</RimAnim_Voices>
	<RimAnim_HumanlikeVoicesDefault>Play human voices by default, when none found for alien race</RimAnim_HumanlikeVoicesDefault>
	<RimAnim_DebugMode>Debug Mode</RimAnim_DebugMode>
	<RimAnim_FloatRangeRenderTree>Float range for Debug Render Tree offset menu: </RimAnim_FloatRangeRenderTree>

	<!-- Main Tab Window -->
	<RimAnims_AnimManager>Animation Manager</RimAnims_AnimManager>
	<RimAnims_Warning>Warning--You generally don't want to change human offsets, only alien offsets or animals</RimAnims_Warning>
	<RimAnims_CopyToClipboard>Copy Offset to Clipboard</RimAnims_CopyToClipboard>
	<RimAnims_ShareSettings>Paste offset values in OffsetDef, or share in Discord</RimAnims_ShareSettings>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>

  <OffsetManager.label>gerenciador de posições</OffsetManager.label>
  <OffsetManager.description>Controla as posições sexuais dos colonos.</OffsetManager.description>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>

  <Dog_Doggystyle.label>estilo cachorrinho</Dog_Doggystyle.label>
  <Horse_Cowgirl.label>vaqueira</Horse_Cowgirl.label>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>

  <Tribadism.label>tribadismo</Tribadism.label>
  <Cunnilingus.label>cunilíngua</Cunnilingus.label>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>

  <Double_Penetration.label>dupla penetração</Double_Penetration.label>

</LanguageData>


code:
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>

  <Doggystyle.label>estilo cachorrinho</Doggystyle.label>
  <Blowjob.label>boquete</Blowjob.label>
  <ReverseStandAndCarry.label>69</ReverseStandAndCarry.label>
  <Cowgirl.label>vaqueira</Cowgirl.label>

</LanguageData>


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<loadFolders>
	<v1.1>

/

1.1

	</v1.1>
	<v1.2>

/

1.2

Patch_HatsDisplaySelection/1.2

	</v1.2>
	<v1.3>

/

1.3

Patch_HatsDisplaySelection/1.2

Patch_SexToysMasturbation

Patch_SexToysMasturbation/1.3

	</v1.3>
	<v1.4>

/

1.4

Patch_HumanoidAlienRaces/1.4

Patch_HatsDisplaySelection/1.2

Patch_SexToysMasturbation

Patch_SexToysMasturbation/1.4

	</v1.4>
	<v1.5>

/

1.5

Patch_HumanoidAlienRaces/1.5

Patch_SexToysMasturbation/1.5

Patch_FacialAnimation/1.5

Patch_NoRimNudeWorld/1.5

Patch_NoRimNudeWorldZoo/1.5

	</v1.5>

</loadFolders>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{11DC70AF-FF23-4D4D-A4E5-6453664B1A12}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Milkable_Colonists_Animations</RootNamespace>
    <AssemblyName>Milkable-Colonists-Animations</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="0Harmony">
      <HintPath>..\..\..\..\..\workshop\content\294100\839005762\1.5\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\rjw\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
    <Reference Include="UnityEngine">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Source\Patches\Harmony_PatchAll.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


code:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Milkable-Colonists-Animations")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Milkable-Colonists-Animations")]
[assembly: AssemblyCopyright("Copyright ©  2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("11dc70af-ff23-4d4d-a4e5-6453664b1a12")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using HarmonyLib;
using System.Reflection;

namespace MC_Animations {

    [StaticConstructorOnStartup]
    public static class Harmony_PatchAll {

        static Harmony_PatchAll() {

            Harmony val = new Harmony("mcanim");
            val.PatchAll(Assembly.GetExecutingAssembly());

        }
    }
}


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>

	<Operation Class="PatchOperationSequence">
		<success>Always</success>
		<operations>

				<xpath>/Defs/PawnRenderTreeDef[defName="Humanlike"]/root/children/li[debugLabel="Head"]/subworkerClasses</xpath>
				<success>Always</success>
				<nomatch Class="PatchOperationAdd">
					<xpath>/Defs/PawnRenderTreeDef[defName="Humanlike"]/root/children/li[debugLabel="Head"]</xpath>
					<value>
						<subworkerClasses />
					</value>
				</nomatch>

				<xpath>/Defs/PawnRenderTreeDef[defName="Humanlike"]/root/children/li[debugLabel="Head"]/subworkerClasses</xpath>
				<value>

Rimworld_Animations.PawnRenderSubWorker_HideWhenAnimating

				</value>

		</operations>
	</Operation>

</Patch>


code:
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
using AlienRace;

namespace Rimworld_Animations {

	[StaticConstructorOnStartup]
	public static class HarmonyPatch_AlienRace
	{
		static HarmonyPatch_AlienRace()
		{
			(new Harmony("rjwanim")).Patch(AccessTools.Method(AccessTools.TypeByName("AlienRace.HarmonyPatches"), "DrawAddons"),
							prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), "Prefix_AnimateHeadAddons")));
		}

		/* 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<CompBodyAnimator>();

			if (animator == null || !animator.isAnimating)
            {
				return;
            }

			if(addon.alignWithHead || addon.drawnInBed)
            {
				rot = animator.headFacing;
				angle = animator.headAngle;
				offsetVector += animator.deltaPos + animator.bodyAngle * animator.headBob;

			}
			else
            {
				rot = animator.bodyFacing;
				angle = animator.bodyAngle;
				offsetVector += animator.deltaPos;
            }

        }
		*/
		public static bool Prefix_AnimateHeadAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation)
		{

			if (renderFlags.FlagSet(PawnRenderFlags.Portrait) || !CompBodyAnimator.IsAnimating(pawn)) return true;
			if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible)) return false;

			List<AlienPartGenerator.BodyAddon> addons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons;
			AlienPartGenerator.AlienComp comp = pawn.GetComp<AlienPartGenerator.AlienComp>();
			CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();

			bool flag = renderFlags.FlagSet(PawnRenderFlags.Portrait);
			bool flag2 = renderFlags.FlagSet(PawnRenderFlags.Invisible);

			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);
				Vector3 vector2 = a + ((offset2 != null) ? offset2.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, pawn.story.headType) : Vector3.zero);
				vector2.y = (ba.inFrontOfBody ? (0.3f + vector2.y) : (-0.3f - vector2.y));
				float num = ba.angle;
				if (((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing) == Rot4.North)
				{
					if (ba.layerInvert)
					{
						vector2.y = 0f - vector2.y;
					}
					num = 0f;
				}
				if (((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing) == Rot4.East)
				{
					num = 0f - num;
					vector2.x = 0f - vector2.x;
				}
				Graphic addonGraphic = comp.addonGraphics[i];

				addonGraphic.drawSize = ((flag && ba.drawSizePortrait != Vector2.zero) ? ba.drawSizePortrait : ba.drawSize) * (ba.scaleWithPawnDrawsize ? (ba.alignWithHead ? ((flag ? comp.customPortraitHeadDrawSize : comp.customHeadDrawSize) * (ModsConfig.BiotechActive ? (pawn.ageTracker.CurLifeStage.headSizeFactor ?? 1.5f) : 1.5f)) : ((flag ? comp.customPortraitDrawSize : comp.customDrawSize) * (ModsConfig.BiotechActive ? pawn.ageTracker.CurLifeStage.bodySizeFactor : 1f) * 1.5f)) : (Vector2.one * 1.5f));

				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));
					}

					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));
					}

				}

			}

			return false;
		}
	}

	/*

    [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<CompBodyAnimator>();
			AlienPartGenerator.AlienComp comp = pawn.GetComp<AlienPartGenerator.AlienComp>();

			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;
					AlienPartGenerator.RotationOffset offset2 = bodyAddon.offsets.GetOffset(rotation);
					Vector3 vector2 = a + ((offset2 != null) ? offset2.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero);
					vector2.y = (bodyAddon.inFrontOfBody ? (0.3f + vector2.y) : (-0.3f - vector2.y));
					float num = bodyAddon.angle;
					if (rotation == Rot4.North)
					{
						if (bodyAddon.layerInvert)
						{
							vector2.y = -vector2.y;
						}
						num = 0f;
					}
					if (rotation == Rot4.East)
					{
						num = -num;
						vector2.x = -vector2.x;
					}

					vector = vector + Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up) * pawn.Drawer.renderer.BaseHeadOffsetAt(pawnAnimator.bodyFacing) - pawnAnimator.getPawnHeadOffset(); //convert vector into pseudo body pos for head
					quat = Quaternion.AngleAxis(pawnAnimator.headAngle, Vector3.up);
					loc = vector + (bodyAddon.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(Mathf.Acos(Quaternion.Dot(Quaternion.identity, quat)) * 2f * 57.29578f);
					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;
					AlienPartGenerator.RotationOffset offset2 = bodyAddon.offsets.GetOffset(rotation);
					Vector3 vector2 = a + ((offset2 != null) ? offset2.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero);
					vector2.y = (bodyAddon.inFrontOfBody ? (0.3f + vector2.y) : (-0.3f - vector2.y));
					float num = bodyAddon.angle;
					if (rotation == Rot4.North)
					{
						if (bodyAddon.layerInvert)
						{
							vector2.y = -vector2.y;
						}
						num = 0f;
					}
					if (rotation == Rot4.East)
					{
						num = -num;
						vector2.x = -vector2.x;
					}
					quat = Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up);
					loc = vector + (bodyAddon.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(Mathf.Acos(Quaternion.Dot(Quaternion.identity, quat)) * 2f * 57.29578f);

				}

			}
			GenDraw.DrawMeshNowOrLater(mesh, loc, quat, mat, drawNow);

			/*
			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"))

					{
						orassan = true;

						orassanv = new Vector3(0, 0, 0.23f);
						if (pawnAnimator.headFacing == Rot4.North)
						{
							orassanv.z -= 0.1f;
							orassanv.y += 1f;

							if(bodyAddon.bodyPart.Contains("left"))
                            {
								orassanv.x += 0.03f;
                            } else
                            {
								orassanv.x -= 0.03f;
							}

						}
						else if (pawnAnimator.headFacing == Rot4.East)
						{
							orassanv.x -= 0.1f;
						}
						else if (pawnAnimator.headFacing == Rot4.West)
						{
							orassanv.x = 0.1f;
						}
						else
                        {
							orassanv.z -= 0.1f;
							orassanv.y += 1f;

							if (bodyAddon.bodyPart.Contains("right"))
							{
								orassanv.x += 0.05f;
							}
							else
							{
								orassanv.x -= 0.05f;
							}
						}
						orassanv = orassanv.RotatedBy(pawnAnimator.headAngle);
					}
				}

			GenDraw.DrawMeshNowOrLater(mesh: graphic.MeshAt(rot: headRotInAnimation), loc: loc + orassanv + (bodyAddon.alignWithHead ? headOffset : Vector3.zero),// + v.RotatedBy(Mathf.Acos(Quaternion.Dot(Quaternion.identity, quat)) * 2f * 57.29578f),
					quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * headQuatInAnimation, mat: graphic.MatAt(rot: pawnAnimator.headFacing), drawNow: drawNow);;
			}

			else
            {

			}

		}

		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
			List<CodeInstruction> 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
					yield return new CodeInstruction(OpCodes.Ldloc, (object)5); //offsetVector/AddonOffset (v)
					yield return new CodeInstruction(OpCodes.Ldarg, (object)2); //headOffset
					yield return new CodeInstruction(OpCodes.Ldarg, (object)3); //pawn
					yield return new CodeInstruction(OpCodes.Ldarg, (object)0); //renderflags
					yield return new CodeInstruction(OpCodes.Ldarg, (object)1); //vector
					yield return new CodeInstruction(OpCodes.Ldarg, (object)5); //rotation

					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<CompBodyAnimator>();

			if(anim == null)
            {
				return true;
            }

			if (anim != null && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && anim.isAnimating)
			{
				//quat = Quaternion.AngleAxis(anim.bodyAngle, Vector3.up);
			}

			return true;

		}
	}

	*/

}