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

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

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

            if(interaction.HasInteractionTag(InteractionTag.Reverse))
            {
                Pawn buffer = participants[1];
                participants[1] = participants[0];
                participants[0] = buffer;
            }

            participants =
                participants.OrderBy(p => p.jobs.curDriver is rjw.JobDriver_SexBaseInitiator)
                .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.sexType.ToStringSafe()));
            if (optionsWithInteractionType.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for interaction type " + sexProps.sexType.ToStringSafe() + "...");
                return optionsWithInteractionType.RandomElement();
            }
            List<AnimationDef> optionsWithSexType = options.ToList().FindAll(x => x.sexTypes != null && x.sexTypes.Contains(sexType));
            if (optionsWithSexType.Any()) {
                if (AnimationSettings.debugMode)
                    Log.Message("Selecting animation for rjwSexType " + sexType.ToStringSafe() + "...");
                return optionsWithSexType.RandomElement();
            }

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

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

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

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

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

namespace Rimworld_Animations
{
    public class PatchOperationAddOrReplace : PatchOperationPathed
    {

        protected string key;
        private XmlContainer value;

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

    }

}

repo_name: Valianteffort/rimworld

license: unknown


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

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

1.1

1.2

1.3

1.4

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

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

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

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

			<packageId>erdelf.HumanoidAlienRaces</packageId>
			<displayName>Humanoid Alien Races 2.0</displayName>
			<steamWorkshopUrl>https://steamcommunity.com/sharedfiles/filedetails/?id=839005762</steamWorkshopUrl>

	</modDependencies>
	<loadAfter>

UnlimitedHugs.HugsLib

brrainz.harmony

erdelf.humanoidalienraces

nals.facialanimation

com.yayo.yayoAni

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

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

  </description>
</ModMetaData>


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


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

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

</LanguageData>


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

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

</LanguageData>


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

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

</LanguageData>


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

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

</LanguageData>


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

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

</LanguageData>


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

/

1.1

	</v1.1>
	<v1.2>

/

1.2

Patch_HatsDisplaySelection/1.2

	</v1.2>
	<v1.3>

/

1.3

Patch_HatsDisplaySelection/1.2

Patch_SexToysMasturbation

Patch_SexToysMasturbation/1.3

	</v1.3>
	<v1.4>

/

1.4

Patch_HumanoidAlienRaces/1.4

Patch_HatsDisplaySelection/1.2

Patch_SexToysMasturbation

Patch_SexToysMasturbation/1.4

	</v1.4>

</loadFolders>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{C76F3790-9AC0-4827-ACD5-84174238954F}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Patch_HumanoidAlienRaces</RootNamespace>
    <AssemblyName>Patch_HumanoidAlienRaces</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>1.4\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="0Harmony">
      <HintPath>..\..\..\..\..\workshop\content\294100\839005762\1.4\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="AlienRace">
      <HintPath>..\..\..\..\..\workshop\content\294100\839005762\1.4\Assemblies\AlienRace.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Rimworld-Animations">
      <HintPath>..\1.4\Assemblies\Rimworld-Animations.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\rjw\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
    <Reference Include="UnityEngine">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Source\HarmonyPatch_AlienRace.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


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

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

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

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c76f3790-9ac0-4827-acd5-84174238954f")]

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


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

namespace Rimworld_Animations {

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

		/* todo: replace jank prefix with this
		public static void Prefix_DrawAddonsFinalHook(ref Pawn pawn, ref AlienPartGenerator.BodyAddon addon, ref Rot4 rot, ref Graphic graphic, ref Vector3 offsetVector, ref float angle, ref Material mat)
        {
			CompBodyAnimator animator = pawn.TryGetComp<CompBodyAnimator>();

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

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

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

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

			if (renderFlags.FlagSet(PawnRenderFlags.Portrait) || pawn.TryGetComp<CompBodyAnimator>() == null || !pawn.TryGetComp<CompBodyAnimator>().isAnimating) return true;
			if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible)) return false;

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

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

			for (int i = 0; i < addons.Count; i++)
			{
				AlienPartGenerator.BodyAddon ba = addons[index: i];

				if (!ba.CanDrawAddon(pawn: pawn)) continue;

				bool forceDrawForBody = false;
				if (alienProps.defName.Contains("Orassan") && ba.path.ToLower().Contains("tail"))
                {
					forceDrawForBody = true;
                }
				AlienPartGenerator.RotationOffset offset = ba.defaultOffsets.GetOffset((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing);
				Vector3 a = (offset != null) ? offset.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, pawn.story.headType) : Vector3.zero;
				AlienPartGenerator.RotationOffset offset2 = ba.offsets.GetOffset((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing);
				Vector3 vector2 = a + ((offset2 != null) ? offset2.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, pawn.story.headType) : Vector3.zero);
				vector2.y = (ba.inFrontOfBody ? (0.3f + vector2.y) : (-0.3f - vector2.y));
				float num = ba.angle;
				if (((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing) == Rot4.North)
				{
					if (ba.layerInvert)
					{
						vector2.y = 0f - vector2.y;
					}
					num = 0f;
				}
				if (((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing) == Rot4.East)
				{
					num = 0f - num;
					vector2.x = 0f - vector2.x;
				}
				Graphic addonGraphic = comp.addonGraphics[i];

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

				if ((ba.drawnInBed && !forceDrawForBody) || ba.alignWithHead)
				{

					Quaternion addonRotation = Quaternion.AngleAxis(pawnAnimator.headAngle < 0 ? 360 - (360 % pawnAnimator.headAngle) : pawnAnimator.headAngle, Vector3.up);

					GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: pawnAnimator.headFacing), loc: vector + (ba.alignWithHead ? headOffset : headOffset - addonRotation * pawn.Drawer.renderer.BaseHeadOffsetAt(pawnAnimator.headFacing)) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f),
					quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * addonRotation, mat: addonGraphic.MatAt(rot: pawnAnimator.headFacing), renderFlags.FlagSet(PawnRenderFlags.DrawNow));

				}

				else
				{
					Quaternion addonRotation = Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up);
					if (AnimationSettings.controlGenitalRotation && pawnAnimator.controlGenitalAngle && ba?.hediffGraphics != null && ba.hediffGraphics.Count != 0 && ba.hediffGraphics[0]?.path != null && (ba.hediffGraphics[0].path.Contains("Penis") || ba.hediffGraphics[0].path.Contains("penis")))
					{
						GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: rotation), loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f),
						quat: Quaternion.AngleAxis(angle: pawnAnimator.genitalAngle, axis: Vector3.up), mat: addonGraphic.MatAt(rot: rotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
					}

					else
                    {
						GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: rotation), loc: vector + (ba.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f),
						quat: Quaternion.AngleAxis(angle: num, axis: Vector3.up) * addonRotation, mat: addonGraphic.MatAt(rot: rotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
					}

				}

			}

			return false;
		}
	}

	[HarmonyPatch(typeof(PawnGraphicSet), "ResolveApparelGraphics")]
	public static class HarmonyPatch_ResolveApparelGraphics
	{
		public static bool Prefix(ref Pawn ___pawn)
		{

			if (___pawn.TryGetComp<CompBodyAnimator>() != null && ___pawn.TryGetComp<CompBodyAnimator>().isAnimating)
			{
				return false;
			}
			return true;
		}
	}

	/*

    [HarmonyPatch(typeof(AlienRace.HarmonyPatches), "DrawAddons")]
    public static class HarmonyPatch_AlienRace {

		public static void RenderHeadAddonInAnimation(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow, Graphic graphic, AlienPartGenerator.BodyAddon bodyAddon, Vector3 v, Vector3 headOffset, Pawn pawn, PawnRenderFlags renderFlags, Vector3 vector, Rot4 rotation)
        {

			CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
			AlienPartGenerator.AlienComp comp = pawn.GetComp<AlienPartGenerator.AlienComp>();

			if (pawnAnimator != null && pawnAnimator.isAnimating)
            {

				if((bodyAddon.drawnInBed || bodyAddon.alignWithHead))
                {

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

					vector = vector + Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up) * pawn.Drawer.renderer.BaseHeadOffsetAt(pawnAnimator.bodyFacing) - pawnAnimator.getPawnHeadOffset(); //convert vector into pseudo body pos for head
					quat = Quaternion.AngleAxis(pawnAnimator.headAngle, Vector3.up);
					loc = vector + (bodyAddon.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(Mathf.Acos(Quaternion.Dot(Quaternion.identity, quat)) * 2f * 57.29578f);
					mat = graphic.MatAt(rot: pawnAnimator.headFacing);
				}
				else
                {

					AlienPartGenerator.RotationOffset offset = bodyAddon.defaultOffsets.GetOffset(rotation);
					Vector3 a = (offset != null) ? offset.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero;
					AlienPartGenerator.RotationOffset offset2 = bodyAddon.offsets.GetOffset(rotation);
					Vector3 vector2 = a + ((offset2 != null) ? offset2.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, comp.crownType) : Vector3.zero);
					vector2.y = (bodyAddon.inFrontOfBody ? (0.3f + vector2.y) : (-0.3f - vector2.y));
					float num = bodyAddon.angle;
					if (rotation == Rot4.North)
					{
						if (bodyAddon.layerInvert)
						{
							vector2.y = -vector2.y;
						}
						num = 0f;
					}
					if (rotation == Rot4.East)
					{
						num = -num;
						vector2.x = -vector2.x;
					}
					quat = Quaternion.AngleAxis(pawnAnimator.bodyAngle, Vector3.up);
					loc = vector + (bodyAddon.alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(Mathf.Acos(Quaternion.Dot(Quaternion.identity, quat)) * 2f * 57.29578f);

				}

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

			/*
			if (pawnAnimator != null && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator.isAnimating && (bodyAddon.drawnInBed || bodyAddon.alignWithHead))
            {

				if ((pawn.def as ThingDef_AlienRace).defName == "Alien_Orassan")
                {
					orassan = true;

					if(bodyAddon.path.Contains("closed"))
                    {
						return;
                    }

					if (bodyAddon.bodyPart.Contains("ear"))

					{
						orassan = true;

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

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

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

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

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

			else
            {

			}

		}

		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
			List<CodeInstruction> ins = instructions.ToList();
			for (int i = 0; i < ins.Count; i++)
            {

				Type[] type = new Type[] { typeof(Mesh), typeof(Vector3), typeof(Quaternion), typeof(Material), typeof(bool) };

				if (ins[i].OperandIs(AccessTools.Method(typeof(GenDraw), "DrawMeshNowOrLater", type)))
                {

					yield return new CodeInstruction(OpCodes.Ldloc, (object)7); //graphic
					yield return new CodeInstruction(OpCodes.Ldloc, (object)4); //bodyAddon
					yield return new CodeInstruction(OpCodes.Ldloc, (object)5); //offsetVector/AddonOffset (v)
					yield return new CodeInstruction(OpCodes.Ldarg, (object)2); //headOffset
					yield return new CodeInstruction(OpCodes.Ldarg, (object)3); //pawn
					yield return new CodeInstruction(OpCodes.Ldarg, (object)0); //renderflags
					yield return new CodeInstruction(OpCodes.Ldarg, (object)1); //vector
					yield return new CodeInstruction(OpCodes.Ldarg, (object)5); //rotation

					yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(HarmonyPatch_AlienRace), "RenderHeadAddonInAnimation"));

                }

				else
                {
					yield return ins[i];
				}
            }
        }

		public static bool Prefix(PawnRenderFlags renderFlags, ref Vector3 vector, ref Vector3 headOffset, Pawn pawn, ref Quaternion quat, ref Rot4 rotation)
		{
			if(pawn == null)
            {
				return true;
			}

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

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

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

			return true;

		}
	}

	*/

}


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<Patch_SexToysMasturbation.SexToyAnimationDef>
		<defName>Masturbation_DildoVaginal</defName>
		dildo masturbation
		<requiredBodyPart>vagina</requiredBodyPart>
		<sounds>true</sounds>
		<sexTypes>

Masturbation

		</sexTypes>
		<actors>

				<defNames>

Human

				</defNames>
				<isFucked>true</isFucked>

		</actors>

		<animationStages>

				<stageName>Masturbating</stageName>
				<isLooping>true</isLooping>
				<playTimeTicks>917</playTimeTicks>
				<stageIndex>0</stageIndex>
				<animationClips>

						<layer>LayingPawn</layer>
						<keyframes>

								<tickDuration>40</tickDuration>
								<bodyAngle>73.01611</bodyAngle>
								<headAngle>40.0739746</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.054543376</bodyOffsetZ>
								<bodyOffsetX>0.112624526</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<soundEffect>Slimy</soundEffect>
								<tickDuration>30</tickDuration>
								<bodyAngle>76.4867554</bodyAngle>
								<headAngle>45.3887634</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.0506898165</bodyOffsetZ>
								<bodyOffsetX>0.08564949</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>30</tickDuration>
								<bodyAngle>78.22131</bodyAngle>
								<headAngle>48.0072327</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.039129138</bodyOffsetZ>
								<bodyOffsetX>0.07794231</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>30</tickDuration>
								<bodyAngle>76.4867554</bodyAngle>
								<headAngle>45.3887634</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.0506898165</bodyOffsetZ>
								<bodyOffsetX>0.08564949</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>1</tickDuration>
								<bodyAngle>73.01611</bodyAngle>
								<headAngle>40.0739746</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.054543376</bodyOffsetZ>
								<bodyOffsetX>0.112624526</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

						</keyframes>

						<keyframes>

								<tickDuration>40</tickDuration>
								<positionX>-0.359264076</positionX>
								<positionZ>-0.00901746</positionZ>
								<rotation>114.011215</rotation>

								<tickDuration>30</tickDuration>
								<positionX>-0.2783391</positionX>
								<positionZ>-0.0514066666</positionZ>
								<rotation>81.16443</rotation>

								<tickDuration>30</tickDuration>
								<positionX>-0.1704393</positionX>
								<positionZ>-0.0668209046</positionZ>
								<rotation>72.8611145</rotation>

								<tickDuration>30</tickDuration>
								<positionX>-0.2783391</positionX>
								<positionZ>-0.0514066666</positionZ>
								<rotation>81.16443</rotation>

								<tickDuration>1</tickDuration>
								<positionX>-0.359264076</positionX>
								<positionZ>-0.00901746</positionZ>
								<rotation>114.011215</rotation>

						</keyframes>

				</animationClips>

				<stageName>GettingIntoPosition</stageName>
				<isLooping>false</isLooping>
				<stageIndex>0</stageIndex>
				<animationClips>

						<layer>LayingPawn</layer>
						<keyframes>

								<tickDuration>50</tickDuration>
								<bodyAngle>73.01611</bodyAngle>
								<headAngle>40.0739746</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.054543376</bodyOffsetZ>
								<bodyOffsetX>0.112624526</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<soundEffect>Slimy</soundEffect>
								<tickDuration>1</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

						</keyframes>

						<keyframes>

								<tickDuration>50</tickDuration>
								<positionX>-0.359264076</positionX>
								<positionZ>-0.00901746</positionZ>
								<rotation>114.011215</rotation>

								<tickDuration>1</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

						</keyframes>

				</animationClips>

				<stageName>FastMasturbation</stageName>
				<isLooping>true</isLooping>
				<stageIndex>0</stageIndex>
				<playTimeTicks>1610</playTimeTicks>
				<animationClips>

						<layer>LayingPawn</layer>
						<keyframes>

								<tickDuration>20</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<soundEffect>Slimy</soundEffect>
								<tickDuration>25</tickDuration>
								<bodyAngle>85.17255</bodyAngle>
								<headAngle>58.0615845</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03527552</bodyOffsetZ>
								<bodyOffsetX>0.0471138731</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>1</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

						</keyframes>

						<keyframes>

								<tickDuration>25</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

								<tickDuration>20</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>1</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

						</keyframes>

				</animationClips>

				<stageName>VeryFastMasturbation</stageName>
				<isLooping>true</isLooping>
				<stageIndex>0</stageIndex>
				<playTimeTicks>225</playTimeTicks>
				<animationClips>

						<layer>LayingPawn</layer>
						<keyframes>

								<tickDuration>6</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<soundEffect>Slimy</soundEffect>
								<tickDuration>8</tickDuration>
								<bodyAngle>85.17255</bodyAngle>
								<headAngle>58.0615845</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03527552</bodyOffsetZ>
								<bodyOffsetX>0.0471138731</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>1</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

						</keyframes>

						<keyframes>

								<tickDuration>6</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

								<tickDuration>8</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>1</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

						</keyframes>

				</animationClips>

				<stageName>Orgasm</stageName>
				<isLooping>false</isLooping>
				<stageIndex>0</stageIndex>
				<animationClips>

						<layer>LayingPawn</layer>
						<keyframes>

								<soundEffect>Slimy</soundEffect>
								<tickDuration>6</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<quiver>True</quiver>
								<soundEffect>Cum</soundEffect>
								<tickDuration>80</tickDuration>
								<bodyAngle>85.17255</bodyAngle>
								<headAngle>58.0615845</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03527552</bodyOffsetZ>
								<bodyOffsetX>0.0471138731</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<quiver>True</quiver>
								<soundEffect>Cum</soundEffect>
								<tickDuration>90</tickDuration>
								<bodyAngle>92.15109</bodyAngle>
								<headAngle>96.34238</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.0237147212</bodyOffsetZ>
								<bodyOffsetX>0.0432603136</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<quiver>True</quiver>
								<soundEffect>Cum</soundEffect>
								<tickDuration>70</tickDuration>
								<bodyAngle>92.15109</bodyAngle>
								<headAngle>96.34238</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.0237147212</bodyOffsetZ>
								<bodyOffsetX>0.0432603136</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>70</tickDuration>
								<bodyAngle>92.15109</bodyAngle>
								<headAngle>96.34238</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.0237147212</bodyOffsetZ>
								<bodyOffsetX>0.0432603136</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

								<tickDuration>1</tickDuration>
								<bodyAngle>81.65927</bodyAngle>
								<headAngle>58.8843079</headAngle>
								<genitalAngle>0</genitalAngle>
								<bodyOffsetZ>0.03912908</bodyOffsetZ>
								<bodyOffsetX>0.08950315</bodyOffsetX>
								<headBob>0</headBob>
								<bodyFacing>3</bodyFacing>
								<headFacing>3</headFacing>

						</keyframes>

						<keyframes>

								<tickDuration>6</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

								<tickDuration>80</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>90</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>70</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>70</tickDuration>
								<positionX>-0.178146541</positionX>
								<positionZ>-0.01672452</positionZ>
								<rotation>96.95889</rotation>

								<tickDuration>1</tickDuration>
								<positionX>-0.2899</positionX>
								<positionZ>-0.0282852575</positionZ>
								<rotation>98.13748</rotation>

						</keyframes>

				</animationClips>

		</animationStages>
	</Patch_SexToysMasturbation.SexToyAnimationDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{87763712-0536-4D5F-9EAA-520F15D4F84E}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Patch_SexToysMasturbation</RootNamespace>
    <AssemblyName>Patch_SexToysMasturbation</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>1.4\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="0Harmony">
      <HintPath>..\..\..\..\..\workshop\content\294100\1127530465\1.3\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Rimworld-Animations, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\1.3\Assemblies\Rimworld-Animations.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\rjw\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW-ToysAndMasturbation">
      <HintPath>..\..\rjw-toys-and-masturbation\Assemblies\RJW-ToysAndMasturbation.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\Defs\SexToyAnimationDef.cs" />
    <Compile Include="Source\Harmony\Harmony_PatchAll.cs" />
    <Compile Include="Source\Patches\HarmonyPatch_JobDriver_SexBaseInitiator.cs" />
    <Compile Include="Source\Utilities\AnimSexToyUtility.cs" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="1.3\Assemblies\" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="1.3\Defs\AnimationDefs\Animations_Dildo.xml" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


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

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

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

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("87763712-0536-4d5f-9eaa-520f15d4f84e")]

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


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

namespace Patch_SexToysMasturbation
{
    public class SexToyAnimationDef : AnimationDef
    {

        public String requiredBodyPart = null;

    }
}


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

    [StaticConstructorOnStartup]
    public static class Harmony_PatchAll {

        static Harmony_PatchAll() {

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

        }
    }
}


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

namespace Patch_SexToysMasturbation
{
    [HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
    public class HarmonyPatch_JobDriver_SexBaseInitiator
    {

        public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
        {

            if(__instance is JobDriver_MasturbateWithToy masturbateJobDriver)
            {

                Log.Message("Rerolling animations...");
                Pawn pawn = masturbateJobDriver.pawn;
                Thing sexToy = masturbateJobDriver.dildo;

                RerollAnimationsForSexToy(pawn, sexToy, masturbateJobDriver.Bed);
            }

        }

        public static void RerollAnimationsForSexToy(Pawn pawn, Thing thing, Thing bed)
        {
            CompSexToy sextoy = thing.TryGetComp<CompSexToy>();

            SexToyAnimationDef anim = AnimSexToyUtility.tryFindAnimation(sextoy, pawn);

            if (anim != null)
            {
                Log.Message("Playing anim " + anim.defName);

                if(bed != null)
                {
                    pawn.TryGetComp<CompBodyAnimator>().setAnchor(bed);
                    thing.TryGetComp<CompThingAnimator>().setAnchor(bed);
                }
                else
                {
                    pawn.TryGetComp<CompBodyAnimator>().setAnchor(pawn.Position);
                    thing.TryGetComp<CompThingAnimator>().setAnchor(pawn.Position);
                }

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

                pawn.TryGetComp<CompBodyAnimator>().StartAnimation(anim, new List<Pawn> { pawn }, 0, mirror);
                thing.TryGetComp<CompThingAnimator>().StartAnimation(anim, pawn, mirror);

                (pawn.jobs.curDriver as JobDriver_Sex).ticks_left = anim.animationTimeTicks;
                (pawn.jobs.curDriver as JobDriver_Sex).sex_ticks = anim.animationTimeTicks;
                (pawn.jobs.curDriver as JobDriver_Sex).duration = anim.animationTimeTicks;
            }
            else
            {
                Log.Message("No animation found");
            }

        }

    }

}


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

namespace Patch_SexToysMasturbation
{
    public class AnimSexToyUtility
    {

        public static SexToyAnimationDef tryFindAnimation(CompSexToy sexToy, Pawn pawn)
        {

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

                if(!sexToy.Props.requiredBodyParts.Contains(x.requiredBodyPart))
                {
                    return false;
                }

                if(x.requiredBodyPart == "vagina" && !Genital_Helper.has_vagina(pawn))
                {
                    return false;
                }

                return true;

            });

            if(options != null && options.Any())
            {
                return options.RandomElement();
            }
            else
            {
                return null;
            }

        }

    }
}


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.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>false</DebugSymbols>
    <DebugType>none</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>1.4\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\839005762\1.4\Assemblies\0Harmony.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="AlienRace">
      <HintPath>..\..\..\..\workshop\content\294100\839005762\1.4\Assemblies\AlienRace.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\rjw\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW-ToysAndMasturbation">
      <HintPath>..\rjw-toys-and-masturbation\Assemblies\RJW-ToysAndMasturbation.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="1.4\Source\Actors\Actor.cs" />
    <Compile Include="1.4\Source\Actors\AlienRaceOffset.cs" />
    <Compile Include="1.4\Source\Actors\BodyTypeOffset.cs" />
    <Compile Include="1.4\Source\Animations\AnimationStage.cs" />
    <Compile Include="1.4\Source\Animations\Clips\BaseAnimationClip.cs" />
    <Compile Include="1.4\Source\Animations\Clips\PawnAnimationClip.cs" />
    <Compile Include="1.4\Source\Animations\Clips\ThingAnimationClip.cs" />
    <Compile Include="1.4\Source\Animations\Keyframes\Keyframe.cs" />
    <Compile Include="1.4\Source\Animations\Keyframes\PawnKeyframe.cs" />
    <Compile Include="1.4\Source\Animations\Keyframes\ThingKeyframe.cs" />
    <Compile Include="1.4\Source\Comps\CompBodyAnimator.cs" />
    <Compile Include="1.4\Source\Comps\CompProperties_BodyAnimator.cs" />
    <Compile Include="1.4\Source\Comps\CompProperties_ThingAnimator.cs" />
    <Compile Include="1.4\Source\Comps\CompThingAnimator.cs" />
    <Compile Include="1.4\Source\Defs\AnimationDef.cs" />
    <Compile Include="1.4\Source\Extensions\PawnWoundDrawerExtension.cs" />
    <Compile Include="1.4\Source\MainTabWindows\MainTabWindow_OffsetConfigure.cs" />
    <Compile Include="1.4\Source\MainTabWindows\OffsetMainButtonDefOf.cs" />
    <Compile Include="1.4\Source\MainTabWindows\WorldComponent_UpdateMainTab.cs" />
    <Compile Include="1.4\Source\Patches\Harmony_PatchAll.cs" />
    <Compile Include="1.4\Source\Patches\OtherModPatches\HarmonyPatch_CSL.cs" />
    <Compile Include="1.4\Source\Patches\OtherModPatches\HarmonyPatch_DontShaveYourHead.cs" />
    <Compile Include="1.4\Source\Patches\OtherModPatches\HarmonyPatch_FacialAnimation.cs" />
    <Compile Include="1.4\Source\Patches\OtherModPatches\HarmonyPatch_HatsDisplaySelection.cs" />
    <Compile Include="1.4\Source\Patches\OtherModPatches\HarmonyPatch_ShowHairWithHats.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_HeadHair.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_PawnRenderer.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_PawnRotation.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_Pawn_DrawTracker.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_SetPawnAnimatable.cs" />
    <Compile Include="1.4\Source\Patches\RimworldPatches\HarmonyPatch_Thing.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\HarmonyPatch_PlaySexSounds.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\HarmonyPatch_SexTick.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\HarmonyPatch_WorkGiverSex.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_JoinInBed.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseInitiator.cs" />
    <Compile Include="1.4\Source\Patches\RJWPatches\JobDrivers\HarmonyPatch_JobDriver_SexBaseReceiverLoved.cs" />
    <Compile Include="1.4\Source\Settings\AnimationSettings.cs" />
    <Compile Include="1.4\Source\Utilities\AnimationUtility.cs" />
    <Compile Include="1.4\Source\Utilities\PatchOperationAddOrReplace.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="1.1\Assemblies\Rimworld-Animations.dll" />
    <Content Include="1.4\Assemblies\Rimworld-Animations.dll" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_Beast.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_Lesbian.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_Masturbate.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_Multi.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_SexToys.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_vanilla.xml" />
    <Content Include="1.4\Defs\AnimationDefs\Animations_Vanilla2.xml" />
    <Content Include="1.4\Defs\AnimationDefs\TemplateAnimation.xml" />
    <Content Include="1.4\Defs\MainTabDefs\MainButtonDef.xml" />
    <Content Include="1.4\Defs\SoundDefs\Sounds_Sex.xml" />
    <Content Include="1.4\Patches\AnimationPatchHSK.xml" />
    <Content Include="1.4\Patches\AnimationPatch_CompBodyAnimator.xml" />
    <Content Include="1.4\Patches\CompatibilityPatch_FacialAnimation.xml" />
    <Content Include="1.4\Patches\CompatibilityPatch_HCSK.xml" />
    <Content Include="1.4\Patches\CompPatches\AutoCleaner.xml" />
    <Content Include="1.4\Patches\CompPatches\CombatExtended.xml" />
    <Content Include="1.4\Patches\CompPatches\ZombieLand.xml" />
    <Content Include="1.4\Patches\RacePatches\Epona race Renaissance.xml" />
    <Content Include="1.4\Patches\RacePatches\Nyaron.xml" />
    <Content Include="1.4\Sounds\Sex\Clap_1.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_2.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_3.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_4.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_5.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_6.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_7.wav" />
    <Content Include="1.4\Sounds\Sex\Clap_8.wav" />
    <Content Include="1.4\Sounds\Sex\cum.wav" />
    <Content Include="1.4\Sounds\Sex\kucyu04.wav" />
    <Content Include="1.4\Sounds\Sex\Slime\Slimy1.wav" />
    <Content Include="1.4\Sounds\Sex\Slime\Slimy2.wav" />
    <Content Include="1.4\Sounds\Sex\Slime\Slimy3.wav" />
    <Content Include="1.4\Sounds\Sex\Slime\Slimy4.wav" />
    <Content Include="1.4\Sounds\Sex\Slime\Slimy5.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_1.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_10.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\suck_2.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_3.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_4.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_5.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_6.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_7.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_8.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Suck_9.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Swallow_1.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Swallow_2.wav" />
    <Content Include="1.4\Sounds\Sex\Suck\Swallow_3.wav" />
    <Content Include="1.4\Textures\UI\MainTab.png" />
    <Content Include="About\About.xml" />
    <Content Include="About\Manifest.xml" />
    <Content Include="Languages\PortugueseBrazilian\DefInjected\MainButtonDef\MainButtonDef.xml" />
    <Content Include="Languages\PortugueseBrazilian\DefInjected\Rimworld_Animations.AnimationDef\Animations_Beast.xml" />
    <Content Include="Languages\PortugueseBrazilian\DefInjected\Rimworld_Animations.AnimationDef\Animations_Lesbian.xml" />
    <Content Include="Languages\PortugueseBrazilian\DefInjected\Rimworld_Animations.AnimationDef\Animations_Multi.xml" />
    <Content Include="Languages\PortugueseBrazilian\DefInjected\Rimworld_Animations.AnimationDef\Animations_vanilla.xml" />
    <Content Include="LoadFolders.xml" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="1.2\" />
  </ItemGroup>
  <ItemGroup>
    <None Include="1.4\Defs\AnimationDefs\AnimationDefs.rar" />
    <None Include="1.4\Patch_HatsDisplaySelection\Patch_HatsDisplaySelection.csproj" />
  </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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Patch_SexToysMasturbation", "Patch_SexToysMasturbation\Patch_SexToysMasturbation.csproj", "{87763712-0536-4D5F-9EAA-520F15D4F84E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Patch_HumanoidAlienRaces", "Patch_HumanoidAlienRaces\Patch_HumanoidAlienRaces.csproj", "{C76F3790-9AC0-4827-ACD5-84174238954F}"
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
		{87763712-0536-4D5F-9EAA-520F15D4F84E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{87763712-0536-4D5F-9EAA-520F15D4F84E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{87763712-0536-4D5F-9EAA-520F15D4F84E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{87763712-0536-4D5F-9EAA-520F15D4F84E}.Release|Any CPU.Build.0 = Release|Any CPU
		{C76F3790-9AC0-4827-ACD5-84174238954F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{C76F3790-9AC0-4827-ACD5-84174238954F}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{C76F3790-9AC0-4827-ACD5-84174238954F}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{C76F3790-9AC0-4827-ACD5-84174238954F}.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:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# 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

license: mit


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. Fix the document (before commiting, Inkscape only)

Inkscape shows weird behaviour in some regards.
If you use Inkscape, close the document and run

    python3 inkscape_svg_fixup.py vector_source.svg

before continuing. Open the file in Inkscape and save it again.
You need to make a minor change, as Inkscape will not save a unchanged file
(move the notes back and forth or something). The fixup does not produce
the same linebreaks as Inkscape or Illustrator and git will go mad because
the whole seems to have changed.

What it does:
* Adobe Illustrator uses group IDs as layer labels.
  Inkscape however uses layer labels and a separate, auto-generated group ID.
  inkscape_svg_fixup.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

Execute

    python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/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.


code:
#!/usr/bin/env python3

'''
Application for "fixing" SVGs edited with Inkscape

These problems are addressed:

* Inkscape notoriously copies class styles into the object definitions.
https://bugs.launchpad.net/inkscape/+bug/167937

* Inkscape uses labels on layers. Layers are basically named groups.
  Inkscape does not sync the group id with the layer label.

Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg

Note:
The output of lxml differs greatly from standard SVG indentation.
Please open and save the file in Inkscape before committing.
'''

import lxml.etree as etree
import sys

def fix(tree):
  # know namespaces
  ns = {
    'svg' : 'http://www.w3.org/2000/svg',
    'inkscape' : 'http://www.inkscape.org/namespaces/inkscape'
  }

  # find document global style definition
  # mangle it and interpret as python dictionary
  style_element = tree.find('./svg:style',namespaces=ns)
  style_definitions = style_element.text
  pythonic_style_definitions = '{'+style_definitions.\
  replace('\t.','"').replace('{','":"').replace('}','",').\
  replace('/*','#')+'}'
  styles = eval(pythonic_style_definitions)

  # go through all SVG elements
  for elem in tree.iter():
    if (elem.tag == etree.QName(ns['svg'], 'g')):
      # compare inkscape label with group element ID
      l = elem.get(etree.QName(ns['inkscape'], 'label'))
      if l:
        i = elem.get('id')
        if (i != l):
          print("Overwriting ID %s with Label %s..."%(i, l))
          elem.set('id', l)

    # remove all offending style attributes
    s = elem.get('style')
    c = elem.get('class')
    if (c and s):
      s = s.lower()
      c = c.split(' ')[0] # regard main style only
      cs = ''
      if c in styles:
        cs = styles[c].strip('; ').lower()
      if (c not in styles):
        print("Object id %s references unknown style class %s."%(i,c))
      else:
        if (cs != s.strip('; ')):
          print("Style %s removed from object id %s differed from class %s style %s."%(s,i,c,cs))
        del elem.attrib["style"]

if __name__ == "__main__":
  input_file = sys.argv[1]
  tree = etree.parse(input_file)
  fix(tree)
  # store SVG into file (input file is overwritten)
  svg = etree.tostring(tree, pretty_print=True)
  with open(input_file, 'wb') as f:
    f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
    f.write(svg)


code:
#!/usr/bin/env python3

'''
Application for procedural content adaption
*THIS IS VERY EXPERIMENTAL*
Contains a very poor man's implementation of spline mesh warping.

This application parses SVG path data of an outfit sample aligned to a body part.
The outfit is then replicated for other shapes of the same body part.

Example data is geared towards generating a strap outfit for boobs and torso
for all sizes of boobs and all shapes of torsos based on a single outfit for
boobs of size 2 and a hourglass torso respectively.

Limitations:
* handles paths only
* only svg.path Line and CubicBezier are tested
* only the aforementioned examples are tested

Usage:
python3 vector_clothing_replicator.py infile clothing bodypart destinationfile

Usage Example:
python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg
python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg
'''

from svg.path import parse_path
import copy
import lxml.etree as etree
import sys

REFERENCE_PATH_SAMPLES = 200
EMBED_REPLICATIONS = True # wether to embed all replications into the input file or output separate files

input_file = sys.argv[1]
clothing = sys.argv[2]
bodypart = sys.argv[3]
output_file_embed = sys.argv[4]

# TODO: make these configurable
output_file_pattern = '%s_%s_%s.svg' #bodypart, target_id, clothing

if ('Torso' == bodypart):
  xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style
  xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]'%(clothing)
  xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]'%(clothing,'%s')
  target_ids = "Unnatural,Hourglass,Normal".split(",")
  reference_id = "Hourglass"
else:
  raise RuntimeError("Please specify a bodypart for clothing to replicate.")

tree = etree.parse(input_file)
ns = {'svg' : 'http://www.w3.org/2000/svg'}

canvas = copy.deepcopy(tree)
for e in canvas.xpath('./svg:g',namespaces=ns)+canvas.xpath('./svg:path',namespaces=ns):
  # TODO: this should be "remove all objects, preserve document properties"
  e.getparent().remove(e)

def get_points(xpath_shape):
  '''
  This funciton extracts reference paths by the given xpath selector.
  Each path is used to sample a fixed number of points.
  '''
  paths_data = tree.xpath(xpath_shape,namespaces=ns)
  points = []
  path_length = None
  for path_data in paths_data:
    p = parse_path(path_data)
    points += [
      p.point(1.0/float(REFERENCE_PATH_SAMPLES)*i)
      for i in range(REFERENCE_PATH_SAMPLES)
    ]
  if (not points):
    raise RuntimeError(
      'No paths for reference points found by selector "%s".'%(xpath_shape)
    )
  return points

def point_movement(point, reference_points, target_points):
  '''
  For a given point, finds the nearest point in the reference path.
  Gives distance vector from the nearest reference point to the
  respective target reference point.
  '''
  distances = [abs(point-reference_point) for reference_point in reference_points]
  min_ref_dist_idx = min(enumerate(distances), key=lambda x:x[1])[0]
  movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
  return movement

reference_points = get_points(xpath_shape%(reference_id))
container = tree.xpath(xpath_outfit_container,namespaces=ns)
if (len(container) != 1):
  raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.'%(xpath_outfit_container))
container = container[0]
outfit_source = container.xpath(xpath_outfit%(reference_id),namespaces=ns)
if (len(outfit_source) != 1):
  raise RuntimeError('Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".'%(xpath_outfit%(reference_id), xpath_outfit_container))
outfit_source = outfit_source[0]

for target_id in target_ids:
  print(
    'Generating variant "%s" of clothing "%s" for bodypart "%s"...'%
    (target_id, clothing, bodypart)
  )
  outfit = copy.deepcopy(outfit_source)
  paths = outfit.xpath('./svg:path',namespaces=ns)
  if target_id == reference_id:
    print("This is the source variant. Skipping...")
  else:
    layerid = outfit.get('id').replace('_%s'%(reference_id),'_%s'%(target_id))
    outfit.set('id', layerid)
    outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'), layerid) # for the Inkscape-users
    target_points = get_points(xpath_shape%(target_id))
    if (len(reference_points) != len(target_points)):
      raise RuntimeError(
        ('Different amounts of sampled points in reference "%s" and target "%s" paths. '+
        'Selector "%s" probably matches different number of paths in the two layers.')%
        (reference_id, target_id, xpath_shape)
      )
    for path in paths:
      path_data = path.get("d")
      p = parse_path(path_data)
      for segment in p:
        original_distance = abs(segment.end-segment.start)
        start_movement = point_movement(segment.start, reference_points, target_points)
        segment.start += start_movement
        end_movement = point_movement(segment.end, reference_points, target_points)
        segment.end += end_movement
        distance = abs(segment.end-segment.start)
        try:
          # enhance position of CubicBezier control points
          # amplification is relative to the distance gained by movement
          segment.control1 += start_movement
          segment.control1 += (segment.control1-segment.start)*(distance/original_distance-1.0)
          segment.control2 += end_movement
          segment.control2 += (segment.control2-segment.end)*(distance/original_distance-1.0)
        except AttributeError as ae:
          # segment is not a CubicBezier
          pass
      path.set("d", p.d())
    if EMBED_REPLICATIONS:
      container.append(outfit)
  if not EMBED_REPLICATIONS:
    container = copy.deepcopy(canvas).xpath('.',namespaces=ns)[0]
    container.append(outfit)

  if not EMBED_REPLICATIONS:
    svg = etree.tostring(container, pretty_print=True)
    with open((output_file_pattern%(bodypart, target_id, clothing)).lower(), 'wb') as f:
      f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
      f.write(svg)

if EMBED_REPLICATIONS:
  svg = etree.tostring(tree, pretty_print=True)
  with open(output_file_embed, 'wb') as f:
    f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
    f.write(svg)


code:
#!/usr/bin/env python3

'''
Application for splitting groups from one SVG file into separate files

Usage:
python3 vector_layer_split.py infile format outdir

Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
'''

import lxml.etree as etree
import sys
import os
import copy
import re
import inkscape_svg_fixup

input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
if not os.path.exists(output_directory):
    os.makedirs(output_directory)

ns = {
  'svg' : 'http://www.w3.org/2000/svg',
  'inkscape' : 'http://www.inkscape.org/namespaces/inkscape',
  'sodipodi':"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}

tree = etree.parse(input_file)
inkscape_svg_fixup.fix(tree)

# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()

# remove all svg root attributes except document size
for a in root.attrib:
  if (a != "viewBox"):
    del root.attrib[a]

# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
  root.attrib["class"] = "'+_art_display_class+'"

# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
  if (e.tag == etree.QName(ns['svg'], 'defs')):
    defs = e
  if (e.tag == etree.QName(ns['svg'], 'g') or
      e.tag == etree.QName(ns['svg'], 'metadata') or
      e.tag == etree.QName(ns['svg'], 'defs') or
      e.tag == etree.QName(ns['sodipodi'], 'namedview') or
      (output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
      ):
    root.remove(e)
# template preparation finished

# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+',)
regex_space = re.compile('[>][ ]+[<]',)

# find all groups
layers = tree.xpath('//svg:g',namespaces=ns)
for layer in layers:
  i = layer.get('id')
  if ( # disregard non-content groups
    i.endswith("_") or # manually suppressed with underscore
    i.startswith("XMLID") or # Illustrator generated group
    i.startswith("g") # Inkscape generated group
  ):
    continue
  # create new canvas
  output = copy.deepcopy(template)
  # copy all shapes into template
  canvas = output.getroot()
  for e in layer:
    canvas.append(e)
  # represent template as SVG (binary string)
  svg = etree.tostring(output, pretty_print=False)
  # poor man's conditional defs insertion
  # TODO: extract only referenced defs (filters, gradients, ...)
  # TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
  if ("filter:" in svg.decode('utf-8')):
    # it seems there is a filter referenced in the generated SVG, re-insert defs from main document
    canvas.insert(0,defs)
    # re-generate output
    svg = etree.tostring(output, pretty_print=False)

  if (output_format == 'tw'):
    # remove unnecessary attributes
    # TODO: never generate unnecessary attributes in the first place
    svg = svg.decode('utf-8')
    svg = regex_xmlns.sub('',svg)
    svg = svg.replace(' inkscape:connector-curvature="0"','') # this just saves space
    svg = svg.replace('\n','').replace('\r','') # print cannot be multi-line
    svg = regex_space.sub('><',svg) # remove indentaion
    svg = svg.replace('svg:','') # svg namespace was removed
    svg = svg.replace('<g ','<g transform="\'+_art_transform+\'"') # internal groups are used for scaling
    svg = svg.encode('utf-8')

  # save SVG string to file
  i = layer.get('id')
  output_path = os.path.join(output_directory,i+'.'+output_format)
  with open(output_path, 'wb') as f:
    if (output_format == 'svg'):
      # Header for normal SVG (XML)
      f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
      f.write(svg)
    elif (output_format == 'tw'):
      # Header for SVG in Twine file (SugarCube print statement)
      f.write((':: Art_Vector_%s [nobr]\n\n'%(i)).encode("utf-8"))
      f.write("<<print '<html>".encode("utf-8"))
      f.write(svg)
      f.write("</html>' >>".encode("utf-8"))


code:
# Ignore everything in this directory
*
# Except the following:
!.gitignore
!resources/vector/*


code:
#!/bin/bash

# Find and insert current commit
COMMIT=$(git rev-list HEAD --count)
sed -i "s/COMMIT/$COMMIT/" ./src/init/storyInit.tw

# Will add all *.tw files to StoryIncludes.
./sanityCheck
rm -f src/config/start.tw
cp src/config/start.tw.proto start.tw.tmp
find src -name '*.tw' -print >>start.tw.tmp
mv start.tw.tmp src/config/start.tw

HASH=`git log -n1 |grep commit | sed 's/commit //'`

if [ $(uname -m) = "x86_64" ]
then
	echo "x64 arch"
	./devTools/tweeGo/tweego_nix64 -o bin/FC_pregmod.html src/config/start.tw
else
	echo "x86 arch"
	./devTools/tweeGo/tweego_nix86 -o bin/FC_pregmod.html src/config/start.tw
fi

#Make the output prettier, replacing \t with a tab and \n with a newline
sed -i -e '/^.*<div id="store-area".*$/s/\\t/\t/g' -e '/^.*<div id="store-area".*$/s/\\n/\n/g'  bin/FC_pregmod.html

# Revert ./src/init/storyInit.tw for next compilation
git checkout -- ./src/init/storyInit.tw

rm -f src/config/start.tw

path: compile


code:
#!/bin/bash

# Will add all *.tw files to StoryIncludes.
./sanityCheck
rm -f src/config/start.tw
cp src/config/start.tw.proto start.tw.tmp
find src -name '*.tw' -print >>start.tw.tmp
mv start.tw.tmp src/config/start.tw

HASH=`git log -n1 |grep -m1 commit | sed 's/commit //'`

if [ $(uname -m) = "x86_64" ]
then
	echo "x64 arch"
	./devTools/tweeGo/tweego_nix64 -o bin/FC_pregmod_$HASH.html src/config/start.tw
else
	echo "x86 arch"
	./devTools/tweeGo/tweego_nix86 -o bin/FC_pregmod_$HASH.html src/config/start.tw
fi

#Make the output prettier, replacing \t with a tab and \n with a newline
sed -i -e '/^<div id="store-area".*$/s/\\t/\t/g' -e '/^<div id="store-area".*$/s/\\n/\n/g'  bin/FC_pregmod_$HASH.html

rm -f src/config/start.tw

echo "FC_pregmod_$HASH.html" compilation finished.

path: compile-git


code:
@echo off
:: Free Cities Basic Compiler - Windows

:: Will add all *.tw files to StoryIncludes.
del src\config\start.tw
copy src\config\start.tw.proto start.tw.tmp >nul
>>start.tw.tmp (for /r "src" %%F in (*.tw) do echo %%F)
move start.tw.tmp src\config\start.tw >nul

:: Run the appropriate compiler for the user's CPU architecture.
if %PROCESSOR_ARCHITECTURE% == AMD64 (
    CALL "%~dp0devTools\tweeGo\tweego_win64.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
) else (
    CALL "%~dp0devTools\tweeGo\tweego_win86.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
)

del src\config\start.tw
ECHO Done


code:
@echo off
:: Free Cities Basic Compiler - Windows

:: See if we can find a git installation
setlocal enabledelayedexpansion

for %%k in (HKCU HKLM) do (
    for %%w in (\ \Wow6432Node\) do (
        for /f "skip=2 delims=: tokens=1*" %%a in ('reg query "%%k\SOFTWARE%%wMicrosoft\Windows\CurrentVersion\Uninstall\Git_is1" /v InstallLocation 2^> nul') do (
            for /f "tokens=3" %%z in ("%%a") do (
                set GIT=%%z:%%b
				set GITFOUND=yes
                goto FOUND
            )
        )
    )
)
:FOUND
if %GITFOUND% == yes (
	set "PATH=%GIT%bin;%PATH%"
	bash --login -c ./sanityCheck
)

:: Will add all *.tw files to StoryIncludes.
del src\config\start.tw
copy src\config\start.tw.proto start.tw.tmp >nul
>>start.tw.tmp (for /r "src" %%F in (*.tw) do echo %%F)
move start.tw.tmp src\config\start.tw >nul

:: Run the appropriate compiler for the user's CPU architecture.
if %PROCESSOR_ARCHITECTURE% == AMD64 (
    CALL "%~dp0devTools\tweeGo\tweego_win64.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
) else (
    CALL "%~dp0devTools\tweeGo\tweego_win86.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
)

if %GITFOUND% == yes (
	:: Make the output prettier, replacing \t with a tab and \n with a newline
	bash -c "sed -i -e '/^.*<div id=\"store-area\".*$/s/\\\t/\t/g' -e '/^.*<div id=\"store-area\".*$/s/\\\n/\n/g' bin/FC_pregmod.html"

	:: Revert ./src/init/storyInit.tw for next compilation
	git checkout -- ./src/init/storyInit.tw
)

del src\config\start.tw
ECHO Done
PAUSE


code:
@echo off
:: Free Cities Basic Compiler - Windows

:: Will add all *.tw files to StoryIncludes.
del src\config\start.tw
copy src\config\start.tw.proto start.tw.tmp >nul
>>start.tw.tmp (for /r "src" %%F in (*.tw) do echo %%F)
move start.tw.tmp src\config\start.tw >nul

:: Run the appropriate compiler for the user's CPU architecture.
if %PROCESSOR_ARCHITECTURE% == AMD64 (
    CALL "%~dp0devTools\tweeGo\tweego_win64.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
) else (
    CALL "%~dp0devTools\tweeGo\tweego_win86.exe" -o "%~dp0bin/FC_pregmod.html" "%~dp0src\config\start.tw"
)

del src\config\start.tw
ECHO Done
PAUSE