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 {
        /*
         Note: always make the list in this order:
             Female pawns, animal female pawns, male pawns, animal male pawns
        */
        public static AnimationDef tryFindAnimation(ref List<Pawn> participants, rjw.xxx.rjwSextype sexType = 0, rjw.SexProps sexProps = null) {

            InteractionWithExtension interaction = InteractionHelper.GetWithExtension(sexProps.dictionaryKey);

            participants =
                participants.OrderBy(p =>
                    p.jobs.curDriver is rjw.JobDriver_Sex
                    && !(p.jobs.curDriver as rjw.JobDriver_Sex).Sexprops.isRevese /*Gargulefix!!!*/
                    && !(p.jobs.curDriver as rjw.JobDriver_Sex).Sexprops.isReceiver)
                .OrderBy(p => rjw.xxx.can_fuck(p))
                .ToList();

            List<Pawn> localParticipants = new List<Pawn>(participants);

            IEnumerable<AnimationDef> options = DefDatabase<AnimationDef>.AllDefs.Where((AnimationDef x) => {

                if (x.actors.Count != localParticipants.Count) {
                    if (AnimationSettings.debugMode)
                        Log.Message(x.defName.ToStringSafe() + " not selected -- doesn't match count");
                    return false;
                }
                for (int i = 0; i < x.actors.Count; i++) {

                    if (rjw.RJWPreferenceSettings.Malesex == rjw.RJWPreferenceSettings.AllowedSex.Nohomo) {
                        if (rjw.xxx.is_male(localParticipants[i]) && x.actors[i].isFucked) {

                            if (AnimationSettings.debugMode)
                                Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " is fucked, shouldn't be due to homo rules");

                            return false;
                        }
                    }
				    if (x.actors[i].requiredGender != null && !x.actors[i].requiredGender.Contains(localParticipants[i].gender.ToStringSafe<Gender>()))
				    {
					    if (AnimationSettings.debugMode)
					    {
						    Log.Message(string.Concat(new string[]
						    {
							    x.defName.ToStringSafe<string>(),
							    " not selected -- ",
							    localParticipants[i].def.defName.ToStringSafe<string>(),
							    " ",
							    localParticipants[i].Name.ToStringSafe<Name>(),
							    " does not match required gender"
						    }));
					    }
					    return false;
				    }
                    if ((x.actors[i].blacklistedRaces != null) && x.actors[i].blacklistedRaces.Contains(localParticipants[i].def.defName)) {
                        if(AnimationSettings.debugMode)
                            Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " is blacklisted");
                        return false;
                    }

                    if(x.actors[i].defNames.Contains("Human")) {
                        if (!rjw.xxx.is_human(localParticipants[i])) {
                            if (AnimationSettings.debugMode)
                                Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " is not human");

                            return false;
                        }

                    }
                    else if (!x.actors[i].bodyDefTypes.Contains(localParticipants[i].RaceProps.body)) {

                        if (!x.actors[i].defNames.Contains(localParticipants[i].def.defName)) {

                            if (AnimationSettings.debugMode)
                            {
                                string animInfo = x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " is not ";
                                foreach(String defname in x.actors[i].defNames) {
                                    animInfo += defname + ", ";
                                }
                                if (AnimationSettings.debugMode)
                                    Log.Message(animInfo);
                            }

                            return false;
                        }
                    }
                    //genitals checking

                    if(!GenitalCheckForPawn(x.actors[i].requiredGenitals, localParticipants[i], out string failReason)) {
                        if (AnimationSettings.debugMode)
                            Log.Message("Didn't select " + x.defName + ", " + localParticipants[i].Name + " " + failReason);
                        return false;
                    }

                    //TESTING ANIMATIONS ONLY REMEMBER TO COMMENT OUT BEFORE PUSH
                    /*
                    if (x.defName != "Cunnilingus")
                        return false;
                    */

                    if (x.actors[i].isFucking && !rjw.xxx.can_fuck(localParticipants[i])) {
                        if (AnimationSettings.debugMode)
                            Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " can't fuck");
                        return false;
                    }

                    if (x.actors[i].isFucked && !rjw.xxx.can_be_fucked(localParticipants[i])) {
                        if (AnimationSettings.debugMode)
                            Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " can't be fucked");
                        return false;
                    }
                }

                if (AnimationSettings.debugMode)
                    Log.Message(x.defName.ToStringSafe() + " selected");

                return true;
            });

            List<AnimationDef> optionsWithInteractionType = options.ToList().FindAll(x => x.interactionDefTypes != null && x.interactionDefTypes.Contains(sexProps.sexType.ToStringSafe()));
            if (optionsWithInteractionType.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for interaction type " + sexProps.sexType.ToStringSafe() + "...");
                return optionsWithInteractionType.RandomElement();
            }
            List<AnimationDef> optionsWithSexType = options.ToList().FindAll(x => x.sexTypes != null && x.sexTypes.Contains(sexType));
            if (optionsWithSexType.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for rjwSexType " + sexType.ToStringSafe() + "...");
                return optionsWithSexType.RandomElement();
            }

            /*
            if(optionsWithInitiator.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for initiators...");
            }
            */

            if (options != null && options.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Randomly selecting animation...");
                return options.RandomElement();
            }
            else
                return null;
        }

        public static void RenderPawnHeadMeshInAnimation1(Mesh mesh, Vector3 loc, Quaternion quaternion, Material material, bool drawNow, Pawn pawn) {

            if (pawn == null || pawn.Map != Find.CurrentMap) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, drawNow);
                return;
            }

            if (!CompBodyAnimator.IsAnimating(pawn)) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, drawNow);
            } else {
                CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
                Vector3 pawnHeadPosition = pawnAnimator.getPawnHeadPosition();
                pawnHeadPosition.y = loc.y;
                GenDraw.DrawMeshNowOrLater(MeshPool.humanlikeHeadSet.MeshAt(pawnAnimator.headFacing), pawnHeadPosition, Quaternion.AngleAxis(pawnAnimator.headAngle, Vector3.up), material, true);
            }
        }

        public static void AdjustHead(ref Quaternion quat, ref Rot4 bodyFacing, ref Vector3 pos, ref float angle, Pawn pawn, PawnRenderFlags flags)
        {
            if (flags.FlagSet(PawnRenderFlags.Portrait)) return;

            if (CompBodyAnimator.IsAnimating(pawn))
            {
                CompBodyAnimator anim = pawn.TryGetComp<CompBodyAnimator>();
                bodyFacing = anim.headFacing;
                angle = anim.headAngle;
                quat = Quaternion.AngleAxis(anim.headAngle, Vector3.up);
                pos = anim.getPawnHeadOffset();
            }
        }

        public static void RenderPawnHeadMeshInAnimation(Mesh mesh, Vector3 loc, Quaternion quaternion, Material material, bool portrait, Pawn pawn, float bodySizeFactor = 1) {

            if (pawn == null) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, portrait);
                return;
            }

            if (!CompBodyAnimator.IsAnimating(pawn) || portrait) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, portrait);
            }
            else {
                CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
                Vector3 pawnHeadPosition = pawnAnimator.getPawnHeadPosition();
                pawnHeadPosition.x *= bodySizeFactor;
                pawnHeadPosition.x *= bodySizeFactor;
                pawnHeadPosition.y = loc.y;
                GenDraw.DrawMeshNowOrLater(mesh, pawnHeadPosition, Quaternion.AngleAxis(pawnAnimator.headAngle, Vector3.up), material, portrait);
            }
        }

        public static bool GenitalCheckForPawn(List<string> requiredGenitals, Pawn pawn, out string failReason) {

            failReason = null;
            if (requiredGenitals != null) {
                if (requiredGenitals.Contains("Vagina")) {

                    if (!rjw.Genital_Helper.has_vagina(pawn)) {
                        failReason = "missing vagina";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("Penis")) {

                    if (!(rjw.Genital_Helper.has_multipenis(pawn) || rjw.Genital_Helper.has_penis_infertile(pawn) || rjw.Genital_Helper.has_penis_fertile(pawn) || rjw.Genital_Helper.has_ovipositorM(pawn) || rjw.Genital_Helper.has_ovipositorF(pawn))) {
                        failReason = "missing penis";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("Mouth")) {

                    if (!rjw.Genital_Helper.has_mouth(pawn)) {
                        failReason = "missing mouth";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("Anus")) {

                    if (!rjw.Genital_Helper.has_anus(pawn)) {
                        failReason = "missing anus";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("Breasts")) {
                    if (!rjw.Genital_Helper.can_do_breastjob(pawn)) {
                        failReason = "missing breasts";
                        return false;
                    }
                }

                if (requiredGenitals.Contains("NoVagina")) {

                    if (rjw.Genital_Helper.has_vagina(pawn)) {
                        failReason = "has vagina";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("NoPenis")) {

                    if ((rjw.Genital_Helper.has_multipenis(pawn) || rjw.Genital_Helper.has_penis_infertile(pawn) || rjw.Genital_Helper.has_penis_fertile(pawn))) {
                        failReason = "has penis";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("NoMouth")) {

                    if (rjw.Genital_Helper.has_mouth(pawn)) {
                        failReason = "has mouth";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("NoAnus")) {

                    if (rjw.Genital_Helper.has_anus(pawn)) {
                        failReason = "has anus";
                        return false;
                    }

                }

                if (requiredGenitals.Contains("NoBreasts")) {
                    if (rjw.Genital_Helper.can_do_breastjob(pawn)) {
                        failReason = "has breasts";
                        return false;
                    }
                }
            }

            return true;

        }

        public static Rot4 PawnHeadRotInAnimation(Pawn pawn, Rot4 regularPos)
        {
            Debug.Log("Test");

            if(pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
            {
                return pawn.TryGetComp<CompBodyAnimator>().headFacing;
            }

            return regularPos;
        }
    }
}


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

namespace Rimworld_Animations
{
    public class PatchOperationAddOrReplace : PatchOperationPathed
    {

        protected string key;
        private XmlContainer value;

        protected override bool ApplyWorker(XmlDocument xml)
        {
            XmlNode valNode = value.node;
            bool result = false;
            IEnumerator enumerator = xml.SelectNodes(xpath).GetEnumerator();
            try
            {
                while (enumerator.MoveNext())
                {
                    object obj = enumerator.Current;
                    result = true;
                    XmlNode parentNode = obj as XmlNode;
                    XmlNode xmlNode = parentNode.SelectSingleNode(key);
                    if (xmlNode == null)
                    {
                        // Add - Add node if not existing
                        xmlNode = parentNode.OwnerDocument.CreateElement(key);
                        parentNode.AppendChild(xmlNode);
                    }
                    else
                    {
                        // Replace - Clear existing children
                        xmlNode.RemoveAll();
                    }
                    // (Re)add value
                    xmlNode.AppendChild(parentNode.OwnerDocument.ImportNode(valNode.FirstChild, true));
                }
            }
            finally
            {
                IDisposable disposable = enumerator as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
            return result;
        }

    }

}

repo_name: nekonyan/rimworld

license: unknown


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<AnimationDef>
		<defName>TestAnimation1</defName>
		<durationTicks>400</durationTicks>
		<playWhenDowned>False</playWhenDowned>
		<animationParts>

				<key>Root</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<tick>30</tick>
							<angle>0</angle>
							<visible>true</visible>

							<tick>100</tick>
							<angle>0</angle>
							<visible>true</visible>

							<tick>200</tick>
							<angle>0</angle>
							<visible>true</visible>

							<tick>300</tick>
							<angle>0</angle>
							<visible>true</visible>

					</keyframes>
				</value>

				<key>Body</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<tick>0</tick>
							<angle>0</angle>
							<rotation>North</rotation>
							<visible>false</visible>

							<tick>50</tick>
							<angle>0</angle>
							<rotation>South</rotation>
							<visible>true</visible>

							<tick>100</tick>
							<angle>30</angle>
							<rotation>East</rotation>
							<visible>false</visible>

					</keyframes>
				</value>

				<key>Head</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<tick>0</tick>
							<angle>0</angle>
							<rotation>North</rotation>
							<visible>true</visible>

							<tick>2</tick>
							<angle>0</angle>
							<rotation>East</rotation>
							<visible>true</visible>

							<tick>4</tick>
							<angle>0</angle>
							<rotation>South</rotation>
							<visible>true</visible>

							<tick>6</tick>
							<angle>0</angle>
							<rotation>West</rotation>
							<visible>false</visible>

							<tick>8</tick>
							<angle>0</angle>
							<rotation>North</rotation>
							<visible>false</visible>

					</keyframes>
				</value>

				<key>RenderNodeTag_Banana</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<tick>30</tick>
							<angle>0</angle>
							<visible>true</visible>

							<tick>100</tick>
							<angle>0</angle>
							<visible>false</visible>

							<tick>200</tick>
							<angle>0</angle>
							<visible>true</visible>

							<tick>300</tick>
							<angle>0</angle>
							<visible>true</visible>

					</keyframes>
				</value>

		</animationParts>
	</AnimationDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<AnimationDef>
		<defName>TestAnimation2</defName>
		<durationTicks>120</durationTicks>
		<startOnRandomTick>False</startOnRandomTick>
		<playWhenDowned>False</playWhenDowned>

		<animationParts>

				<key>Root</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<offset>(1, -1, 0)</offset>
							<tick>0</tick>
							<angle>0</angle>
							<rotation>North</rotation>

						  <offset>(0, -1, 0)</offset>
						  <tick>30</tick>
						  <angle>15</angle>
						  <rotation>North</rotation>

						  <offset>(-1, -1, 0)</offset>
						  <tick>60</tick>
						  <angle>0</angle>
						  <rotation>North</rotation>

						  <offset>(0, -1, 0)</offset>
						  <tick>90</tick>
						  <angle>-15</angle>
						  <rotation>North</rotation>

					</keyframes>
				</value>

				<key>Head</key>
				<value>
					<workerClass>Rimworld_Animations.AnimationWorker_KeyframesExtended</workerClass>
					<keyframes>

							<tick>0</tick>
							<angle>0</angle>
							<rotation>North</rotation>

							<tick>15</tick>
							<angle>30</angle>
							<rotation>East</rotation>

							<tick>30</tick>
							<angle>0</angle>
							<rotation>South</rotation>

							<tick>45</tick>
							<angle>-30</angle>
							<rotation>West</rotation>

					</keyframes>
				</value>

		</animationParts>
	</AnimationDef>
</Defs>


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

	<PawnRenderNodeTagDef>
		<defName>RenderNodeTag_Banana</defName>
	</PawnRenderNodeTagDef>

	<Rimworld_Animations.AnimationPropDef>
		<defName>AnimationProp_Banana</defName>

		<!-- Note: props always return graphic_multi; set cardinal textures to blank and only north in anim if only one tex-->
		<!-- or draw east, south, and west directions, use them in animations? -->
		<animPropProperties>
			<debugLabel>AnimProp_Banana</debugLabel>
			<workerClass>Rimworld_Animations.PawnRenderNodeWorker_AnimationProp</workerClass>
			<tagDef>RenderNodeTag_Banana</tagDef>
			<parentTagDef>Body</parentTagDef>
			<texPath>AnimationProps/Banana/Banana</texPath>
			<!-- for height -->
			<overlayLayer>Head</overlayLayer>
			<baseLayer>95</baseLayer>

		</animPropProperties>
	</Rimworld_Animations.AnimationPropDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<Rimworld_Animations.GroupAnimationDef>
		<defName>TestGroupAnimation1</defName>
		<numActors>2</numActors>
		<animationStages>

				<loops>10</loops>
				<loopOptions>

						<probability>3</probability>
						<animationDefs>

TestAnimation1

TestAnimation2

						</animationDefs>

						<probability>1</probability>
						<animationDefs>

TestAnimation1

TestAnimation2

						</animationDefs>

				</loopOptions>

				<paths>

						<loops>10</loops>
						<loopOptions>

								<probability>3</probability>
								<animationDefs>

TestAnimation1

TestAnimation1

								</animationDefs>

								<probability>1</probability>
								<animationDefs>

TestAnimation1

TestAnimation1

								</animationDefs>

						</loopOptions>

						<loops>10</loops>
						<loopOptions>

								<probability>3</probability>
								<animationDefs>

TestAnimation2

TestAnimation2

								</animationDefs>

								<probability>1</probability>
								<animationDefs>

TestAnimation2

TestAnimation2

								</animationDefs>

						</loopOptions>

				</paths>

		</animationStages>

		<contexts>

				<interactionDefs>

Sex_Anal

Sex_Vaginal

				</interactionDefs>

				<actorShift>1</actorShift>
				<interactionDefs>

Sex_Reverse_Anal

Sex_Reverse_Vaginal

				</interactionDefs>

		</contexts>

	</Rimworld_Animations.GroupAnimationDef>
</Defs>


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

<Defs>
  <MainButtonDef>
    <defName>OffsetManager</defName>
    <label>offset manager</label>
    <description>Control pawn offsets</description>
    <tabWindowClass>Rimworld_Animations.MainTabWindow_OffsetConfigure</tabWindowClass>
    <order>54</order>
    <buttonVisible>false</buttonVisible>
    <iconPath>UI/MainTab</iconPath>
    <minimized>true</minimized>
  </MainButtonDef>

</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<Rimworld_Animations.OffsetDef>
		<defName>GroinToAppropriateHeight</defName>
		<bodyTypeOffsets>

				<bodyType>Male</bodyType>
				<offset>(0, 0, 0.5)</offset>

		</bodyTypeOffsets>
		<raceOffsets>

				<def>MinecraftCreeper</def>
				<genderGraphics>
					<Female>
						<Hulk>(0, 0, 0.5)</Hulk>
						<Thin>(0, 0, 0.3)</Thin>
					</Female>
					<Male>
						<Hulk>(0, 0, 0.5)</Hulk>
						<Thin>(0, 0, 0.3)</Thin>
					</Male>
				</genderGraphics>

				<def>MinecraftPig</def>
				<bodyType>Male</bodyType>
				<offset>(0, 0, -0.5)</offset>

				<def>MinecraftPig</def>
				<bodyType>Female</bodyType>
				<offset>(0, 0, -0.5)</offset>

		</raceOffsets>
	</Rimworld_Animations.OffsetDef>
</Defs>


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

<Defs>
	<SoundDef>
		<defName>Cum</defName>
		<context>MapOnly</context>
		<eventNames />
		<maxSimultaneous>1</maxSimultaneous>
		<maxVoices>1</maxVoices>
		<subSounds>

				<grains>

						<clipPath>Sex/cum</clipPath>

				</grains>
				<volumeRange>
					<min>30</min>
					<max>40</max>
				</volumeRange>
				<pitchRange>
					<min>0.8</min>
					<max>1.2</max>
				</pitchRange>
				<distRange>
					<min>0</min>
					<max>51.86047</max>
				</distRange>
				<sustainLoop>False</sustainLoop>

		</subSounds>
	</SoundDef>
  <SoundDef>
		<defName>Sex</defName>
		<context>MapOnly</context>
		<eventNames />
		<maxSimultaneous>1</maxSimultaneous>
		<maxVoices>1</maxVoices>
		<subSounds>

				<grains>

						<clipPath>Sex/kucyu04</clipPath>

				</grains>
				<volumeRange>
					<min>16</min>
					<max>16</max>
				</volumeRange>
				<pitchRange>
					<min>0.8</min>
					<max>1.2</max>
				</pitchRange>
				<distRange>
					<min>0</min>
					<max>51.86047</max>
				</distRange>
				<sustainLoop>False</sustainLoop>

		</subSounds>
	</SoundDef>
	<SoundDef>
		<defName>Suck</defName>
		<context>MapOnly</context>
		<eventNames />
		<maxSimultaneous>1</maxSimultaneous>
		<maxVoices>1</maxVoices>
		<subSounds>

				<grains>

						<clipPath>Sex/Suck/Suck_1</clipPath>

						<clipPath>Sex/Suck/Suck_2</clipPath>

						<clipPath>Sex/Suck/Suck_3</clipPath>

						<clipPath>Sex/Suck/Suck_4</clipPath>

						<clipPath>Sex/Suck/Suck_5</clipPath>

            <clipPath>Sex/Suck/Suck_6</clipPath>

            <clipPath>Sex/Suck/Suck_7</clipPath>

            <clipPath>Sex/Suck/Suck_8</clipPath>

            <clipPath>Sex/Suck/Suck_9</clipPath>

            <clipPath>Sex/Suck/Suck_10</clipPath>

				</grains>
				<volumeRange>
          <min>20</min>
          <max>35</max>
				</volumeRange>
				<pitchRange>
					<min>1.0</min>
					<max>1.0</max>
				</pitchRange>
				<distRange>
					<min>0</min>
					<max>51.86047</max>
				</distRange>
				<repeatMode>NeverTwice</repeatMode>
				<sustainLoop>false</sustainLoop>

		</subSounds>
	</SoundDef>
  <SoundDef>
    <defName>Fuck</defName>
    <context>MapOnly</context>
    <eventNames />
    <maxSimultaneous>1</maxSimultaneous>
    <maxVoices>1</maxVoices>
    <subSounds>

        <grains>

            <clipPath>Sex/Clap_1</clipPath>

            <clipPath>Sex/Clap_2</clipPath>

            <clipPath>Sex/Clap_3</clipPath>

            <clipPath>Sex/Clap_4</clipPath>

            <clipPath>Sex/Clap_5</clipPath>

            <clipPath>Sex/Clap_6</clipPath>

            <clipPath>Sex/Clap_7</clipPath>

            <clipPath>Sex/Clap_8</clipPath>

        </grains>
        <volumeRange>
          <min>45</min>
          <max>70</max>
        </volumeRange>
        <pitchRange>
          <min>1.0</min>
          <max>1.0</max>
        </pitchRange>
        <distRange>
          <min>0</min>
          <max>51.86047</max>
        </distRange>
        <repeatMode>NeverTwice</repeatMode>
        <sustainLoop>false</sustainLoop>

    </subSounds>
  </SoundDef>
  <SoundDef>
    <defName>Slimy</defName>
    <context>MapOnly</context>
    <eventNames />
    <maxSimultaneous>1</maxSimultaneous>
    <maxVoices>1</maxVoices>
    <subSounds>

        <grains>

            <clipPath>Sex/Slime/Slimy1</clipPath>

            <clipPath>Sex/Slime/Slimy2</clipPath>

            <clipPath>Sex/Slime/Slimy3</clipPath>

            <clipPath>Sex/Slime/Slimy4</clipPath>

            <clipPath>Sex/Slime/Slimy5</clipPath>

        </grains>
        <volumeRange>
          <min>45</min>
          <max>75</max>
        </volumeRange>
        <pitchRange>
          <min>1.4</min>
          <max>1.8</max>
        </pitchRange>
        <distRange>
          <min>0</min>
          <max>100</max>
        </distRange>
        <repeatMode>NeverTwice</repeatMode>
        <sustainLoop>false</sustainLoop>

    </subSounds>
  </SoundDef>
</Defs>


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>{BA766964-1716-422D-A09E-29426F8EB9D5}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Patch_HatsDisplaySelection</RootNamespace>
    <AssemblyName>Patch_HatsDisplaySelection</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>1.2\Assemblies\</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\2009463077\Current\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="HatDisplaySelection">
      <HintPath>..\..\..\..\..\workshop\content\294100\1542291825\1.2\Assemblies\HatDisplaySelection.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Rimworld-Animations">
      <HintPath>..\1.2\Assemblies\Rimworld-Animations.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>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Source\Patches\Patch_HatsDisplaySelection.cs" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="1.2\Assemblies\" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


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>{0AD4F9B2-AE28-4E42-86D6-611FB93B7FF0}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Patch_HumanoidAlienRaces</RootNamespace>
    <AssemblyName>Patch_HumanoidAlienRaces</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
  </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.4\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="AlienRace">
      <HintPath>..\..\..\..\..\..\workshop\content\294100\839005762\1.4\Assemblies\AlienRace.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="HugsLib">
      <HintPath>..\..\..\..\..\..\workshop\content\294100\818773962\v1.4\Assemblies\HugsLib.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Rimworld-Animations">
      <HintPath>..\Assemblies\Rimworld-Animations.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>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="HarmonyPatch_AlienRace.cs" />
    <Compile Include="Properties\AssemblyInfo.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("Patch_HumanoidAlienRaces")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Patch_HumanoidAlienRaces")]
[assembly: AssemblyCopyright("Copyright ©  2022")]
[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("0ad4f9b2-ae28-4e42-86d6-611fb93b7ff0")]

// 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:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
	<Operation Class="PatchOperationFindMod">
    <mods>

Core SK

    </mods>
		<match Class="PatchOperationSequence">
			<operations>

					<xpath>Defs/ThingDef/comps</xpath>
					<success>Always</success>
					<nomatch Class="PatchOperationAdd">
						<xpath>Defs/ThingDef</xpath>
						<value>
							<comps />
						</value>
					</nomatch>

					<xpath>Defs/ThingDef[@Name="BaseAnimalPawn" or @Name="SK_BasePawn" or @Name="BasePawnSkynet"]/comps</xpath>
					<value>

					</value>

			</operations>
		</match>
	</Operation>
</Patch>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
  <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>

		<success>Always</success>
		<xpath>Defs/ThingDef[race][not(comps)]</xpath>
		<value>
		  <comps />
		</value>

		<success>Always</success>
		<xpath>Defs/AlienRace.ThingDef_AlienRace[not(comps)]</xpath>
		<value>
		  <comps />
		</value>

        <xpath>Defs/ThingDef[@Name="BasePawn"]/comps</xpath>
        <value>

        </value>

        <xpath>Defs/AlienRace.ThingDef_AlienRace/comps</xpath>
        <value>

        </value>

    </operations>
  </Operation>
</Patch>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
  <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>

        <xpath>/Defs/ThingDef[@Name="BaseBaseAutocleaner"]/comps</xpath>
        <success>Always</success>
	    <match Class="PatchOperationAdd">
		  <xpath>/Defs/ThingDef[@Name="BaseBaseAutocleaner"]/comps</xpath>
		  <value>

		  </value>
	    </match>

    </operations>
  </Operation>
</Patch>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
  <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>

        <xpath>/Defs/ThingDef[@Name="BasePawnSimple"]/comps</xpath>
        <success>Always</success>
	    <match Class="PatchOperationAdd">
		  <xpath>/Defs/ThingDef[@Name="BasePawnSimple"]/comps</xpath>
		  <value>

		  </value>
	    </match>

    </operations>
  </Operation>
</Patch>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
  <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>

        <xpath>/Defs/ThingDef[@Name="BaseZombie"]/comps</xpath>
        <success>Always</success>
	    <match Class="PatchOperationAdd">
		  <xpath>/Defs/ThingDef[@Name="BaseZombie"]/comps</xpath>
		  <value>

		  </value>
	    </match>

    </operations>
  </Operation>
</Patch>


code:
<?xml version="1.0" encoding="utf-8"?>
<Patch>
  <Operation Class="PatchOperationFindMod">
    <mods>

[NL] Facial Animation - WIP

    </mods>
    <match Class="PatchOperationSequence">
      <success>Always</success>
      <operations>

          <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Lovin" or defName="Lovin2"]/targetJobs</xpath>
          <success>Always</success>
          <value>

RJW_Masturbate

GettinBred

Bestiality

BestialityForFemale

ViolateCorpse

Quickie

GettingQuickie

GettinRaped

JoinInBed

GettinLoved

GettinLicked

GettinSucked

WhoreIsServingVisitors

JoinInBedAnimation

GettinLovedAnimation

          </value>

          <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="WaitCombat" or defName="Wait_Combat_Rare"]/targetJobs</xpath>
          <success>Always</success>
          <value>

RapeComfortPawn

RandomRape

RapeEnemy

          </value>

          <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="StandAndBeSociallyActive"]/targetJobs</xpath>
          <success>Always</success>
          <value>

WhoreInvitingVisitors

          </value>

          <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Wear" or defName="Wear2" or defName="Wear3"]/targetJobs</xpath>
          <success>Always</success>
          <value>

CleanSelf

StruggleInBondageGear

          </value>

          <mods>

Rimworld-Animations

          </mods>
          <match Class="PatchOperationSequence">
            <success>Always</success>
            <operations>

                <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Lovin" or defName="Lovin2"]/animationFrames/li[1]/headOffset</xpath>
                <success>Always</success>

                <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Lovin"]/animationFrames/li[2]/headOffset</xpath>
                <success>Always</success>

                <xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Lovin"]/animationFrames/li[3]/headOffset</xpath>
                <success>Always</success>

            </operations>
          </match>

      </operations>
    </match>
  </Operation>
</Patch>


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

	<Operation Class="PatchOperationFindMod">
   		<mods>

Core SK

   		</mods>
		<match Class="PatchOperationSequence">
			<success>Always</success>
            <operations>

                    <xpath>/Defs/ThingDef/comps</xpath>
                    <success>Always</success>
                    <nomatch Class="PatchOperationAdd">
                        <xpath>/Defs/ThingDef</xpath>
                        <value>
                            <comps />
                        </value>
                    </nomatch>

                    <xpath>/Defs/ThingDef[@Name="SK_BasePawn"]/comps</xpath>
                    <value>

                    </value>

                    <xpath>/Defs/ThingDef[@Name="BaseAnimalPawn"]/comps</xpath>
                    <value>

                    </value>

            </operations>
		</match>
    </Operation>

</Patch>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
	<!-- Add genitalia rendernode to pawns, body is parent
	<Operation Class="PatchOperationAdd">
		<xpath>Defs/PawnRenderTreeDef[defName="Humanlike"]/root/children/li[debugLabel="Body"]/children</xpath>
		<value>
			<li Class="PawnRenderNodeProperties_Overlay">
				<debugLabel>Genitalia</debugLabel>
				<workerClass>PawnRenderNodeWorker_OverlayWounds</workerClass>
				<overlayLayer>Body</overlayLayer>
				<baseLayer>3</baseLayer>
			</li>
		</value>
	</Operation>
	-->
</Patch>


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 Actor {
        public List<string> defNames;
        public List<string> requiredGenitals;
        public List<AlienRaceOffset> raceOffsets;
        public List<string> blacklistedRaces;
        public bool initiator = false;
        public string gender;
        public bool isFucking = false;
        public bool isFucked = false;
        public bool controlGenitalAngle = false;
        public List<BodyDef> bodyDefTypes = new List<BodyDef>();
        public BodyTypeOffset bodyTypeOffset = new BodyTypeOffset();
        public Vector3 offset = new Vector2(0, 0);
		public List<string> requiredGender;
        public List<string> tags = new List<string>();
    }
}


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

namespace Rimworld_Animations {
    public class AlienRaceOffset {

        public string defName;
        public Vector2 offset;

    }
}


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

namespace Rimworld_Animations {
    public class BodyTypeOffset {

        public Vector2? Male;
        public Vector2? Female;
        public Vector2? Thin;
        public Vector2? Hulk;
        public Vector2? Fat;

    }
}


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

namespace Rimworld_Animations
{
    public class AnimationPropDef : Def
    {

        public PawnRenderNodeProperties animPropProperties;

    }
}


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

namespace Rimworld_Animations
{
    class AnimationWorker_KeyframesExtended : AnimationWorker_Keyframes
    {

        public AnimationWorker_KeyframesExtended(AnimationDef def, Pawn pawn, AnimationPart part, PawnRenderNode node) : base(def, pawn, part, node)
        {
        }

        public override Vector3 OffsetAtTick(int tick, PawnDrawParms parms)
        {
            //Todo: Use this for bodyoffsets
            return base.OffsetAtTick(tick, parms);
        }

        //use extendedkeyframes to determine addon facing
        public Rot4 facingAtTick(int tick)
        {

            //if ticks are < first keyframe tick, just be stuck to first keyframe rot
            if (tick <= this.part.keyframes[0].tick) {

                return (this.part.keyframes[0] as ExtendedKeyframe).rotation;

            }

            //if ticks are > last keyframe tick, just be stuck to last keyframe rot
            if (tick >= this.part.keyframes[this.part.keyframes.Count - 1].tick)
            {

                return (this.part.keyframes[this.part.keyframes.Count - 1] as ExtendedKeyframe).rotation;

            }
            Verse.Keyframe keyframe = this.part.keyframes[0];
            Verse.Keyframe keyframe2 = this.part.keyframes[this.part.keyframes.Count - 1];
            int i = 0;
            while (i < this.part.keyframes.Count)
            {
                if (tick <= this.part.keyframes[i].tick)
                {
                    keyframe2 = this.part.keyframes[i];
                    if (i > 0)
                    {
                        keyframe = this.part.keyframes[i - 1];
                        break;
                    }
                    break;
                }
                else
                {
                    i++;
                }
            }

            return (keyframe as ExtendedKeyframe).rotation;

        }
        public bool visibleAtTick(int tick)
        {
            //if ticks are < first keyframe tick, just be stuck to first keyframe rot
            if (tick <= this.part.keyframes[0].tick)
            {

                return (this.part.keyframes[0] as ExtendedKeyframe).visible;

            }

            //if ticks are > last keyframe tick, just be stuck to last keyframe rot
            if (tick >= this.part.keyframes[this.part.keyframes.Count - 1].tick)
            {

                return (this.part.keyframes[this.part.keyframes.Count - 1] as ExtendedKeyframe).visible;

            }

            Verse.Keyframe keyframe = this.part.keyframes[0];
            Verse.Keyframe keyframe2 = this.part.keyframes[this.part.keyframes.Count - 1];

            int i = 0;
            while (i < this.part.keyframes.Count)
            {
                if (tick <= this.part.keyframes[i].tick)
                {
                    keyframe2 = this.part.keyframes[i];
                    if (i > 0)
                    {
                        keyframe = this.part.keyframes[i - 1];
                        break;
                    }
                    break;
                }
                else
                {
                    i++;
                }
            }

            return (keyframe as ExtendedKeyframe).visible;

        }

        public bool shouldRecache(int tick)
        {
            if (facingAtTick(tick) != facingAtTick(tick - 1) || visibleAtTick(tick) != visibleAtTick(tick - 1))
            {
                return true;
            }

            return true;
        }
    }
}


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

namespace Rimworld_Animations {
    public class CompExtendedAnimator : ThingComp
    {

        // CompExtendedAnimator
        // Helps manage AnimationQueue, AbsolutePosition
        //ticks of current animation
        private int animationTicks;

        private List<AnimationDef> animationQueue;
        private BaseExtendedAnimatorAnchor anchor;
        private bool isAnimating = false;

        public bool IsAnimating
        {
            get
            {
                return isAnimating;
            }
        }

        public bool IsAnchored
        {
            get
            {
                return anchor != null;
            }
        }

        public Vector3 getAnchor()
        {
            return anchor.getDrawPos();
        }

        public override void CompTick()
        {
            if (isAnimating)
            {
                animationTicks++;

                //if animationticks is equal to cur. anim duration,
                if (animationTicks >= animationQueue[0].durationTicks)
                {
                    //dequeue; returns false if more animations
                    if (!PopAnimationQueue())
                    {
                        //play next if more anims still
                        PlayNextAnimation();
                    }
                    else
                    {
                        StopAnimating();
                    }
                }
            }

            base.CompTick();
        }

        //returns false if still more animations
        public bool PopAnimationQueue()
        {

            if (!animationQueue.Empty())
            {
                //pop queue
                animationQueue.RemoveAt(0);
            }

            return animationQueue.Empty();
        }

        public void PlayNextAnimation()
        {
            if (!animationQueue.Empty())
            {
                isAnimating = true;
                animationTicks = 0;
                pawn.Drawer.renderer.SetAnimation(animationQueue[0]);
            }
        }

        public void StopAnimating()
        {
            isAnimating = false;
            animationQueue = null;
            anchor = null;
            pawn.Drawer.renderer.SetAnimation(null);
        }

        public void PlayGroupAnimation(List<AnimationDef> groupAnimation)
        {
            animationQueue = groupAnimation;
            PlayNextAnimation();
        }

        public void PlayGroupAnimation(List<AnimationDef> groupAnimation, BaseExtendedAnimatorAnchor anchor)
        {
            this.anchor = anchor;
            PlayGroupAnimation(groupAnimation);
        }

        public override void PostExposeData()
        {
            base.PostExposeData();
            Scribe_Values.Look<bool>(ref this.isAnimating, "animations_isAnimating", false);
            Scribe_Values.Look<int>(ref this.animationTicks, "animations_ticks", 0);
            Scribe_Collections.Look<AnimationDef>(ref animationQueue, "animations_queue");
            Scribe_Deep.Look<BaseExtendedAnimatorAnchor>(ref this.anchor, "animations_anchor");

        }

        public override List<PawnRenderNode> CompRenderNodes()
        {
            //only if pawn is animating for performance
            if (IsAnimating)
            {
                List<PawnRenderNode> animRenderNodes = new List<PawnRenderNode>();

                // for all animationpropdefs,
                foreach (AnimationPropDef animationProp in DefDatabase<AnimationPropDef>.AllDefsListForReading)
                {
                    //if animation makes use of prop,
                    if (AnimationMakesUseOfProp(animationProp))
                    {
                        //create new render node
                        PawnRenderNode animRenderNode = new PawnRenderNode(pawn, animationProp.animPropProperties, pawn.Drawer.renderer.renderTree);
                        animRenderNodes.Add(animRenderNode);
                    }

                }

                //return list of rendernodes that should animate
                return animRenderNodes;

            }
            else
            {
                return null;
            }

        }

        public bool AnimationMakesUseOfProp(AnimationPropDef animationProp)
        {
            // never true if not animating; anim props shouldn't be attached
            if (!IsAnimating) return false;

            //for all queued animations,
            foreach (AnimationDef animation in animationQueue)
            {
                // for each prop tag in the currently playing animation,
                foreach (PawnRenderNodeTagDef propTag in animation.animationParts.Keys)
                {
                    // if that proptag is the same as the one for animationProp,
                    if (propTag == animationProp.animPropProperties.tagDef)
                    {
                        //that prop is being used in the animation
                        return true;
                    }
                }
            }
            return false;
        }

        private Pawn pawn => base.parent as Pawn;

    }

}


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

namespace Rimworld_Animations {
    public class CompProperties_ExtendedAnimator : CompProperties
    {
        public CompProperties_ExtendedAnimator()
        {
            base.compClass = typeof(CompExtendedAnimator);

        }
    }
}


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

namespace Rimworld_Animations {
    public class CompProperties_ThingAnimator : CompProperties
    {

        public CompProperties_ThingAnimator()
        {
            base.compClass = typeof(CompThingAnimator);
        }
    }
}


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


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()
        {
            return thing.DrawPos;
        }

        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()
        {
            this.position = position;
        }

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

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


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

namespace Rimworld_Animations
{
    public abstract class BaseGroupAnimationContext
    {
        public int actorShift = 0;
        public abstract bool CanAnimationBeUsed(List<Pawn> actors, out int reorder);
        public abstract string DebugMessage();

        //cool class for designating contexts for animations
        // configure CanAnimationBeUsed to test whether it can be used
    }
}


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

namespace Rimworld_Animations
{
    public class GroupAnimationContext_RJWSex : BaseGroupAnimationContext
    {

        public List<InteractionDef> interactionDefs;

        public override bool CanAnimationBeUsed(List<Pawn> actors, out int reorder)
        {

            JobDriver_SexBaseInitiator latestSexBaseInitiator = (actors.FindLast(x => x.jobs?.curDriver is JobDriver_SexBaseInitiator).jobs.curDriver as JobDriver_SexBaseInitiator);
            reorder = base.actorShift;

            return interactionDefs.Contains(latestSexBaseInitiator.Sexprops.dictionaryKey);
        }

        public override string DebugMessage()
        {
            return "Checking for RJWSex AnimationContext\n"
                + "InteractionDefs: " + interactionDefs;
        }
    }
}


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

namespace Rimworld_Animations
{
    public class GroupAnimationDef : Def
    {
        public int numActors;
        public List<AnimationStage> animationStages;
        public List<BaseGroupAnimationContext> contexts;

        public bool canAnimationBeUsed(List<Pawn> actors, out int reorder)
        {
            if (AnimationSettings.debugMode)
            {
                Log.Message("[anims] Checking if " + defName + " is valid animation");
            }

            foreach (BaseGroupAnimationContext context in contexts)
            {
                if (context.CanAnimationBeUsed(actors, out reorder))
                {
                    return true;
                }
            }

            reorder = 0;
            return false;
        }

        public List<AnimationDef> GetAllAnimationsForActor(int actor, int seed, int reorder = 0)
        {
            List<AnimationDef> animations = new List<AnimationDef>();
            int actorNumber = (actor + reorder) % numActors;

            foreach (AnimationStage stage in animationStages)
            {
                //add all new animations to list of animations
                animations.AddRange(stage.GetAnimations(actorNumber, seed));
            }

            return animations;
        }
    }
}


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

namespace Rimworld_Animations
{
    public abstract class AnimationStage
    {

        //Return a list containing a tuple; int for how long the animation should play for
        public abstract List<AnimationDef> GetAnimations(int actorNumber, int seed);

    }
}


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

namespace Rimworld_Animations
{
    public class AnimationStage_Branch : AnimationStage
    {
        public List<AnimationStage> paths;
        public override List<AnimationDef> GetAnimations(int actorNumber, int seed)
        {
            return paths[(seed * 59) % paths.Count].GetAnimations(actorNumber, seed);
        }
    }
}


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

namespace Rimworld_Animations
{
    public class AnimationStage_LoopRandomSelectChance : AnimationStage
    {
        public int loops;
        public List<AnimationLoopOption> loopOptions;

        public override List<AnimationDef> GetAnimations(int actorNumber, int seed)
        {
            int numberOfActors = loopOptions[0].animationDefs.Count;
            List<AnimationDef> animations = new List<AnimationDef>();

            for (int i = 0; i < loops; i++)
            {
                AnimationLoopOption option = getAnimationLoopOptionByWeight(seed + i);
                animations.Add(option.animationDefs[actorNumber]);
            }

            return animations;
        }

        //select random element from loop options by weight; also calculate the longest anim length
        private AnimationLoopOption getAnimationLoopOptionByWeight(int seed)
        {
            int totalWeight = loopOptions.Sum(x => x.probability);
            int randomNumber = ((seed * 59) % totalWeight) + 1;

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

                cumulativeWeight += loopOptions[i].probability;

                //random number is same for all pawns because they all have the same seed

                if (randomNumber <= cumulativeWeight)
                {
                    return loopOptions[i];
                }
            }

            //default
            return loopOptions.Last();
        }
    }

    public class AnimationLoopOption
    {
        public int probability;
        public List<AnimationDef> animationDefs;
    }
}


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

namespace Rimworld_Animations
{
    /* don't use? just use looprandomselect once
    public class AnimationStage_TicksDuration : AnimationStage
    {
        public int ticks;
        public List<AnimationDef> animationDefs;

        public override List<Tuple<int, AnimationDef>> GetAnimations(int actor, int seed)
        {
            return new List<Tuple<int, AnimationDef>>() { Tuple.Create(ticks, animationDefs[actor]) };
        }
    }

    */
}


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 ExtendedKeyframe : Verse.Keyframe
    {
        public Rot4 rotation = Rot4.North;
        public SoundDef sound = null;
        public bool visible = true;

    }
}


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

namespace Rimworld_Animations {
    class MainTabWindow_OffsetConfigure : MainTabWindow
    {

        public override Vector2 RequestedTabSize => new Vector2(505, 380);
        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("Animation Manager");

            listingStandard.GapLine();

            /*
            if (Find.Selector.SingleSelectedThing is Pawn curPawn) {

                if (CompBodyAnimator.IsAnimating(curPawn)) {

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

                }

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

        }

        public override void PreOpen() {
            base.PreOpen();
            if(AnimationSettings.offsets == null) {
                if (AnimationSettings.debugMode)
                    Log.Message("New offsets");
                AnimationSettings.offsets = new Dictionary<string, Vector2>();
            }

            if(AnimationSettings.rotation == null) {
                if (AnimationSettings.debugMode)
                    Log.Message("New rotation");
                AnimationSettings.rotation = new Dictionary<string, float>();
            }
        }

        public override void PostClose() {
            base.PostClose();

            LoadedModManager.GetMod<RJW_Animations>().WriteSettings();
        }
    }
}


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 = AnimationSettings.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 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 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;
using Verse;

namespace Rimworld_Animations
{
    /*
    [HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
    public class HarmonyPatch_JobDriver_Sex
    {
        public static void Prefix(JobDriver_Sex __instance)
        {
            Pawn partner = __instance.Partner;

        }
    }

    */
}


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) {
			/*
			 These particular jobs need special code
			 don't play anim for now
			 */
			if(__instance is JobDriver_Masturbate || __instance is JobDriver_ViolateCorpse) {

				return;

			}

			if(!AnimationSettings.PlayAnimForNonsexualActs && NonSexualAct(__instance))
            {
				return;
            }

			Pawn pawn = __instance.pawn;
			Pawn partner = __instance.Target as Pawn;
			Building_Bed bed = __instance.Bed;

			if (partner?.jobs?.curDriver is JobDriver_SexBaseReciever partnerSexBaseReceiver) {

				Pawn Target = __instance.Target as Pawn;

				bool quickie = (__instance is JobDriver_SexQuick) && AnimationSettings.fastAnimForQuickie;

				int preAnimDuration = __instance.duration;
				int AnimationTimeTicks = 0;

				List<Pawn> participants = partnerSexBaseReceiver.parteners.Append(partner).ToList();

				GroupAnimationDef groupAnimation = AnimationUtility.FindGroupAnimation(participants, out int reorder);
				if (groupAnimation != null)
                {
					AnimationUtility.StartGroupAnimation(participants, groupAnimation, reorder, partner);
				}

				//Modify Orgasm ticks to only orgasm as many times as RJW stock orgasm allows
				if (AnimationTimeTicks != 0)
                {
					__instance.orgasmstick = preAnimDuration * __instance.orgasmstick / AnimationTimeTicks;
				}

			}
		}

		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)
		{

			AnimationUtility.StopGroupAnimation(__instance.pawn);
		}
	}
}


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


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
    {
        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 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)
            {

                // 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
                //return extendedAnimWorker.visibleAtTick(__instance.tree.AnimationTick);

            }

            return true;

        }
    }
}


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(PawnRenderTree), "TryGetMatrix")]
    public class HarmonyPatch_PawnRenderTree
    {
        public static void Prefix(PawnRenderTree __instance, Dictionary<PawnRenderNodeTagDef, PawnRenderNode> ___nodesByTag, PawnRenderNode node, ref PawnDrawParms parms)
        {

            //find lowest parent that is animating, or nothing if not animating
            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);
            }
        }

    }

    //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 rjw;
using UnityEngine;
using Verse;

namespace Rimworld_Animations {

    [HarmonyPatch(typeof(Pawn_DrawTracker), "DrawPos", MethodType.Getter)]
    public static class HarmonyPatch_Pawn_DrawTracker {
        public static bool Prefix(ref Pawn ___pawn, ref Vector3 __result) {

            CompExtendedAnimator animator = ___pawn.TryGetComp<CompExtendedAnimator>();

            //align pos on top of partner, position, etc., based on animatoranchor
            if (animator != null && animator.IsAnchored)
            {
                __result = animator.getAnchor();
                return false;
            }

            return true;

        }
    }
}


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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace Rimworld_Animations
{
    public class PawnRenderNodeWorker_AnimationProp : PawnRenderNodeWorker
    {
        public override bool CanDrawNow(PawnRenderNode node, PawnDrawParms parms)
        {
            if (node.AnimationWorker is AnimationWorker_KeyframesExtended propAnimator)
            {
                return propAnimator.visibleAtTick(node.tree.AnimationTick);
            }

            return false;
        }

    }
}


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 AnimationSettings : ModSettings {

        public static bool orgasmQuiver, rapeShiver, soundOverride = true, hearts = true, controlGenitalRotation = false, applySemenOnAnimationOrgasm = false, fastAnimForQuickie = false,
            PlayAnimForNonsexualActs = true;
        public static bool offsetTab = false, debugMode = false;
        public static float shiverIntensity = 2f;

        public static Dictionary<string, Vector2> offsets = new Dictionary<string, Vector2>();
        public static Dictionary<string, float> rotation = new Dictionary<string, float>();

        public override void ExposeData() {

            base.ExposeData();

            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 fastAnimForQuickie, "RJWAnimations-fastAnimForQuickie");
            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 applySemenOnAnimationOrgasm, "RJWAnimations-applySemenOnOrgasm", false);
            Scribe_Values.Look(ref soundOverride, "RJWAnimations-rjwAnimSoundOverride", true);
            Scribe_Values.Look(ref shiverIntensity, "RJWAnimations-shiverIntensity", 2f);
            //todo: save offsetsByDefName

            Scribe_Collections.Look(ref offsets, "RJWAnimations-animationOffsets");
            Scribe_Collections.Look(ref rotation, "RJWAnimations-rotationOffsets");

            //needs to be rewritten
            //probably somewhere in options?

        }

    }

    public class RJW_Animations : Mod {

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

        }

        public override void DoSettingsWindowContents(Rect inRect) {

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

            listingStandard.CheckboxLabeled("Enable Sound Override", ref AnimationSettings.soundOverride);
            listingStandard.CheckboxLabeled("Control Genital Rotation", ref AnimationSettings.controlGenitalRotation);
            listingStandard.CheckboxLabeled("Play Fast Animation for Quickie", ref AnimationSettings.fastAnimForQuickie);
            listingStandard.CheckboxLabeled("Apply Semen on Animation Orgasm", ref AnimationSettings.applySemenOnAnimationOrgasm);

            if(AnimationSettings.applySemenOnAnimationOrgasm) {
                listingStandard.Label("Recommended--turn down \"Cum on body percent\" in RJW settings to about 33%");
            }

            listingStandard.CheckboxLabeled("Enable Orgasm Quiver", ref AnimationSettings.orgasmQuiver);
            listingStandard.CheckboxLabeled("Enable Rape Shiver", ref AnimationSettings.rapeShiver);
            listingStandard.CheckboxLabeled("Enable hearts during lovin'", ref AnimationSettings.hearts);
            listingStandard.CheckboxLabeled("Play animation for nonsexual acts (handholding, makeout)", ref AnimationSettings.PlayAnimForNonsexualActs);
            listingStandard.CheckboxLabeled("Enable Animation Manager Tab", ref AnimationSettings.offsetTab);

            listingStandard.Label("Shiver/Quiver Intensity (default 2): " + AnimationSettings.shiverIntensity);
            AnimationSettings.shiverIntensity = listingStandard.Slider(AnimationSettings.shiverIntensity, 0.0f, 12f);

            listingStandard.CheckboxLabeled("Debug Mode", ref AnimationSettings.debugMode);

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

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

        }

        public override string SettingsCategory() {
            return "RJW Animation Settings";
        }
    }
}


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++)
            {
                if (anchor is Pawn pawn && pawn == participants[i])
                {

                    List<AnimationDef> allAnimationsForPawn = groupAnimationDef.GetAllAnimationsForActor(i, seed, reorder);
                    participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn);
                }
                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);

                    participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn, 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);
                participants[i].TryGetComp<CompExtendedAnimator>().PlayGroupAnimation(allAnimationsForPawn);
            }
        }

        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

            int reorder2 = 0;

            DefDatabase<GroupAnimationDef>.AllDefsListForReading.TryRandomElement((GroupAnimationDef x) =>
                x.canAnimationBeUsed(participants, out reorder2), out GroupAnimationDef result);

            reorder = reorder2;
            if (result != null) return result;

            return null;

        }
    }
}


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

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

1.1

1.2

1.3

1.4

	</supportedVersions>
	<packageId>c0ffee.rimworld.animations</packageId>
	<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! 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>1.3.7</version>
</Manifest>


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

	</v1.5>

</loadFolders>