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

namespace Patch_HatsDisplaySelection
{
	[HarmonyBefore(new string[] { "velc.HatsDisplaySelection" })]
    [HarmonyPatch(typeof(HatDisplaySelection.Patch), "Patch_PawnRenderer_RenderPawnInternal_Initialize")]
    public class Patch_HatsDisplaySelectionInitialize

    {

		public static void Prefix(PawnRenderer __instance, ref Pawn ___pawn, ref Vector3 rootLoc, ref float angle, ref Rot4 bodyFacing, ref Rot4 headFacing)
        {

			CompBodyAnimator bodyAnim = ___pawn.TryGetComp<CompBodyAnimator>();
			bodyAnim.animatePawn(ref rootLoc, ref angle, ref bodyFacing, ref headFacing);
		}

        public static void Postfix(PawnRenderer __instance)
        {
			PawnGraphicSet graphics = __instance.graphics;
			Pawn pawn = graphics.pawn;
			CompBodyAnimator bodyAnim = pawn.TryGetComp<CompBodyAnimator>();

			if (!graphics.AllResolved)
			{
				graphics.ResolveAllGraphics();
			}

			if (bodyAnim != null && bodyAnim.isAnimating && pawn.Map == Find.CurrentMap)
			{
				bodyAnim.tickGraphics(graphics);

			}
		}
    }
}


code:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
  <Operation 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="BasePawn"]/comps</xpath>
        <value>

        </value>

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

license: unknown


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

Avian Races

1514158118

Avian Races only One Faction

1582200954

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

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Cassowary"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Chicken"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Dodo"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Owl"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Parrot"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Penguin"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="Alien_Vulture"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

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


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

Epona Race

2405585786

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

					<xpath>/Defs/ThingDef[@Name="EponaCentaur_PawnBase"]</xpath>
					<value>
						<ThingDef Name="EponaCentaur_PawnBase" ParentName="BasePawn" Abstract="True">
							<statBases>
								<Mass>100</Mass>
							</statBases>
						</ThingDef>
					</value>

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


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

	<Operation Class="PatchOperationFindMod">
		<mods>

Ferian Race

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

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="AFerian"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

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


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

	<Operation Class="PatchOperationFindMod">
		<mods>

The GiantRace

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

					<xpath>Defs/AlienRace.ThingDef_AlienRace[defName="HC_Giant"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

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


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

	<Operation Class="PatchOperationFindMod">
		<mods>

Lapelli Race

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

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="ALapelli"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

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


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

Rabbie The Moonrabbit race

1837246563

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

					<xpath>/Defs/ThingDef[@Name="RB_PawnBase"]</xpath>
					<value>
						<ThingDef Name="RB_PawnBase" ParentName="BasePawn" Abstract="True">
						    <butcherProducts>
							  <RB_Braincrystal>1</RB_Braincrystal>
							</butcherProducts>
							<statBases>
							  <Mass>55</Mass>
							</statBases>
						</ThingDef>
					</value>

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


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

Rakkle the rattle snake Race mod

2208700786

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

					<xpath>/Defs/ThingDef[@Name="RS_PawnBase"]</xpath>
					<value>
						<ThingDef Name="RS_PawnBase" ParentName="BasePawn" Abstract="True">
							<statBases>
								<Mass>55</Mass>
							</statBases>
						</ThingDef>
					</value>

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


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

[1.3]Silkiera Race

2233666290

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

					<xpath>/Defs/AlienRace.ThingDef_AlienRace[defName="HAR_Silkiera"]</xpath>
					<attribute>ParentName</attribute>
					<value>BasePawn</value>

			</operations>
		</match>
	</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/Verse.ThingDef/comps</xpath>
                    <success>Always</success>
                    <nomatch Class="PatchOperationAdd">
                    <xpath>/Defs/Verse.ThingDef</xpath>
                    <value>
                        <comps />
                    </value>
                    </nomatch>

                    <xpath>/Defs/Verse.ThingDef/comps</xpath>
                    <value>

                    </value>

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

</Patch>


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("Rimworld-Animations")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Rimworld-Animations")]
[assembly: AssemblyCopyright("Copyright ©  2020")]
[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("71b05d71-67b2-4014-82cd-18c20ac0882f")]

// 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"?>
<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>{71B05D71-67B2-4014-82CD-18C20AC0882F}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Rimworld_Animations</RootNamespace>
    <AssemblyName>Rimworld-Animations</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.3\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>none</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="AlienRace">
      <HintPath>..\..\..\..\workshop\content\294100\839005762\1.3\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="RJW">
      <HintPath>..\rjw\1.3\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="Properties\AssemblyInfo.cs" />
    <Compile Include="Source\Actors\Actor.cs" />
    <Compile Include="Source\Actors\AlienRaceOffset.cs" />
    <Compile Include="Source\Actors\BodyTypeOffset.cs" />
    <Compile Include="Source\Animations\AnimationStage.cs" />
    <Compile Include="Source\Animations\Clips\BaseAnimationClip.cs" />
    <Compile Include="Source\Animations\Clips\PawnAnimationClip.cs" />
    <Compile Include="Source\Animations\Clips\ThingAnimationClip.cs" />
    <Compile Include="Source\Animations\Keyframes\Keyframe.cs" />
    <Compile Include="Source\Animations\Keyframes\PawnKeyframe.cs" />
    <Compile Include="Source\Animations\Keyframes\ThingKeyframe.cs" />
    <Compile Include="Source\AnimationUtility.cs" />
    <Compile Include="Source\Comps\CompBodyAnimator.cs" />
    <Compile Include="Source\Comps\CompProperties_BodyAnimator.cs" />
    <Compile Include="Source\Comps\CompProperties_ThingAnimator.cs" />
    <Compile Include="Source\Comps\CompThingAnimator.cs" />
    <Compile Include="Source\Defs\AnimationDef.cs" />
    <Compile Include="Source\MainTabWindows\WorldComponent_UpdateMainTab.cs" />
    <Compile Include="Source\MainTabWindows\MainTabWindow_OffsetConfigure.cs" />
    <Compile Include="Source\MainTabWindows\OffsetMainButtonDefOf.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_AlienRace.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_CSL.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_DontShaveYourHead.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_FacialAnimation.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_HatsDisplaySelection.cs" />
    <Compile Include="Source\Patches\RimworldPatches\HarmonyPatch_HeadHair.cs" />
    <Compile Include="Source\Patches\RimworldPatches\HarmonyPatch_PawnRenderer.cs" />
    <Compile Include="Source\Patches\RimworldPatches\HarmonyPatch_PawnRotation.cs" />
    <Compile Include="Source\Patches\RimworldPatches\HarmonyPatch_Pawn_DrawTracker.cs" />
    <Compile Include="Source\Patches\RimworldPatches\HarmonyPatch_SetPawnAnimatable.cs" />
    <Compile Include="Source\Patches\OtherModPatches\HarmonyPatch_ShowHairWithHats.cs" />
    <Compile Include="Source\Patches\Harmony_PatchAll.cs" />
    <Compile Include="Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_JoinInBed.cs" />
    <Compile Include="Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseInitiator.cs" />
    <Compile Include="Source\Patches\RJWPatches\HarmonyPatch_PlaySexSounds.cs" />
    <Compile Include="Source\Patches\RJWPatches\HarmonyPatch_SexTick.cs" />
    <Compile Include="Source\Patches\RJWPatches\HarmonyPatch_WorkGiverSex.cs" />
    <Compile Include="Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseReceiverLoved.cs" />
    <Compile Include="Source\Settings\AnimationSettings.cs" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="1.1\Assemblies\Rimworld-Animations.dll" />
    <Content Include="1.2\Assemblies\Rimworld-Animations.dll" />
    <Content Include="About\About.xml" />
    <Content Include="About\Manifest.xml" />
    <Content Include="Defs\AnimationDefs\Animations_Beast.xml" />
    <Content Include="Defs\AnimationDefs\Animations_Lesbian.xml" />
    <Content Include="Defs\AnimationDefs\Animations_Masturbate.xml" />
    <Content Include="Defs\AnimationDefs\Animations_Multi.xml" />
    <Content Include="Defs\AnimationDefs\Animations_vanilla.xml" />
    <Content Include="Defs\AnimationDefs\Animations_Vanilla2.xml" />
    <Content Include="Defs\MainTabDefs\MainButtonDef.xml" />
    <Content Include="Defs\SoundDefs\Sounds_Sex.xml" />
    <Content Include="LoadFolders.xml" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="1.3\Assemblies\" />
    <Folder Include="Defs\JobDefs\" />
    <Folder Include="Source\JobDrivers\" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


code:
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29905.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rimworld-Animations", "Rimworld-Animations.csproj", "{71B05D71-67B2-4014-82CD-18C20AC0882F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Patch_HatsDisplaySelection", "Patch_HatsDisplaySelection\Patch_HatsDisplaySelection.csproj", "{BA766964-1716-422D-A09E-29426F8EB9D5}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{71B05D71-67B2-4014-82CD-18C20AC0882F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{71B05D71-67B2-4014-82CD-18C20AC0882F}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{71B05D71-67B2-4014-82CD-18C20AC0882F}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{71B05D71-67B2-4014-82CD-18C20AC0882F}.Release|Any CPU.Build.0 = Release|Any CPU
		{BA766964-1716-422D-A09E-29426F8EB9D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{BA766964-1716-422D-A09E-29426F8EB9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{BA766964-1716-422D-A09E-29426F8EB9D5}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {7E01292A-D76F-4CA3-AA64-085B84ECD905}
	EndGlobalSection
EndGlobal


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;

    }
}


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 RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;

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

            participants =
                participants.OrderBy(p => p.jobs.curDriver is rjw.JobDriver_SexBaseInitiator)
                .OrderBy(p => p == sexProps.Giver)
                .OrderByDescending(p => rjw.GenderHelper.GetSex(p) == rjw.GenderHelper.Sex.futa)
                .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) {
                    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) {
                            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 (rjw.RJWSettings.DevMode) {
                                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)) {
                        Debug.Log("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;
                    }
                }
                return true;
            });
            List<AnimationDef> optionsWithInteractionType = options.ToList().FindAll(x => x.interactionDefTypes != null && x.interactionDefTypes.Contains(sexProps.DictionaryKey));
            if (optionsWithInteractionType.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for interaction type " + sexProps.DictionaryKey.defName + "...");
                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;
            }

            CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();

            if (pawnAnimator == null || !pawnAnimator.isAnimating) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, drawNow);
            } else {
                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;

            CompBodyAnimator anim = pawn.TryGetComp<CompBodyAnimator>();
            if (anim.isAnimating)
            {
                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;
            }

            CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();

            if (pawnAnimator == null || !pawnAnimator.isAnimating || portrait) {
                GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, portrait);
            }
            else {
                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.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rimworld_Animations {
    public class AnimationStage
    {
        public string stageName;
        public int stageIndex;
        public int playTimeTicks = 0;
        public bool isLooping;
        public List<BaseAnimationClip> animationClips;

        public void initialize() {
            foreach (BaseAnimationClip clip in animationClips) {
                clip.buildSimpleCurves();
                //select playTimeTicks as longest playtime of all the animations
                if(clip.duration > playTimeTicks) {
                    playTimeTicks = clip.duration;
                }
            }
        }
    }
}


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

namespace Rimworld_Animations {
    public abstract class BaseAnimationClip
    {
        public List<ThingDef> types; //types of participants
        public int duration;
        public abstract void buildSimpleCurves();
        public string soundDef = null; //for playing sounds
        public int actor;

    }
}


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

namespace Rimworld_Animations {
    public class PawnAnimationClip : BaseAnimationClip {

        public List<PawnKeyframe> keyframes;
        public AltitudeLayer layer = AltitudeLayer.Pawn;

        public Dictionary<int, string> SoundEffects = new Dictionary<int, string>();
        public Dictionary<int, bool> quiver = new Dictionary<int, bool>();
        public SimpleCurve GenitalAngle = new SimpleCurve();
        public SimpleCurve BodyAngle = new SimpleCurve();
        public SimpleCurve HeadAngle = new SimpleCurve();
        public SimpleCurve HeadBob = new SimpleCurve();
        public SimpleCurve BodyOffsetX = new SimpleCurve();
        public SimpleCurve BodyOffsetZ = new SimpleCurve();
        public SimpleCurve HeadFacing = new SimpleCurve();
        public SimpleCurve BodyFacing = new SimpleCurve();

        public override void buildSimpleCurves() {

            int duration = 0;
            //getting the length of the whole clip
            foreach(PawnKeyframe frame in keyframes) {
                duration += frame.tickDuration;
            }

            //guarantees loops don't get cut off mid-anim
            this.duration = duration;

            int keyframePosition = 0;
            foreach (PawnKeyframe frame in keyframes) {

                if (frame.atTick.HasValue) {
                    if (frame.bodyAngle.HasValue)
                        BodyAngle.Add((float)frame.atTick / (float)duration, frame.bodyAngle.Value, true);

                    if (frame.headAngle.HasValue)
                        HeadAngle.Add((float)frame.atTick / (float)duration, frame.headAngle.Value, true);

                    if (frame.bodyOffsetX.HasValue)
                        BodyOffsetX.Add((float)frame.atTick / (float)duration, frame.bodyOffsetX.Value, true);

                    if (frame.bodyOffsetZ.HasValue)
                        BodyOffsetZ.Add((float)frame.atTick / (float)duration, frame.bodyOffsetZ.Value, true);

                    if (frame.headFacing.HasValue)
                        HeadFacing.Add((float)frame.atTick / (float)duration, frame.headFacing.Value, true);

                    if (frame.bodyFacing.HasValue)
                        BodyFacing.Add((float)frame.atTick / (float)duration, frame.bodyFacing.Value, true);

                    if (frame.headBob.HasValue)
                        HeadBob.Add((float)frame.atTick / (float)duration, frame.headBob.Value, true);

                    if (frame.genitalAngle.HasValue)
                        GenitalAngle.Add((float)frame.atTick / (float)duration, frame.genitalAngle.Value, true);

                    if (frame.soundEffect != null) {
                        SoundEffects.Add((int)frame.atTick, frame.soundEffect);
                    }

                }
                else {
                    if (frame.bodyAngle.HasValue)
                        BodyAngle.Add((float)keyframePosition / (float)duration, frame.bodyAngle.Value, true);

                    if (frame.headAngle.HasValue)
                        HeadAngle.Add((float)keyframePosition / (float)duration, frame.headAngle.Value, true);

                    if (frame.bodyOffsetX.HasValue)
                        BodyOffsetX.Add((float)keyframePosition / (float)duration, frame.bodyOffsetX.Value, true);

                    if (frame.bodyOffsetZ.HasValue)
                        BodyOffsetZ.Add((float)keyframePosition / (float)duration, frame.bodyOffsetZ.Value, true);

                    if (frame.headFacing.HasValue)
                        HeadFacing.Add((float)keyframePosition / (float)duration, frame.headFacing.Value, true);

                    if (frame.bodyFacing.HasValue)
                        BodyFacing.Add((float)keyframePosition / (float)duration, frame.bodyFacing.Value, true);

                    if (frame.headBob.HasValue)
                        HeadBob.Add((float)keyframePosition / (float)duration, frame.headBob.Value, true);

                    if (frame.genitalAngle.HasValue)
                        GenitalAngle.Add((float)keyframePosition / (float)duration, frame.genitalAngle.Value, true);

                    if (frame.soundEffect != null) {
                        SoundEffects.Add(keyframePosition, frame.soundEffect);
                    }

                    if(frame.tickDuration != 1 && frame.quiver.HasValue) {

                        quiver.Add(keyframePosition, true);
                        quiver.Add(keyframePosition + frame.tickDuration - 1, false);
                    }
                    keyframePosition += frame.tickDuration;

                }

            }

        }

    }
}


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

namespace Rimworld_Animations {
    public class ThingAnimationClip : BaseAnimationClip
    {
        public List<ThingKeyframe> keyframes;

        public override void buildSimpleCurves() {

        }
    }
}


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

namespace Rimworld_Animations {
    public abstract class Keyframe
    {
        public int tickDuration = 1;

    }
}


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

namespace Rimworld_Animations {
    public class PawnKeyframe : Keyframe
    {
        public float? bodyAngle;
        public float? headAngle;

        public float? genitalAngle;

        public float? bodyOffsetZ;
        public float? bodyOffsetX;

        public float? headBob;
        //todo: add headOffsets l/r?

        public int? bodyFacing;
        public int? headFacing;

        public string soundEffect;
        public bool? quiver;

        public float? atTick;
    }
}


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

namespace Rimworld_Animations {
    public class ThingKeyframe : Keyframe
    {
    }
}


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

namespace Rimworld_Animations {
    public class CompBodyAnimator : ThingComp
    {
        public Pawn pawn => base.parent as Pawn;
        public PawnGraphicSet Graphics;

        //public CompProperties_BodyAnimator Props => (CompProperties_BodyAnimator)(object)base.props;

        public bool isAnimating {
            get {
                return Animating;
            }
            set {
                Animating = value;

                if (value == true) {
                    SexUtility.DrawNude(pawn);
                } else {
                    pawn.Drawer.renderer.graphics.ResolveAllGraphics();
                    actorsInCurrentAnimation = null;
                }

                PortraitsCache.SetDirty(pawn);
            }
        }
        private bool Animating = false;
        private bool mirror = false, quiver = false, shiver = false;
        private int actor;

        private int lastDrawFrame = -1;

        private int animTicks = 0, stageTicks = 0, clipTicks = 0;
        private int curStage = 0;
        private float clipPercent = 0;

        public Vector3 anchor = Vector3.zero, deltaPos = Vector3.zero, headBob = Vector3.zero;
        public float bodyAngle = 0, headAngle = 0, genitalAngle = 0;
        public Rot4 headFacing = Rot4.North, bodyFacing = Rot4.North;

        public List<Pawn> actorsInCurrentAnimation;

        public bool controlGenitalAngle = false;

        private AnimationDef anim;
        private AnimationStage stage {
            get
            {
                return anim.animationStages[curStage];
            }

        }
        private PawnAnimationClip clip => (PawnAnimationClip)stage.animationClips[actor];

        public bool Mirror {
            get {
                return mirror;
            }
        }

        public void setAnchor(IntVec3 pos)
        {
            anchor = pos.ToVector3Shifted();
        }
        public void setAnchor(Thing thing) {

            //center on bed
            if(thing is Building_Bed) {
                anchor = thing.Position.ToVector3();
                if (((Building_Bed)thing).SleepingSlotsCount == 2) {
                    if (thing.Rotation.AsInt == 0) {
                        anchor.x += 1;
                        anchor.z += 1;
                    }
                    else if (thing.Rotation.AsInt == 1) {
                        anchor.x += 1;
                    }
                    else if(thing.Rotation.AsInt == 3) {
                        anchor.z += 1;
                    }

                }
                else {
                    if(thing.Rotation.AsInt == 0) {
                        anchor.x += 0.5f;
                        anchor.z += 1f;
                    }
                    else if(thing.Rotation.AsInt == 1) {
                        anchor.x += 1f;
                        anchor.z += 0.5f;
                    }
                    else if(thing.Rotation.AsInt == 2) {
                        anchor.x += 0.5f;
                    } else {
                        anchor.z += 0.5f;
                    }
                }
            }
            else {
                anchor = thing.Position.ToVector3Shifted();
            }
        }
        public void StartAnimation(AnimationDef anim, List<Pawn> actors, int actor, bool mirror = false, bool shiver = false, bool fastAnimForQuickie = false) {

            actorsInCurrentAnimation = actors;
            AlienRaceOffset raceOffset = anim?.actors[actor]?.raceOffsets?.Find(x => x.defName == pawn.def.defName);

            if (raceOffset != null) {
                anchor.x += mirror ? raceOffset.offset.x * -1f : raceOffset.offset.x;
                anchor.z += raceOffset.offset.y;
            }

            //change the offset based on pawn body type
            if(pawn?.story?.bodyType != null) {
                if (pawn.story.bodyType == BodyTypeDefOf.Fat && anim?.actors[actor]?.bodyTypeOffset?.Fat != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Fat.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Fat.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Female && anim?.actors[actor]?.bodyTypeOffset?.Female != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Female.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Female.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Male && anim?.actors[actor]?.bodyTypeOffset?.Male != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Male.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Male.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Thin && anim?.actors[actor]?.bodyTypeOffset?.Thin != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Thin.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Thin.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Hulk && anim?.actors[actor]?.bodyTypeOffset?.Hulk != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Hulk.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Hulk.Value.y;
                }
            }

            pawn.jobs.posture = PawnPosture.Standing;

            this.actor = actor;
            this.anim = anim;
            this.mirror = mirror;
            if(fastAnimForQuickie)
            {
                curStage = 1;
                animTicks = anim.animationStages[0].playTimeTicks;
            } else
            {
                curStage = 0;
                animTicks = 0;
            }

            stageTicks = 0;
            clipTicks = 0;

            quiver = false;
            this.shiver = shiver && AnimationSettings.rapeShiver;

            controlGenitalAngle = anim.actors[actor].controlGenitalAngle;

            isAnimating = true;
            //tick once for initialization
            tickAnim();

        }

        public override void CompTick() {

            base.CompTick();

            if(isAnimating) {

                GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);

                if (pawn.Dead || pawn?.jobs?.curDriver == null || (pawn?.jobs?.curDriver != null && !(pawn?.jobs?.curDriver is rjw.JobDriver_Sex))) {
                    isAnimating = false;
                }
                else {
                    tickAnim();
                }
            }
        }
        public void animatePawnBody(ref Vector3 rootLoc, ref float angle, ref Rot4 bodyFacing) {

            if(!isAnimating) {
                return;
            }
            rootLoc = anchor + deltaPos;
            angle = bodyAngle;
            bodyFacing = this.bodyFacing;

        }

        public Rot4 AnimateHeadFacing()
        {
            return this.headFacing;
        }

        public void tickGraphics(PawnGraphicSet graphics) {
            this.Graphics = graphics;
        }

        public void tickAnim() {

            if (!isAnimating) return;

            if (anim == null) {
                isAnimating = false;
                return;
            }

            animTicks++;

            if (animTicks < anim.animationTimeTicks) {
                tickStage();
            } else {

                if(LoopNeverending())
                {
                    ResetOnLoop();
                } else
                {
                    isAnimating = false;
                }

            }

        }

        public void tickStage()
        {

            if(stage == null)
            {
                isAnimating = false;
                return;
            }

            stageTicks++;

            if(stageTicks >= stage.playTimeTicks) {

                curStage++;

                stageTicks = 0;
                clipTicks = 0;
                clipPercent = 0;
            }

            if(curStage >= anim.animationStages.Count) {
                if (LoopNeverending())
                {
                    ResetOnLoop();
                }
                else
                {
                    isAnimating = false;
                    pawn.jobs.curDriver.ReadyForNextToil();
                }

            } else {
                tickClip();
            }

        }

        public void tickClip() {

            clipTicks++;

            //play sound effect
            if(rjw.RJWSettings.sounds_enabled && clip.SoundEffects.ContainsKey(clipTicks) && AnimationSettings.soundOverride) {

                SoundInfo sound = new TargetInfo(pawn.Position, pawn.Map);
                string soundEffectName = clip.SoundEffects[clipTicks];

                if ((pawn.jobs.curDriver as JobDriver_Sex).isAnimalOnAnimal)
                {
                    sound.volumeFactor *= RJWSettings.sounds_animal_on_animal_volume;
                }

                if(soundEffectName.StartsWith("Voiceline_"))
                {
                    sound.volumeFactor *= RJWSettings.sounds_voice_volume;
                }

                if (clip.SoundEffects[clipTicks] == "Cum") {

                    sound.volumeFactor *= RJWSettings.sounds_cum_volume;
                    considerApplyingSemen();

                } else
                {
                    sound.volumeFactor *= RJWSettings.sounds_sex_volume;
                }

                SoundDef.Named(soundEffectName).PlayOneShot(sound);

            }
            if(AnimationSettings.orgasmQuiver && clip.quiver.ContainsKey(clipTicks)) {
                quiver = clip.quiver[clipTicks];
            }

            //loop animation if possible
            if (clipPercent >= 1 && stage.isLooping) {
                clipTicks = 1;//warning: don't set to zero or else calculations go wrong
            }
            clipPercent = (float)clipTicks / (float)clip.duration;

            calculateDrawValues();
        }

        public void considerApplyingSemen()
        {
            if(AnimationSettings.applySemenOnAnimationOrgasm && (pawn?.jobs?.curDriver is JobDriver_Sex))
            {
                Pawn partner = (pawn.jobs.curDriver as JobDriver_Sex)?.Partner;

                if (anim.sexTypes.Contains((pawn.jobs.curDriver as JobDriver_Sex).sexType))
                {
                    SemenHelper.calculateAndApplySemen(pawn, partner, (pawn.jobs.curDriver as JobDriver_Sex).sexType);
                }
            }
        }

        public void calculateDrawValues() {

            /*if(Find.TickManager.TickRateMultiplier > 1 && (lastDrawFrame + 1 >= RealTime.frameCount || RealTime.deltaTime < 0.05f)) {
                return;
            }*/

            deltaPos = new Vector3(clip.BodyOffsetX.Evaluate(clipPercent) * (mirror ? -1 : 1), clip.layer.AltitudeFor(), clip.BodyOffsetZ.Evaluate(clipPercent));

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

            if (AnimationSettings.offsets != null && AnimationSettings.offsets.ContainsKey(CurrentAnimation.defName + pawn.def.defName + bodyTypeDef + ActorIndex)) {
                deltaPos.x += AnimationSettings.offsets[CurrentAnimation.defName + pawn.def.defName + bodyTypeDef + ActorIndex].x * (mirror ? -1 : 1);
                deltaPos.z += AnimationSettings.offsets[CurrentAnimation.defName + pawn.def.defName + bodyTypeDef + ActorIndex].y;
            }

            bodyAngle = (clip.BodyAngle.Evaluate(clipPercent) + (quiver || shiver ? ((Rand.Value * AnimationSettings.shiverIntensity) - (AnimationSettings.shiverIntensity / 2f)) : 0f)) * (mirror ? -1 : 1);
            headAngle = clip.HeadAngle.Evaluate(clipPercent) * (mirror ? -1 : 1);

            if (controlGenitalAngle) {
                genitalAngle = clip.GenitalAngle.Evaluate(clipPercent) * (mirror ? -1 : 1);
            }

            if (AnimationSettings.rotation != null && AnimationSettings.rotation.ContainsKey(CurrentAnimation.defName + pawn.def.defName + bodyTypeDef + ActorIndex)) {
                float offsetRotation = AnimationSettings.rotation[CurrentAnimation.defName + pawn.def.defName + bodyTypeDef + ActorIndex] * (Mirror ? -1 : 1);
                genitalAngle += offsetRotation;
                bodyAngle += offsetRotation;
                headAngle += offsetRotation;
            }

            //don't go past 360 or less than 0
            if (bodyAngle < 0) bodyAngle = 360 - ((-1f*bodyAngle) % 360);
            if (bodyAngle > 360) bodyAngle %= 360;

            if (headAngle < 0) headAngle = 360 - ((-1f * headAngle) % 360);
            if (headAngle > 360) headAngle %= 360;

            if (genitalAngle < 0) genitalAngle = 360 - ((-1f * genitalAngle) % 360);
            if (genitalAngle > 360) genitalAngle %= 360;

            bodyFacing = mirror ? new Rot4((int)clip.BodyFacing.Evaluate(clipPercent)).Opposite : new Rot4((int)clip.BodyFacing.Evaluate(clipPercent));

            bodyFacing = new Rot4((int)clip.BodyFacing.Evaluate(clipPercent));
            if(bodyFacing.IsHorizontal && mirror) {
                bodyFacing = bodyFacing.Opposite;
            }

            headFacing = new Rot4((int)clip.HeadFacing.Evaluate(clipPercent));
            if(headFacing.IsHorizontal && mirror) {
                headFacing = headFacing.Opposite;
            }
            headBob = new Vector3(0, 0, clip.HeadBob.Evaluate(clipPercent));

            lastDrawFrame = RealTime.frameCount;

        }

        public Vector3 getPawnHeadPosition() {

            Vector3 headPos = anchor + deltaPos + Quaternion.AngleAxis(bodyAngle, Vector3.up) * (pawn.Drawer.renderer.BaseHeadOffsetAt(headFacing) + headBob);

            return headPos;

        }

        public Vector3 getPawnHeadOffset()
        {
            return Quaternion.AngleAxis(bodyAngle, Vector3.up) * (pawn.Drawer.renderer.BaseHeadOffsetAt(headFacing) + headBob);

        }

        public AnimationDef CurrentAnimation {
            get {
                return anim;
            }
        }

        public int ActorIndex {
            get {
                return actor;
            }
        }

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

            Scribe_Defs.Look(ref anim, "RJWAnimations-Anim");

            Scribe_Values.Look(ref animTicks, "RJWAnimations-animTicks", 1);
            Scribe_Values.Look(ref stageTicks, "RJWAnimations-stageTicks", 1);
            Scribe_Values.Look(ref clipTicks, "RJWAnimations-clipTicks", 1);
            Scribe_Values.Look(ref clipPercent, "RJWAnimations-clipPercent", 1);
            Scribe_Values.Look(ref mirror, "RJWAnimations-mirror");

            Scribe_Values.Look(ref curStage, "RJWAnimations-curStage", 0);
            Scribe_Values.Look(ref actor, "RJWAnimations-actor");

            Scribe_Values.Look(ref anchor, "RJWAnimations-anchor");
            Scribe_Values.Look(ref deltaPos, "RJWAnimations-deltaPos");
            Scribe_Values.Look(ref headBob, "RJWAnimations-headBob");
            Scribe_Values.Look(ref bodyAngle, "RJWAnimations-bodyAngle");
            Scribe_Values.Look(ref headAngle, "RJWAnimations-headAngle");

            Scribe_Values.Look(ref genitalAngle, "RJWAnimations-GenitalAngle");
            Scribe_Values.Look(ref controlGenitalAngle, "RJWAnimations-controlGenitalAngle");

            Scribe_Values.Look(ref headFacing, "RJWAnimations-headFacing");
            Scribe_Values.Look(ref headFacing, "RJWAnimations-bodyFacing");

            Scribe_Values.Look(ref quiver, "RJWAnimations-orgasmQuiver");
        }

        public void shiftActorPositionAndRestartAnimation() {
            actor = (actor == anim.actors.Count - 1 ? 0 : actor + 1);

            if (pawn?.story?.bodyType != null) {
                if (pawn.story.bodyType == BodyTypeDefOf.Fat && anim?.actors[actor]?.bodyTypeOffset?.Fat != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Fat.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Fat.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Female && anim?.actors[actor]?.bodyTypeOffset?.Female != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Female.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Female.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Male && anim?.actors[actor]?.bodyTypeOffset?.Male != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Male.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Male.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Thin && anim?.actors[actor]?.bodyTypeOffset?.Thin != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Thin.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Thin.Value.y;
                }
                else if (pawn.story.bodyType == BodyTypeDefOf.Hulk && anim?.actors[actor]?.bodyTypeOffset?.Hulk != null) {
                    anchor.x += anim.actors[actor].bodyTypeOffset.Hulk.Value.x * (mirror ? -1f : 1f);
                    anchor.z += anim.actors[actor].bodyTypeOffset.Hulk.Value.y;
                }
            }

            curStage = 0;
            animTicks = 0;
            stageTicks = 0;
            clipTicks = 0;

            controlGenitalAngle = anim.actors[actor].controlGenitalAngle;

            tickAnim();
        }

        public bool LoopNeverending()
        {
            if(pawn?.jobs?.curDriver != null &&
                (pawn.jobs.curDriver is JobDriver_Sex) && (pawn.jobs.curDriver as JobDriver_Sex).neverendingsex ||
                (pawn.jobs.curDriver is JobDriver_SexBaseReciever) && (pawn.jobs.curDriver as JobDriver_Sex).Partner?.jobs?.curDriver != null && ((pawn.jobs.curDriver as JobDriver_Sex).Partner.jobs.curDriver as JobDriver_Sex).neverendingsex)
            {
                return true;
            }

            return false;
        }

        public void ResetOnLoop()
        {
            curStage = 1;
            animTicks = 0;
            stageTicks = 0;
            clipTicks = 0;

            tickAnim();
        }
    }
}


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

namespace Rimworld_Animations {
    class CompProperties_BodyAnimator : CompProperties
    {
        public CompProperties_BodyAnimator() {

            base.compClass = typeof(CompBodyAnimator);
        }
    }
}


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

namespace Rimworld_Animations {
    class CompProperties_ThingAnimator
    {
    }
}


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

namespace Rimworld_Animations {
    class CompThingAnimator
    {
    }
}


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

namespace Rimworld_Animations {
    public class AnimationDef : Def
    {
        public List<AnimationStage> animationStages;
        public List<Actor> actors;
        public int animationTimeTicks = 0; //do not set manually
        public bool sounds = false;
        public List<rjw.xxx.rjwSextype> sexTypes = null;
        public List<InteractionDef> interactionDefTypes = null;

        public override void PostLoad() {
            base.PostLoad();
            foreach(AnimationStage stage in animationStages) {
                stage.initialize();
                animationTimeTicks += stage.playTimeTicks;
            }
        }
    }
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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) {

                Pawn curPawn = Find.Selector.SingleSelectedThing as Pawn;

                if (curPawn.TryGetComp<CompBodyAnimator>().isAnimating) {

                    AnimationDef def = curPawn.TryGetComp<CompBodyAnimator>().CurrentAnimation;
                    int ActorIndex = curPawn.TryGetComp<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");
                    }

                    bool mirrored = curPawn.TryGetComp<CompBodyAnimator>().Mirror;

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

                    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("rimworldanim");
            val.PatchAll(Assembly.GetExecutingAssembly());

        }
    }
}


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

namespace Rimworld_Animations {
    public static class HarmonyPatch_HatsDisplaySelection {

        public static void PatchHatsDisplaySelectionArgs() {
            (new Harmony("rjw")).Patch(AccessTools.Method(AccessTools.TypeByName("HatDisplaySelection.Patch"), "DrawHatCEWithHair"),
                transpiler: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_HatsDisplaySelection), "ReplaceDrawMeshOrLaterWithAnimate")));

            (new Harmony("rjw")).Patch(AccessTools.Method(AccessTools.TypeByName("HatDisplaySelection.Patch"), "DrawHatWithHair"),
                transpiler: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_HatsDisplaySelection), "ReplaceDrawMeshOrLaterWithAnimate")));

            (new Harmony("rjw")).Patch(AccessTools.Method(AccessTools.TypeByName("HatDisplaySelection.Patch"), "DrawHeadApparelWithHair"),
                prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_HatsDisplaySelection), "PrefixPatchForDrawHeadApparelWithHair")));

        }

        public static void PrefixPatchForDrawHeadApparelWithHair(PawnRenderer renderer, ref Vector3 rootLoc, ref float angle, bool renderBody, ref Rot4 bodyFacing, ref Rot4 headFacing, RotDrawMode bodyDrawType, bool portrait, bool headStump, bool invisible)
        {
            PawnGraphicSet graphics = renderer.graphics;
            Pawn pawn = graphics.pawn;
            CompBodyAnimator bodyAnim = pawn.TryGetComp<CompBodyAnimator>();

            if (!graphics.AllResolved)
            {
                graphics.ResolveAllGraphics();
            }

            if (bodyAnim != null && bodyAnim.isAnimating && !portrait && pawn.Map == Find.CurrentMap)
            {
                bodyAnim.tickGraphics(graphics);
                bodyAnim.animatePawn(ref rootLoc, ref angle, ref bodyFacing, ref headFacing);

            }
        }

        public static IEnumerable<CodeInstruction> ReplaceDrawMeshOrLaterWithAnimate(IEnumerable<CodeInstruction> instructions) {

            MethodInfo drawMeshNowOrLater = AccessTools.Method(typeof(GenDraw), "DrawMeshNowOrLater");
            List<CodeInstruction> codes = instructions.ToList();
            for (int i = 0; i < instructions.Count(); i++) {

                if (codes[i].

(drawMeshNowOrLater)) {

                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(AccessTools.TypeByName("HatDisplaySelection.Patch"), "pawn"));
                    yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationUtility), nameof(AnimationUtility.RenderPawnHeadMeshInAnimation), new Type[] { typeof(Mesh), typeof(Vector3), typeof(Quaternion), typeof(Material), typeof(bool), typeof(Pawn) }));

                }
                else {
                    yield return codes[i];
                }

            }

        }

    }
}
*/


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

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(xxx), "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 && !xxx.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;

            toils[3].AddPreTickAction(() =>
            {
                if (!__instance.Partner.TryGetComp<CompBodyAnimator>().isAnimating)
                {
                    __instance.pawn.TryGetComp<CompBodyAnimator>().isAnimating = false;
                }
            });

            __result = toils.AsEnumerable();

        }

    }
}


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

			Pawn pawn = __instance.pawn;

			Building_Bed bed = __instance.Bed;

			if ((__instance.Target as Pawn)?.jobs?.curDriver is JobDriver_SexBaseReciever) {

				Pawn Target = __instance.Target as Pawn;

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

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

				if (bed != null) {
                    RerollAnimations(Target, out AnimationTimeTicks, bed as Thing, __instance.sexType, quickie, sexProps: __instance.Sexprops);
                }
				else {
					RerollAnimations(Target, out AnimationTimeTicks, sexType: __instance.sexType, fastAnimForQuickie: quickie, sexProps: __instance.Sexprops);
				}

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

			}
		}

		public static void RerollAnimations(Pawn pawn, out int AnimationTimeTicks, Thing bed = null, xxx.rjwSextype sexType = xxx.rjwSextype.None, bool fastAnimForQuickie = false, rjw.SexProps sexProps = null) {

			AnimationTimeTicks = 0;

			if(pawn == null || !(pawn.jobs?.curDriver is JobDriver_SexBaseReciever)) {
				Log.Error("Error: Tried to reroll animations when pawn isn't sexing");
				return;
			}

			List<Pawn> pawnsToAnimate = (pawn.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList();

			if (!pawnsToAnimate.Contains(pawn)) {
				pawnsToAnimate = pawnsToAnimate.Append(pawn).ToList();
			}

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

				if(pawnsToAnimate[i].TryGetComp<CompBodyAnimator>() == null) {
					Log.Error("Error: " + pawnsToAnimate[i].Name + " of race " + pawnsToAnimate[i].def.defName + " does not have CompBodyAnimator attached!");
					break;
				}
			}

			AnimationDef anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate, sexType, sexProps);

			if (anim != null) {

				bool mirror = GenTicks.TicksGame % 2 == 0;

				IntVec3 pos = pawn.Position;

				for (int i = 0; i < anim.actors.Count; i++)
				{
					pawnsToAnimate[i].TryGetComp<CompBodyAnimator>().isAnimating = false;
				}

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

					if (bed != null)
						pawnsToAnimate[i].TryGetComp<CompBodyAnimator>().setAnchor(bed);
					else {

						pawnsToAnimate[i].TryGetComp<CompBodyAnimator>().setAnchor(pos);
					}

					bool shiver = pawnsToAnimate[i].jobs.curDriver is JobDriver_SexBaseRecieverRaped;
					pawnsToAnimate[i].TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, i, mirror, shiver, fastAnimForQuickie);

					int animTicks = anim.animationTimeTicks - (fastAnimForQuickie ? anim.animationStages[0].playTimeTicks : 0);
					(pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).ticks_left = animTicks;
					(pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).sex_ticks = animTicks;
					(pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).duration = animTicks;

					AnimationTimeTicks = animTicks;

					if(!AnimationSettings.hearts) {
						(pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).ticks_between_hearts = Int32.MaxValue;
					}

				}
			}
			else {
				Log.Message("No animation found");

				/*

				//if pawn isn't already animating,
				if (!pawn.TryGetComp<CompBodyAnimator>().isAnimating) {
					(pawn.jobs.curDriver as JobDriver_SexBaseReciever).increase_time(duration);
					//they'll just do the thrusting anim
				}

				*/
			}
		}
	}

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

		public static void Postfix(ref JobDriver_SexBaseInitiator __instance) {

			if ((__instance.Target as Pawn)?.jobs?.curDriver is JobDriver_SexBaseReciever) {
				if (__instance.pawn.TryGetComp<CompBodyAnimator>().isAnimating) {

					List<Pawn> parteners = ((__instance.Target as Pawn)?.jobs.curDriver as JobDriver_SexBaseReciever).parteners;

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

						//prevents pawns who started a new anim from stopping their new anim
						if (!((parteners[i].jobs.curDriver as JobDriver_SexBaseInitiator) != null && (parteners[i].jobs.curDriver as JobDriver_SexBaseInitiator).Target != __instance.pawn))
							parteners[i].TryGetComp<CompBodyAnimator>().isAnimating = false;

					}

					__instance.Target.TryGetComp<CompBodyAnimator>().isAnimating = false;

					if (xxx.is_human((__instance.Target as Pawn))) {
						(__instance.Target as Pawn)?.Drawer.renderer.graphics.ResolveApparelGraphics();
						PortraitsCache.SetDirty((__instance.Target as Pawn));
					}
				}

				((__instance.Target as Pawn)?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Remove(__instance.pawn);

			}

			if (xxx.is_human(__instance.pawn)) {
				__instance.pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
				PortraitsCache.SetDirty(__instance.pawn);
			}
		}
	}
}


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

namespace Rimworld_Animations
{

    [HarmonyPatch(typeof(JobDriver_SexBaseRecieverLoved), "MakeSexToil")]
    public static class HarmonyPatch_JobDriver_SexBaseReceiverLoved
    {

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

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

                    ins.RemoveRange(i, 14);

                }

                else
                {
                    yield return ins[i];
                }
            }

        }

    }
}


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

namespace Rimworld_Animations
{
    [HarmonyPatch(typeof(PawnRenderer), "DrawHeadHair")]
    public static class HarmonyPatch_HeadHair
    {
        public static void Prefix(ref Vector3 headOffset, ref float angle)
        {

        }

    }
}


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

namespace Rimworld_Animations {

	[HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
		{
					typeof(Vector3),
					typeof(float),
					typeof(bool),
					typeof(Rot4),
					typeof(RotDrawMode),
					typeof(PawnRenderFlags)
		}
	)]
	public static class HarmonyPatch_PawnRenderer
	{

		[HarmonyBefore(new string[] { "showhair.kv.rw", "erdelf.HumanoidAlienRaces", "Nals.FacialAnimation" })]
		public static void Prefix(PawnRenderer __instance, ref Vector3 rootLoc, ref float angle, bool renderBody, ref Rot4 bodyFacing, RotDrawMode bodyDrawType, PawnRenderFlags flags)
		{

			if (flags.FlagSet(PawnRenderFlags.Portrait)) return;

			PawnGraphicSet graphics = __instance.graphics;
			Pawn pawn = graphics.pawn;
			CompBodyAnimator bodyAnim = pawn.TryGetComp<CompBodyAnimator>();

			if (bodyAnim != null && bodyAnim.isAnimating && pawn.Map == Find.CurrentMap)
			{
				bodyAnim.animatePawnBody(ref rootLoc, ref angle, ref bodyFacing);

			}

		}

		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> ins = instructions.ToList();

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

				if (i - 3 >= 0 && ins[i - 3].opcode == OpCodes.Call && ins[i - 3].operand != null && ins[i - 3].OperandIs(AccessTools.DeclaredMethod(typeof(PawnRenderer), "BaseHeadOffsetAt")))
                {

					yield return new CodeInstruction(OpCodes.Ldloca, (object)0);
					yield return new CodeInstruction(OpCodes.Ldloca, (object)7);
					yield return new CodeInstruction(OpCodes.Ldloca, (object)6);
					yield return new CodeInstruction(OpCodes.Ldarga, (object)2);
					yield return new CodeInstruction(OpCodes.Ldarg_0);
					yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
					yield return new CodeInstruction(OpCodes.Ldarg, (object)6);
					yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationUtility), "AdjustHead"));
					yield return ins[i];
					//headFacing equals true
                }

				else
                {
					yield return ins[i];
				}

			}

		}

	}

}


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

namespace Rimworld_Animations {
    [HarmonyPatch(typeof(Thing), "Rotation", MethodType.Getter)]
    public static class HarmonyPatch_PawnRotation {

        public static bool Prefix(Thing __instance, ref Rot4 __result) {

            if(!(__instance is Pawn) || (__instance as Pawn)?.TryGetComp<CompBodyAnimator>() == null || !(__instance as Pawn).TryGetComp<CompBodyAnimator>().isAnimating) {
                return true;
            }

            Pawn pawn = (__instance as Pawn);
            __result = pawn.TryGetComp<CompBodyAnimator>().bodyFacing;

            return false;

        }

    }

}


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

namespace Rimworld_Animations {

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

            CompBodyAnimator bodyAnim = ___pawn.TryGetComp<CompBodyAnimator>();

            if (bodyAnim != null && bodyAnim.isAnimating) {
                __result = ___pawn.TryGetComp<CompBodyAnimator>().anchor + ___pawn.TryGetComp<CompBodyAnimator>().deltaPos;

                return false;
            }
            return true;
        }
    }
}


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

namespace Rimworld_Animations
{
	[HarmonyPatch(typeof(PawnRenderer), "RenderPawnAt")]
	public static class PawnRenderer_RenderPawnAt_Patch
	{
		static bool ClearCache(Pawn pawn)
		{
			return pawn.IsInvisible() ||  pawn.TryGetComp<CompBodyAnimator>().isAnimating;
		}

		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			var list = instructions.ToList();

			foreach (CodeInstruction i in instructions)
			{
				if (i.OperandIs(AccessTools.Method(typeof(PawnUtility), "IsInvisible")))
				{

					yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PawnRenderer_RenderPawnAt_Patch), "ClearCache"));
				}
				else
				{
					yield return i;
				}
			}
		}
	}

}


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), "PlaySexSound")]
    class HarmonyPatch_PlaySexSounds
    {
        public static bool Prefix(JobDriver_Sex __instance)
        {
            if (__instance.pawn.TryGetComp<CompBodyAnimator>().isAnimating)
            {
                return false;
            }

            return true;
        }
    }
}


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.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;
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;
        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-sheartsOnLovin");
            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("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:
*.bat	text eol=crlf
*.tw	text eol=lf
*.sh	text eol=lf
*.py	text eol=lf
*.txt	text eol=lf
compile	text eol=lf
compile-git	text eol=lf
sanityCheck	text eol=lf

repo_name: deswes/fc

path: .gitattributes


code:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.swp
*.swo

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

# Start.tw
src/config/start.tw

path: .gitignore


code:
further development:
-specialized slave schools
-fortifications
-more levels for militia edict (further militarize society)
-conquering other arcologies?

Events:
-famous criminal escapes to the arcology, followed by another arcology police force

Bugs:
-sometimes troop counts breaks
-sometimes rebel numbers have fractionary parts


code:
# How to vector art

Please read the whole document before starting to work.
Some aspects are not obvious right away.

Note: This does not actually describe how to be an artist.

## TL;DR

    killall inkscape
    artTools/vector_layer_split.py artTools/vector_source.svg tw src/art/vector/layers/
    compile

## 1. Be an artist

Make changes to the vector_source.svg.
Inkscape was thoroughly tested.
Adobe Illustrator might work decently, too.

## 2. Respect the structure

While editing, keep the Layers in mind.

* In Inkscape, Layers are special "Inkscape groups".
* In Illustrator, Layers are groups with user-definded IDs.
* All Layers should have an ID that is globally unique
  (not just within their subtree).

* Please use anonymous groups only for the ease of editing. Remove all "helper" groups before finally saving the file.
* Anonymous groups can be used for continous scaling (of e.g. boobs).

* Every asset that should go into a separate file needs to be in a labelled group
  (even if that is a group with only one shape).
* There are some globally available styles defined as CSS classes (e.g. skin, hair).
  Use them if your asset should be changed upon display.
  Do not set the style directly in the object.

## 3. Normalise the document (before committing)

**THIS IS IMPORTANT**

The various editors out there (Inkscape, Illustrator) may use different variants of formatting the SVG XML.
Use

    python3 normalize_svg.py vector_source.svg

before committing to normalise the format so git will not freak out about the changed indentation.

If you use Inkscape, please use in Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default.
In case your Editor uses another path data style which cannot be changed, please contact the other artists and developers via the issue tracker to find a new common ground.

What it does:
* Formats the SVG XML according to Pythons lxml module, regardless of editor.
* Adobe Illustrator uses group IDs as layer labels.
  Inkscape however uses layer labels and a separate, auto-generated group ID.
  normalize_svg.py overwrites the group ID with the Inkscape layer label
  so they are synchronised with Inkscape layer labels.
* Inkscape copies the global style definition into the object *every time*
  the object is edited. If an object references a CSS class AND at the same time
  has a local style, the local style is removed
  so global dynamic styling is possible later on.

Note: Behaviour of Adobe Illustrator is untested.

## 4. Split the layers

For NoX original, deepurk extensions, execute

    python3 vector_layer_split.py vector_source_ndmain.svg tw ../src/art/vector/layers/

For faraen revamped art (based on NoX original)

	python3 vector_revamp_layer_split.py vector_revamp_source.svg tw ../src/art/vector_revamp/layers/

. This application reads all groups in `vector_source.svg`.
Each group is stored in a separate file in the target directory `/src/art/vector/layers/`.
The group ID sets the file name. Therefore, the group ID **must not** contain spaces or any other weird characters.

Also consider:
* A group with ID ending in _ is ignored. Use Layers ending in _ to keep overview.
* A group with ID starting with "g" (Inkscape) or "XMLID" (Illustrator) is also ignored.
* Existing files are overwritten **without enquiry**.
* The target directory is not emptied. If a file is no longer needed, you should remove it manually.
* This procedure removes global definitions. This means, SVG filters are currently not supported.

Available output formats are `svg` and `tw`.
`svg` output exists for debug reasons.
`tw` embeds the SVG data into Twine files, but removes the global style definitions so they can be set during display.

## 5. Edit the code

`/src/art/` contains Twine code which shows the assets in the story.
There are many helpful comments in `/src/art/artWidgets.tw`.
The code also generates the previously removed global style definitions on the fly and per display.

## 6. Compile the story

Use the provided compile script (Windows batch or shell) for compiling.

## 7. Advanced usage

You can define multiple CSS classes to one element, e.g. "skin torso". When procedurally generating CSS, you can then set a global "skin" style, and a different one for "torso".

You can put variable text into the image using single quotes, e.g. "'+_tattooText+'". The single quotes *break* the quoting in Twine so the content of the Twine variable `_tattooText` is shown upon display. You can even align text on paths.

An anonymous group can be used for dynamic transformation. However, finding the correct parameters for the affine transformation matrix can be cumbersome. Use any method you see fit. See `src/art/vector/Boob.tw` for one example of the result.