code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	public class JobDriver_BestialityForFemale : JobDriver_SexBaseInitiator
	{
		public IntVec3 SleepSpot => Bed.SleepPosOfAssignedPawn(pawn);
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, 1, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();

			this.FailOnDespawnedOrNull(iTarget);
			this.FailOnDespawnedNullOrForbidden(iBed);
			this.FailOn(() => !pawn.CanReserveAndReach(Partner, PathEndMode.Touch, Danger.Deadly));
			this.FailOn(() => pawn.Drafted);
			this.FailOn(() => Partner.IsFighting());
			this.FailOn(() => !Partner.CanReach(pawn, PathEndMode.Touch, Danger.Deadly));

			yield return Toils_Reserve.Reserve(iTarget, 1, 0);
			Toil gotoAnimal = Toils_Goto.GotoThing(iTarget, PathEndMode.Touch);
			yield return gotoAnimal;

			Toil gotoBed = new Toil();
			gotoBed.defaultCompleteMode = ToilCompleteMode.PatherArrival;
			gotoBed.FailOnBedNoLongerUsable(iBed);
			gotoBed.AddFailCondition(() => Partner.Downed);
			gotoBed.initAction = delegate
			{
				pawn.pather.StartPath(SleepSpot, PathEndMode.OnCell);
				Partner.jobs.StopAll();
				Job job = JobMaker.MakeJob(JobDefOf.GotoMindControlled, SleepSpot);
				Partner.jobs.StartJob(job, JobCondition.InterruptForced);
			};
			yield return gotoBed;

			Toil waitInBed = new Toil();
			waitInBed.FailOn(() => pawn.GetRoom(RegionType.Set_Passable) == null);
			waitInBed.defaultCompleteMode = ToilCompleteMode.Delay;
			waitInBed.initAction = delegate
			{
				ticksLeftThisToil = 5000;
			};
			waitInBed.tickAction = delegate
			{
				pawn.GainComfortFromCellIfPossible();
				if (IsInOrByBed(Bed, Partner) && pawn.PositionHeld == Partner.PositionHeld)
				{
					ReadyForNextToil();
				}
			};
			yield return waitInBed;

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				var gettin_loved = JobMaker.MakeJob(xxx.gettin_loved, pawn, Bed);
				Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced);
			};
			yield return StartPartnerJob;

			Toil SexToil = new Toil();
			SexToil.AddFailCondition(() => Partner.Dead || !IsInOrByBed(Bed, Partner));
			SexToil.socialMode = RandomSocialMode.Off;
			SexToil.defaultCompleteMode = ToilCompleteMode.Never;
			SexToil.handlingFacing = true;
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				usedCondom = CondomUtility.TryUseCondom(pawn);
				Start();
			};
			SexToil.AddPreTickAction(delegate
			{
				--ticks_left;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					if (xxx.is_zoophile(pawn))
						ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
					else
						ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
				SexTick(pawn, Partner);
				SexUtility.reduce_rest(pawn, 1);
				SexUtility.reduce_rest(Partner, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
			});
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			Toil afterSex = new Toil
			{
				initAction = delegate
				{
					//Log.Message("JobDriver_BestialityForFemale::MakeNewToils() - Calling aftersex");
					SexUtility.ProcessSex(Partner, pawn, usedCondom: usedCondom, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
			yield return afterSex;
		}
	}
}


code:
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	public class JobDriver_BestialityForMale : JobDriver_Rape
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, 1, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--ModLog.Message(" JobDriver_BestialityForMale::MakeNewToils() called");

			setup_ticks();
			var PartnerJob = xxx.gettin_bred;

			//this.FailOn (() => (!Partner.health.capacities.CanBeAwake) || (!comfort_prisoners.is_designated (Partner)));
			// Fail if someone else reserves the prisoner before the pawn arrives or colonist can't reach animal
			this.FailOn(() => !pawn.CanReserveAndReach(Partner, PathEndMode.Touch, Danger.Deadly));
			this.FailOn(() => Partner.HostileTo(pawn));
			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => pawn.Drafted);

			yield return Toils_Reserve.Reserve(iTarget, 1, 0);
			//ModLog.Message(" JobDriver_BestialityForMale::MakeNewToils() - moving towards animal");
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.Touch);
			yield return Toils_Interpersonal.WaitToBeAbleToInteract(pawn);
			yield return Toils_Interpersonal.GotoInteractablePosition(iTarget);

			if (xxx.is_kind(pawn)
				|| (xxx.CTIsActive && xxx.has_traits(pawn) && pawn.story.traits.HasTrait(xxx.RCT_AnimalLover)))
			{
				yield return TalkToAnimal(pawn, Partner);
				yield return TalkToAnimal(pawn, Partner);
			}

			if (Rand.Chance(0.6f))
				yield return TalkToAnimal(pawn, Partner);

			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			SexUtility.RapeTargetAlert(pawn, Partner);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				//--ModLog.Message(" JobDriver_BestialityForMale::MakeNewToils() - Setting animal job driver");
				var dri = Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped;
				if (dri == null)
				{
					//wild animals may flee or attack
					if (pawn.Faction != Partner.Faction && Partner.RaceProps.wildness > Rand.Range(0.22f, 1.0f)
						&& !(pawn.TicksPerMoveCardinal < (Partner.TicksPerMoveCardinal / 2) && !Partner.Downed && xxx.is_not_dying(Partner)))
					{
						Partner.jobs.StopAll(); // Wake up animal if sleeping.

						float aggro = Partner.kindDef.RaceProps.manhunterOnTameFailChance;
						if (Partner.kindDef.RaceProps.predator)
							aggro += 0.2f;
						else
							aggro -= 0.1f;

						//wild animals may attack
						if (Rand.Chance(aggro) && Partner.CanSee(pawn))
						{
							Partner.rotationTracker.FaceTarget(pawn);
							LifeStageUtility.PlayNearestLifestageSound(Partner, (ls) => ls.soundAngry, 1.4f);
							ThrowMetaIcon(Partner.Position, Partner.Map, ThingDefOf.Mote_IncapIcon);
							ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_ColonistFleeing); //red '!'
							Partner.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.Manhunter);
							if (Partner.kindDef.RaceProps.herdAnimal && Rand.Chance(0.2f))
							{ // 20% chance of turning the whole herd hostile...
								List<Pawn> packmates = Partner.Map.mapPawns.AllPawnsSpawned.Where(x =>
									x != Partner && x.def == Partner.def && x.Faction == Partner.Faction &&
									x.Position.InHorDistOf(Partner.Position, 24f) && x.CanSee(Partner)).ToList();

								foreach (Pawn packmate in packmates)
								{
									packmate.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.Manhunter);
								}
							}
							Messages.Message(pawn.Name.ToStringShort + " is being attacked by " + xxx.get_pawnname(Partner) + ".", pawn, MessageTypeDefOf.ThreatSmall);
						}
						//wild animals may flee
						else
						{
							ThrowMetaIcon(Partner.Position, Partner.Map, ThingDefOf.Mote_ColonistFleeing);
							LifeStageUtility.PlayNearestLifestageSound(Partner, (ls) => ls.soundCall);
							Partner.mindState.StartFleeingBecauseOfPawnAction(pawn);
							Partner.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.PanicFlee);
						}
						pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
					}
					else
					{
						Job gettin_bred = JobMaker.MakeJob(PartnerJob, pawn, Partner);
						Partner.jobs.StartJob(gettin_bred, JobCondition.InterruptForced, null, true);
						(Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped)?.increase_time(ticks_left);
					}
				}
			};
			yield return StartPartnerJob;

			Toil SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					if (xxx.is_zoophile(pawn))
						ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
					else
						ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
				SexTick(pawn, Partner);
				//no hitting wild animals, and getting rect by their Manhunter
				/*
				if (pawn.IsHashIntervalTick (ticks_between_hits))
					roll_to_hit (pawn, Partner);
					*/
				SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);

			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					//ModLog.Message(" JobDriver_BestialityForMale::MakeNewToils() - creating aftersex toil");
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}

		[SyncMethod]
		private Toil TalkToAnimal(Pawn pawn, Pawn animal)
		{
			Toil toil = new Toil();
			toil.initAction = delegate
			{
				pawn.interactions.TryInteractWith(animal, SexUtility.AnimalSexChat);
			};
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			toil.defaultCompleteMode = ToilCompleteMode.Delay;
			toil.defaultDuration = Rand.Range(120, 220);
			return toil;
		}
	}
}

repo_name: Euclidean/rjw

license: mit


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// This is the driver for animals mounting breeders.
	/// </summary>
	public class JobDriver_Breeding : JobDriver_Rape
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, BreederHelper.max_animals_at_once, 0);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();
			var PartnerJob = xxx.gettin_raped;

			//--Log.Message("JobDriver_Breeding::MakeNewToils() - setting fail conditions");
			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !pawn.CanReserve(Partner, BreederHelper.max_animals_at_once, 0)); // Fail if someone else reserves the target before the animal arrives.
			this.FailOn(() => !pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some)); // Fail if animal cannot reach target.
			this.FailOn(() => pawn.Drafted);

			// Path to target
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			//if (!(pawn.IsDesignatedBreedingAnimal() && Partner.IsDesignatedBreeding()));
			if (!(pawn.IsAnimal() && Partner.IsAnimal()))
				SexUtility.RapeTargetAlert(pawn, Partner);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				var dri = Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped;
				if (dri == null)
				{
					Job gettin_raped = JobMaker.MakeJob(PartnerJob, pawn);
					Building_Bed Bed = null;
					if (Partner.GetPosture() == PawnPosture.LayingInBed)
						Bed = Partner.CurrentBed();

					Partner.jobs.StartJob(gettin_raped, JobCondition.InterruptForced, null, false, true, null);
					if (Bed != null)
						(Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped)?.Set_bed(Bed);
				}
			};
			yield return StartPartnerJob;

			// Breed target
			var SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					if (xxx.is_zoophile(pawn) || xxx.is_animal(pawn))
						ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
					else
						ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
				if (pawn.IsHashIntervalTick(ticks_between_hits) && !xxx.is_zoophile(Partner))
					Roll_to_hit(pawn, Partner);
				SexTick(pawn, Partner);
				if (!Partner.Dead)
					SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					//Log.Message("JobDriver_Breeding::MakeNewToils() - Calling aftersex");
					//// Trying to add some interactions and social logs
					bool isRape = !(pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, Partner) ||
					 	(xxx.is_animal(pawn) && (pawn.RaceProps.wildness - pawn.RaceProps.petness + 0.18f) > Rand.Range(0.36f, 1.8f)));
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	public class JobDriver_Masturbate : JobDriver_SexBaseInitiator
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return true; // No reservations needed.
		}
		public virtual IntVec3 cell => (IntVec3)job.GetTarget(iCell);

		[SyncMethod]
		new public void setup_ticks()
		{
			base.setup_ticks();
			// Faster fapping when frustrated.
			duration = (int)(xxx.is_frustrated(pawn) ? 2500.0f * Rand.Range(0.2f, 0.7f) : 2500.0f * Rand.Range(0.2f, 0.4f));
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();

			//this.FailOn(() => PawnUtility.PlayerForcedJobNowOrSoon(pawn));
			this.FailOn(() => pawn.health.Downed);
			this.FailOn(() => pawn.IsBurning());
			this.FailOn(() => pawn.IsFighting());
			this.FailOn(() => pawn.Drafted);

			Toil findfapspot = new Toil
			{
				initAction = delegate
				{
					pawn.pather.StartPath(cell, PathEndMode.OnCell);
				},
				defaultCompleteMode = ToilCompleteMode.PatherArrival
			};
			yield return findfapspot;

			//ModLog.Message(" Making new toil for QuickFap.");

			Toil SexToil = Toils_General.Wait(duration);
			SexToil.handlingFacing = true;
			SexToil.initAction = delegate
			{
				Start();
			};
			SexToil.tickAction = delegate
			{
				--duration;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				SexTick(pawn, null);
				SexUtility.reduce_rest(pawn, 1);
				if (duration <= 0)
					ReadyForNextToil();
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					SexUtility.Aftersex(pawn, xxx.rjwSextype.Masturbation);
					if (!SexUtility.ConsiderCleaning(pawn)) return;

					LocalTargetInfo own_cum = pawn.PositionHeld.GetFirstThing<Filth>(pawn.Map);

					Job clean = JobMaker.MakeJob(JobDefOf.Clean);
					clean.AddQueuedTarget(TargetIndex.A, own_cum);

					pawn.jobs.jobQueue.EnqueueFirst(clean);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// This is the driver for animals mating.
	/// </summary>
	public class JobDriver_Mating : JobDriver_Rape
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, BreederHelper.max_animals_at_once, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();
			var PartnerJob = xxx.gettin_loved;

			//--Log.Message("JobDriver_Mating::MakeNewToils() - setting fail conditions");
			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !pawn.CanReserve(Partner, BreederHelper.max_animals_at_once, 0)); // Fail if someone else reserves the target before the animal arrives.
			this.FailOn(() => !pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some)); // Fail if animal cannot reach target.
			this.FailOn(() => pawn.Drafted);

			// Path to target
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				var dri = Partner.jobs.curDriver as JobDriver_SexBaseRecieverLoved;
				if (dri == null)
				{
					Job gettin_loved = JobMaker.MakeJob(PartnerJob, pawn);
					Building_Bed Bed = null;
					if (Partner.GetPosture() == PawnPosture.LayingInBed)
						Bed = Partner.CurrentBed();

					Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced, null, false, true, null);
					if (Bed != null)
						(Partner.jobs.curDriver as JobDriver_SexBaseRecieverLoved)?.Set_bed(Bed);
				}
			};
			yield return StartPartnerJob;

			// Mate target
			var SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				SexTick(pawn, Partner);
				if (!Partner.Dead)
					SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					bool isRape = false;
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_Rape : JobDriver_SexBaseInitiator
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			if (RJWSettings.DebugRape) ModLog.Message("" + this.GetType().ToString() + "::MakeNewToils() called");
			setup_ticks();
			var PartnerJob = xxx.gettin_raped;

			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !pawn.CanReserve(Partner, xxx.max_rapists_per_prisoner, 0)); // Fail if someone else reserves the prisoner before the pawn arrives
			this.FailOn(() => pawn.IsFighting());
			this.FailOn(() => Partner.IsFighting());
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			SexUtility.RapeTargetAlert(pawn, Partner);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				var dri = Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped;
				if (dri == null)
				{
					Job gettin_raped = JobMaker.MakeJob(PartnerJob, pawn);
					Building_Bed Bed = null;
					if (Partner.GetPosture() == PawnPosture.LayingInBed)
						Bed = Partner.CurrentBed();

					Partner.jobs.StartJob(gettin_raped, JobCondition.InterruptForced, null, false, true, null);
					if (Bed != null)
						(Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped)?.Set_bed(Bed);
				}
			};
			yield return StartPartnerJob;

			var SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				if (RJWSettings.rape_stripping && (Partner.IsColonist || pawn.IsColonist))
					Partner.Strip();
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_hits))
					Roll_to_hit(pawn, Partner);
				SexTick(pawn, Partner);
				SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					//// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_RapeComfortPawn : JobDriver_Rape
	{
		protected override IEnumerable<Toil> MakeNewToils()
		{
			if (RJWSettings.DebugRape) ModLog.Message("" + this.GetType().ToString() + "::MakeNewToils() called");
			setup_ticks();
			var PartnerJob = xxx.gettin_raped;

			this.FailOnDespawnedNullOrForbidden(iTarget);
			//this.FailOn(() => (!Partner.health.capacities.CanBeAwake) || (!comfort_prisoners.is_designated(Partner)));//this is wrong
			this.FailOn(() => (!Partner.IsDesignatedComfort()));
			this.FailOn(() => !pawn.CanReserve(Partner, xxx.max_rapists_per_prisoner, 0)); // Fail if someone else reserves the prisoner before the pawn arrives
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			SexUtility.RapeTargetAlert(pawn, Partner);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				var dri = Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped;
				if (dri == null)
				{
					Job gettin_raped = JobMaker.MakeJob(PartnerJob, pawn);
					Building_Bed Bed = null;
					if (Partner.GetPosture() == PawnPosture.LayingInBed)
						Bed = Partner.CurrentBed();

					Partner.jobs.StartJob(gettin_raped, JobCondition.InterruptForced, null, false, true, null);
					if (Bed != null)
						(Partner.jobs.curDriver as JobDriver_SexBaseRecieverRaped)?.Set_bed(Bed);
				}
			};
			yield return StartPartnerJob;

			Toil SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				// Unlike normal rape try use comfort prisoner condom
				CondomUtility.GetCondomFromRoom(Partner);
				usedCondom = CondomUtility.TryUseCondom(Partner);

				if (RJWSettings.DebugRape) ModLog.Message("JobDriver_RapeComfortPawn::MakeNewToils() - reserving prisoner");
				//pawn.Reserve(Partner, xxx.max_rapists_per_prisoner, 0);
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_hits))
					Roll_to_hit(pawn, Partner);
				SexTick(pawn, Partner);
				SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
					Partner.records.Increment(xxx.GetRapedAsComfortPawn);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_ViolateCorpse : JobDriver_Rape
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, 1, -1, null, errorOnFailed);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			if (RJWSettings.DebugRape) ModLog.Message(" JobDriver_ViolateCorpse::MakeNewToils() called");
			setup_ticks();

			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !pawn.CanReserve(Target, 1, 0));  // Fail if someone else reserves the prisoner before the pawn arrives
			this.FailOn(() => pawn.IsFighting());
			this.FailOn(() => pawn.Drafted);
			this.FailOn(Target.IsBurning);

			if (RJWSettings.DebugRape) ModLog.Message(" JobDriver_ViolateCorpse::MakeNewToils() - moving towards Target");
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			var alert = RJWPreferenceSettings.rape_attempt_alert == RJWPreferenceSettings.RapeAlert.Disabled ?
				MessageTypeDefOf.SilentInput : MessageTypeDefOf.NeutralEvent;
			Messages.Message(xxx.get_pawnname(pawn) + " is trying to rape a corpse of " + xxx.get_pawnname(Partner), pawn, alert);

			setup_ticks();// re-setup ticks on arrival

			var SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Delay;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.initAction = delegate
			{
				if (RJWSettings.DebugRape) ModLog.Message(" JobDriver_ViolateCorpse::MakeNewToils() - stripping Target");
				(Target as Corpse).Strip();
				Start();
			};
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					if (xxx.is_necrophiliac(pawn))
						ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
					else
						ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
				//if (pawn.IsHashIntervalTick (ticks_between_hits))
				//	roll_to_hit (pawn, Target);
				SexTick(pawn, Target);
				SexUtility.reduce_rest(pawn, 2);
			};
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					if (RJWSettings.DebugRape) ModLog.Message(" JobDriver_ViolateCorpse::MakeNewToils() - creating aftersex toil");
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	internal class JobDef_RapeEnemy : JobDef
	{
		public List<JobDef> interruptJobs;
		public List<string> TargetDefNames = new List<string>();
		public int priority = 0;

		protected JobDriver_RapeEnemy instance
		{
			get
			{
				if (_tmpInstance == null)
				{
					_tmpInstance = (JobDriver_RapeEnemy)Activator.CreateInstance(driverClass);
				}
				return _tmpInstance;
			}
		}

		private JobDriver_RapeEnemy _tmpInstance;

		public override void ResolveReferences()
		{
			base.ResolveReferences();
			interruptJobs = new List<JobDef> { null, JobDefOf.LayDown, JobDefOf.Wait_Wander, JobDefOf.GotoWander, JobDefOf.AttackMelee };
		}

		public virtual bool CanUseThisJobForPawn(Pawn rapist)
		{
			bool busy = !interruptJobs.Contains(rapist.CurJob?.def);
			if (RJWSettings.DebugRape) ModLog.Message(" JobDef_RapeEnemy::CanUseThisJobForPawn( " + xxx.get_pawnname(rapist) + " ) - busy:" + busy + " with current job: " + rapist.CurJob?.def?.ToString());
			if (busy) return false;

			return instance.CanUseThisJobForPawn(rapist);// || TargetDefNames.Contains(rapist.def.defName);
		}

		public virtual Pawn FindVictim(Pawn rapist, Map m)
		{
			return instance.FindVictim(rapist, m);
		}
	}

	public class JobDriver_RapeEnemy : JobDriver_Rape
	{
		private static readonly HediffDef is_submitting = HediffDef.Named("Hediff_Submitting");//used in find_victim

		//override can_rape mechanics
		protected bool requireCanRape = true;

		public virtual bool CanUseThisJobForPawn(Pawn rapist)
		{
			return xxx.is_human(rapist);
		}

		// this is probably useseless, maybe there be something in future
		public virtual bool considerStillAliveEnemies => true;

		[SyncMethod]
		public virtual Pawn FindVictim(Pawn rapist, Map m)
		{
			if (RJWSettings.DebugRape) ModLog.Message($"{this.GetType().ToString()}::TryGiveJob({xxx.get_pawnname(rapist)}) map {m?.ToString()}");
			if (rapist == null || m == null) return null;

			if (RJWSettings.DebugRape) ModLog.Message($" can rape = {xxx.can_rape(rapist)}");
			if (requireCanRape && !xxx.can_rape(rapist)) return null;

			List<Pawn> validTargets = new List<Pawn>();
			float min_fuckability = 0.10f;                          // Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;                             // Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();      // Valid pawns and their fuckability
			Pawn chosentarget = null;                               // Final target pawn

			IEnumerable<Pawn> targets = m.mapPawns.AllPawnsSpawned.Where(x
				=> !x.IsForbidden(rapist) && x != rapist && x.HostileTo(rapist)
				&& IsValidTarget(rapist, x))
				.ToList();

			if (RJWSettings.DebugRape) ModLog.Message($" targets {targets.Count()}");

			if (targets.Any(x => IsBlocking(rapist, x)))	//If any of the targets is not downed and visible - don't proceed with rape (you have more pressing things to do).
			{												//This is a bit whacky bearing in mind target selection. For example vulnerable pawns will block, but non-vulnearable will not
				return null;
			}

			foreach (var target in targets)
			{
				if (!xxx.cells_to_target_rape(rapist, target.Position))
				{
					//if (RJWSettings.DebugRape) ModLog.Message($" {xxx.get_pawnname(target)} too far (cells) = {rapist.Position.DistanceToSquared(target.Position)}, skipping");
					if (RJWSettings.DebugRape) ModLog.Message($" {xxx.get_pawnname(target)} too far (cells) = {rapist.Position.DistanceTo(target.Position)}, skipping");
					continue;// too far
				}

				float fuc = GetFuckability(rapist, target);

				if (fuc > min_fuckability)
				{
					if (xxx.can_path_to_target(rapist, target.Position))
						valid_targets.Add(target, fuc);
					else
						if (RJWSettings.DebugRape) ModLog.Message($" {xxx.get_pawnname(target)} too far (path), skipping");
				}
				else
					if (RJWSettings.DebugRape) ModLog.Message($" {xxx.get_pawnname(target)} fuckability too low = {fuc}, skipping");

			}
			if (RJWSettings.DebugRape) ModLog.Message($" fuckable targets {valid_targets.Count()}");

			if (valid_targets.Any())
			{
				avg_fuckability = valid_targets.Average(x => x.Value);
				if (RJWSettings.DebugRape) ModLog.Message($" avg_fuckability {avg_fuckability}");

				// choose pawns to fuck with above average fuckability
				var valid_targetsFiltered = valid_targets.Where(x => x.Value >= avg_fuckability);
				if (RJWSettings.DebugRape) ModLog.Message($" targets above avg_fuckability {valid_targetsFiltered.Count()}");

				if (valid_targetsFiltered.Any())
					chosentarget = valid_targetsFiltered.RandomElement().Key;
			}

			return chosentarget;
		}

		bool IsBlocking(Pawn rapist, Pawn target)
		{
			return considerStillAliveEnemies && !target.Downed && rapist.CanSee(target);
		}

		bool IsValidTarget(Pawn rapist, Pawn target)
		{

			if (!RJWSettings.bestiality_enabled)
			{
				if (xxx.is_animal(target) && xxx.is_human(rapist))
				{
					//bestiality disabled, skip.
					return false;
				}
				if (xxx.is_animal(rapist) && xxx.is_human(target))
				{
					//bestiality disabled, skip.
					return false;
				}
			}

			if (!RJWSettings.animal_on_animal_enabled)
				if ((xxx.is_animal(target) && xxx.is_animal(rapist)))
				{
					//animal_on_animal disabled, skip.
					return false;
				}

			if ((xxx.is_mechanoid(rapist) && xxx.is_animal(target)) || (xxx.is_animal(rapist) && xxx.is_mechanoid(target)))
				return false; //no Mech on Animal action, ref JobDriver_RapeEnemyByMech::GetFuckability()

			if (target.CurJob?.def == xxx.gettin_raped || target.CurJob?.def == xxx.gettin_loved)
			{
				//already having sex with someone, skip, give chance to other victims.
				return false;
			}

			return Can_rape_Easily(target) &&
				(xxx.is_human(target) || xxx.is_animal(target)) &&
				rapist.CanReserveAndReach(target, PathEndMode.OnCell, Danger.Some, xxx.max_rapists_per_prisoner, 0);
		}

		public virtual float GetFuckability(Pawn rapist, Pawn target)
		{
			float fuckability = 0;
			if (target.health.hediffSet.HasHediff(is_submitting)) // it's not about attractiveness anymore, it's about showing who's whos bitch
			{
				fuckability = 2 * SexAppraiser.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
			}
			else if (SexAppraiser.would_rape(rapist, target))
			{
				fuckability = SexAppraiser.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
			}

			if (RJWSettings.DebugRape) ModLog.Message($"JobDriver_RapeEnemy::GetFuckability({xxx.get_pawnname(rapist)}, {xxx.get_pawnname(target)})");

			return fuckability;
		}

		protected bool Can_rape_Easily(Pawn pawn)
		{
			return xxx.can_get_raped(pawn) && !pawn.IsBurning();
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByAnimal : JobDriver_RapeEnemy
	{
		public override bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && (rapist.CurJob.def != JobDefOf.LayDown || rapist.CurJob.def != JobDefOf.Wait_Wander || rapist.CurJob.def != JobDefOf.GotoWander))
				return false;

			return xxx.is_animal(rapist) && !xxx.is_insect(rapist);
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByHumanlike : JobDriver_RapeEnemy
	{
		public override bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && (rapist.CurJob.def != JobDefOf.LayDown || rapist.CurJob.def != JobDefOf.Wait_Wander || rapist.CurJob.def != JobDefOf.GotoWander))
				return false;

			return xxx.is_human(rapist);
		}
	}
}


code:
using System.Linq;
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByInsect : JobDriver_RapeEnemy
	{
		public override bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && (rapist.CurJob.def != JobDefOf.LayDown || rapist.CurJob.def != JobDefOf.Wait_Wander || rapist.CurJob.def != JobDefOf.GotoWander))
				return false;

			return xxx.is_insect(rapist);
		}

		public override float GetFuckability(Pawn rapist, Pawn target)
		{
			//Female plant Eggs to everyone.
			if (rapist.gender == Gender.Female) //Genital_Helper.has_ovipositorF(rapist);
			{
				//only rape when target dont have eggs yet
				//if ((from x in target.health.hediffSet.GetHediffs<Hediff_InsectEgg>() where (x.IsParent(rapist)) select x).Count() > 0)
				{
					return 1f;
				}
			}
			//Male rape to everyone.
			//Feritlize eggs to target with planted eggs.
			else //Genital_Helper.has_ovipositorM(rapist);
			{
				//only rape target when can fertilize
				//if ((from x in target.health.hediffSet.GetHediffs<Hediff_InsectEgg>() where (x.IsParent(rapist) && !x.fertilized) select x).Count() > 0)
				if ((from x in target.health.hediffSet.GetHediffs<Hediff_InsectEgg>() where x.IsParent(rapist) select x).Count() > 0)
				{
					return 1f;
				}
			}
			return 0f;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByMech : JobDriver_RapeEnemy
	{
		public override bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && (rapist.CurJob.def != JobDefOf.LayDown || rapist.CurJob.def != JobDefOf.Wait_Wander || rapist.CurJob.def != JobDefOf.GotoWander))
				return false;

			return xxx.is_mechanoid(rapist);
		}

		public override float GetFuckability(Pawn rapist, Pawn target)
		{
			//Plant stuff into humanlikes.
			if (xxx.is_human(target))
				return 1f;
			else
				return 0f;
		}

	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyToParasite : JobDriver_RapeEnemy
	{
		//not implemented
		public JobDriver_RapeEnemyToParasite()
		{
			this.requireCanRape = false;
		}

		public override bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && (rapist.CurJob.def != JobDefOf.LayDown || rapist.CurJob.def != JobDefOf.Wait_Wander || rapist.CurJob.def != JobDefOf.GotoWander))
				return false;

			return false;
		}
	}
}


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

namespace rjw
{
	public class JobDriver_RandomRape : JobDriver_Rape
	{
		//Add some stuff. planning became bersek when failed to rape.
	}
}


code:
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Verse.Sound;
using Multiplayer.API;
using System.Linq;

namespace rjw
{
	public abstract class JobDriver_Sex : JobDriver
	{

		public readonly TargetIndex iTarget = TargetIndex.A;	//pawn or corpse
		public readonly TargetIndex iBed = TargetIndex.B;		//bed(maybe some furniture in future?)
		public readonly TargetIndex iCell = TargetIndex.C;		//cell/location to have sex at(fapping)

		public float satisfaction = 1.0f;

		public bool shouldreserve = true;

		public int stackCount = 0;

		public int ticks_between_hearts = 60;
		public int ticks_between_hits = 60;
		public int ticks_between_thrusts = 60;
		public int ticks_left = 1000;
		public int duration = 5000;
		public int ticks_remaining = 10;

		public bool usedCondom = false;
		public bool isRape = false;
		public bool isWhoring = false;
		public bool face2face = false;

		public Thing Target			// for reservation
		{
			get
			{
				return (Thing)job.GetTarget(TargetIndex.A);
			}
		}
		public Pawn Partner
		{
			get
			{
				if (Target is Pawn)
					return (Pawn)job.GetTarget(TargetIndex.A);
				else if (Target is Corpse)
					return ((Corpse)job.GetTarget(TargetIndex.A)).InnerPawn;
				else
					return null;
			}
		}

		public Building_Bed Bed
		{
			get
			{
				if (pBed != null)
					return pBed;
				else if ((Thing)job.GetTarget(TargetIndex.B) is Building_Bed)
					return (Building_Bed)job.GetTarget(TargetIndex.B);
				else
					return null;
			}
		}
		//not bed; chair, maybe something else in future
		public Building Building
		{
			get
			{
				if ((Thing)job.GetTarget(TargetIndex.B) is Building && !((Thing)job.GetTarget(TargetIndex.B) is Building_Bed))
					return (Building)job.GetTarget(TargetIndex.B);
				else
					return null;
			}
		}

		public Building_Bed pBed = null;

		public SexProps Sexprops = null;
		public xxx.rjwSextype sexType = xxx.rjwSextype.None;

		[SyncMethod]
		public void setup_ticks()
		{
			ticks_left = (int)(2000.0f * Rand.Range(0.50f, 0.90f));
			ticks_between_hearts = Rand.RangeInclusive(70, 130);
			ticks_between_hits = Rand.Range(xxx.config.min_ticks_between_hits, xxx.config.max_ticks_between_hits);
			if (xxx.is_bloodlust(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.75);
			if (xxx.is_brawler(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.90);
			ticks_between_thrusts = 120;
			duration = ticks_left;
		}

		public void increase_time(int min_ticks_remaining)
		{
			if (min_ticks_remaining > ticks_remaining)
				ticks_remaining = min_ticks_remaining;
		}

		public void Set_bed(Building_Bed newBed)
		{
			pBed = newBed;
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref ticks_left, "ticks_left", 0, false);
			Scribe_Values.Look(ref ticks_between_hearts, "ticks_between_hearts", 0, false);
			Scribe_Values.Look(ref ticks_between_hits, "ticks_between_hits", 0, false);
			Scribe_Values.Look(ref ticks_between_thrusts, "ticks_between_thrusts", 0, false);
			Scribe_Values.Look(ref duration, "duration", 0, false);
			Scribe_Values.Look(ref ticks_remaining, "ticks_remaining", 10, false);

			Scribe_References.Look(ref pBed, "pBed");
			//Scribe_Values.Look(ref props, "props");
			Scribe_Values.Look(ref usedCondom, "usedCondom");
			Scribe_Values.Look(ref isRape, "isRape");
			Scribe_Values.Look(ref isWhoring, "isWhoring");
			Scribe_Values.Look(ref sexType, "sexType");
			Scribe_Values.Look(ref face2face, "face2face");
		}

		public void SexTick(Pawn pawn, Thing target, bool pawnnude = true, bool partnernude = true)
		{
			var partner = target as Pawn;

			if (pawn.IsHashIntervalTick(ticks_between_thrusts))
			{
				ChangePsyfocus(pawn, partner);
				Animate(pawn, partner);
				PlaySexSound();
				if (!isRape)
				{
					pawn.GainComfortFromCellIfPossible();
					if (partner != null)
						partner.GainComfortFromCellIfPossible();
				}
			}

			//refresh DrawNude after beating and Notify_MeleeAttackOn
			// Endytophiles prefer clothed sex, everyone else gets nude.
			if (!xxx.has_quirk(pawn, "Endytophile"))
			{
				if (pawnnude)
				{
					SexUtility.DrawNude(pawn);
				}

				if (partner != null)
					if (partnernude)
					{
						SexUtility.DrawNude(partner);
					}
			}
		}

		/// <summary>
		/// simple rjw thrust animation
		/// </summary>
		public void Animate(Pawn pawn, Thing target)
		{
			RotatePawns(pawn, Partner);
			//attack/ride 1x2 cell cocksleeve/dildo?
			//if (Building != null)
			//	target = Building;
			if (target != null)
			{
				pawn.Drawer.Notify_MeleeAttackOn(target);

				var partner = target as Pawn;
				if (partner != null && !isRape)
					partner.Drawer.Notify_MeleeAttackOn(pawn);
			}
		}

		/// <summary>
		/// handle Psyfocus
		/// </summary>
		public void ChangePsyfocus(Pawn pawn, Thing target)
		{
			if (ModsConfig.RoyaltyActive)
			{
				if (pawn.jobs?.curDriver is JobDriver_ViolateCorpse)
					if (xxx.is_necrophiliac(pawn) && MeditationFocusTypeAvailabilityCache.PawnCanUse(pawn, DefDatabase<MeditationFocusDef>.GetNamedSilentFail("Morbid")))
					{
						SexUtility.OffsetPsyfocus(pawn, 0.01f);
					}

				if (target != null)
				{
					var partner = target as Pawn;
					if (partner != null)
					{
						if (xxx.is_nympho(pawn))
						{
							SexUtility.OffsetPsyfocus(pawn, 0.01f);
						}
						else if (xxx.is_zoophile(pawn) && xxx.is_animal(partner) && MeditationFocusTypeAvailabilityCache.PawnCanUse(pawn, MeditationFocusDefOf.Natural))
						{
							SexUtility.OffsetPsyfocus(pawn, 0.01f);
						}

						if (xxx.is_nympho(partner))
						{
							SexUtility.OffsetPsyfocus(partner, 0.01f);
						}
						else if (xxx.is_zoophile(partner) && xxx.is_animal(pawn) && MeditationFocusTypeAvailabilityCache.PawnCanUse(partner, MeditationFocusDefOf.Natural))
						{
							SexUtility.OffsetPsyfocus(partner, 0.01f);
						}

						if (xxx.RoMIsActive)
						{
							if (xxx.has_traits(pawn))
								if (pawn.story.traits.HasTrait(xxx.Succubus))
								{
									SexUtility.OffsetPsyfocus(pawn, 0.01f);
								}

							if (xxx.has_traits(partner))
								if (partner.story.traits.HasTrait(xxx.Succubus))
								{
									SexUtility.OffsetPsyfocus(partner, 0.01f);
								}
						}
						if (xxx.NightmareIncarnationIsActive)
						{
							if (xxx.has_traits(pawn))
								foreach (var x in pawn.AllComps?.Where(x => x?.props?.ToStringSafe() == "NightmareIncarnation.CompProperties_SuccubusRace"))
								{
									SexUtility.OffsetPsyfocus(pawn, 0.01f);
									break;
								}

							if (xxx.has_traits(partner))
								foreach (var x in partner.AllComps?.Where(x => x?.props?.ToStringSafe() == "NightmareIncarnation.CompProperties_SuccubusRace"))
								{
									SexUtility.OffsetPsyfocus(partner, 0.01f);
									break;
								}
						}

					}
				}
			}
		}

		/// <summary>
		/// rotate pawns
		/// </summary>
		public void RotatePawns(Pawn pawn, Thing target)
		{
			if (Building != null)
			{
				if (face2face)
					pawn.Rotation = Building.Rotation.Opposite;
				else
					pawn.Rotation = Building.Rotation;

				return;
			}
			if (target == null) // solo
			{
				//pawn.Rotation = Rot4.South;
				return;
			}
			var partner = target as Pawn;
			if (partner == null || partner.Dead) // necro
			{
				pawn.rotationTracker.Face(target.DrawPos);
				return;
			}
			if (partner.jobs?.curDriver is JobDriver_SexBaseReciever)
				if (((JobDriver_SexBaseReciever)partner.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Count > 1) return;

			//maybe could do a hand check for monster girls but w/e
			//bool partnerHasHands = Receiver.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand));

			// most of animal sex is likely doggystyle.
			if (xxx.is_animal(pawn) && xxx.is_animal(partner))
			{
				if (sexType == xxx.rjwSextype.Anal || sexType == xxx.rjwSextype.Vaginal || sexType == xxx.rjwSextype.DoublePenetration)
				{
					//>>
					//Log.Message("animal doggy");
					pawn.rotationTracker.Face(partner.DrawPos);
					partner.Rotation = pawn.Rotation;
				}
				else
				{
					//><
					//Log.Message("animal non doggy");
					pawn.rotationTracker.Face(target.DrawPos);
					partner.rotationTracker.Face(pawn.DrawPos);
				}
			}
			else
			{
				if (this is JobDriver_BestialityForFemale)
				{
					if (sexType == xxx.rjwSextype.Anal || sexType == xxx.rjwSextype.Vaginal || sexType == xxx.rjwSextype.DoublePenetration)
					{
						//<<
						//Log.Message("bestialityFF doggy");
						partner.rotationTracker.Face(pawn.DrawPos);
						pawn.Rotation = partner.Rotation;
					}
					else
					{
						//><
						//Log.Message("bestialityFF non doggy");
						pawn.rotationTracker.Face(target.DrawPos);
						partner.rotationTracker.Face(pawn.DrawPos);
					}
				}
				else if (partner.GetPosture() == PawnPosture.LayingInBed)
				{
					//x^
					//Log.Message("loving/casualsex in bed");
					// this could use better handling for cowgirl/reverse cowgirl and who pen who, if such would be implemented
					//until then...

					if (!face2face && sexType == xxx.rjwSextype.Anal ||
										sexType == xxx.rjwSextype.Vaginal ||
										sexType == xxx.rjwSextype.DoublePenetration ||
										sexType == xxx.rjwSextype.Fisting)
						//if (xxx.is_female(pawn) && xxx.is_female(partner))
					{
						// in bed loving face down
						pawn.Rotation = partner.CurrentBed().Rotation.Opposite;
					}
					//else if (!(xxx.is_male(pawn) && xxx.is_male(partner)))
					//{
					//	// in bed loving face down
					//	pawn.Rotation = partner.CurrentBed().Rotation.Opposite;
					//}
					else
					{
						// in bed loving, face up
						pawn.Rotation = partner.CurrentBed().Rotation;
					}
				}
				// 30% chance of face-to-face regardless, for variety.
				else if (!face2face && (sexType == xxx.rjwSextype.Anal ||
										sexType == xxx.rjwSextype.Vaginal ||
										sexType == xxx.rjwSextype.DoublePenetration ||
										sexType == xxx.rjwSextype.Fisting))
				{
					//>>
					//Log.Message("doggy");
					pawn.rotationTracker.Face(target.DrawPos);
					partner.Rotation = pawn.Rotation;
				}
				// non doggystyle, or face-to-face regardless
				else
				{
					//><
					//Log.Message("non doggy");
					pawn.rotationTracker.Face(target.DrawPos);
					partner.rotationTracker.Face(pawn.DrawPos);
				}
			}
		}

		[SyncMethod]
		public void Rollface2face(float chance = 0.3f)
		{
			Setface2face(Rand.Chance(chance));
		}
		public void Setface2face(bool chance)
		{
			face2face = chance;
		}

		public static void Roll_to_hit(Pawn Pawn, Pawn Partner, bool isRape = true)
		{
			SexUtility.Sex_Beatings(Pawn, Partner, isRape);
		}
		public void ThrowMetaIcon(IntVec3 pos, Map map, ThingDef icon)
		{
			MoteMaker.ThrowMetaIcon(pos, map, icon);
		}
		public void PlaySexSound()
		{
			if (RJWSettings.sounds_enabled)
			{
				SoundInfo sound = new TargetInfo(pawn.Position, pawn.Map);
				sound.volumeFactor = RJWSettings.sounds_sex_volume;
				SoundDef.Named("Sex").PlayOneShot(sound);
			}
		}
		public void PlayCumSound()
		{
			if (RJWSettings.sounds_enabled)
			{
				SoundInfo sound = new TargetInfo(pawn.Position, pawn.Map);
				sound.volumeFactor = RJWSettings.sounds_cum_volume;
				SoundDef.Named("Cum").PlayOneShot(sound);
			}
		}
		public void PlaySexVoice()
		{
			//if (RJWSettings.sounds_enabled)
			//{
			//	SoundInfo sound = new TargetInfo(pawn.Position, pawn.Map);
			//	sound.volumeFactor = RJWSettings.sounds_voice_volume;
			//	SoundDef.Named("Sex").PlayOneShot(sound);
			//}
		}
		public void PlayOrgasmVoice()
		{
			//if (RJWSettings.sounds_enabled)
			//{
			//	SoundInfo sound = new TargetInfo(pawn.Position, pawn.Map);
			//	sound.volumeFactor = RJWSettings.sounds_orgasm_volume;
			//	SoundDef.Named("Orgasm").PlayOneShot(sound);
			//}
		}
		public void CalculateSatisfactionPerTick()
		{
			satisfaction = 1.0f;
		}

		public static bool IsInOrByBed(Building_Bed b, Pawn p)
		{
			for (int i = 0; i < b.SleepingSlotsCount; i++)
			{
				if (b.GetSleepingSlotPos(i).InHorDistOf(p.Position, 1f))
				{
					return true;
				}
			}
			return false;
		}

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return true; // No reservations needed.
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			return null;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	public abstract class JobDriver_SexBaseInitiator : JobDriver_Sex
	{

		public void Start()
		{
			if (Partner == null) //TODO: solo sex descriptions
			{
				isRape = false;
				isWhoring = false;
				//Sexprops = SexUtility.SelectSextype(pawn, Partner, isRape, isWhoring, Partner);
				//sexType = Sexprops.SexType;
				//SexUtility.LogSextype(Sexprops.Giver, Sexprops.Reciever, Sexprops.RulePack, Sexprops.DictionaryKey);
			}
			else if (Partner.Dead)
			{
				isRape = true;
				isWhoring = false;
				if (Sexprops == null)
					Sexprops = SexUtility.SelectSextype(pawn, Partner, isRape, isWhoring, Partner);
				sexType = Sexprops.SexType;
				SexUtility.LogSextype(Sexprops.Giver, Sexprops.Reciever, Sexprops.RulePack, Sexprops.DictionaryKey);
			}
			else if (Partner.jobs?.curDriver is JobDriver_SexBaseReciever)
			{
				(Partner.jobs.curDriver as JobDriver_SexBaseReciever).parteners.AddDistinct(pawn);
				(Partner.jobs.curDriver as JobDriver_SexBaseReciever).increase_time(duration);

				//prevent Receiver standing up and interrupting rape
				if (Partner.health.hediffSet.HasHediff(HediffDef.Named("Hediff_Submitting")))
					Partner.health.AddHediff(HediffDef.Named("Hediff_Submitting"));

				//(Target.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Count; //TODO: add multipartner support so sex doesn't repeat, maybe, someday
				isRape = Partner?.CurJob.def == xxx.gettin_raped;
				isWhoring = pawn?.CurJob.def == xxx.whore_is_serving_visitors;
				if (Sexprops == null)
					Sexprops = SexUtility.SelectSextype(pawn, Partner, isRape, isWhoring, Partner);
				sexType = Sexprops.SexType;
				SexUtility.LogSextype(Sexprops.Giver, Sexprops.Reciever, Sexprops.RulePack, Sexprops.DictionaryKey);
			}
			//Log.Message("sexType: " + sexType.ToString());
			//props = new SexProps(pawn, Partener, sexType, isRape);//maybe merge everything into this ?
		}

		//public void Change(xxx.rjwSextype sexType)
		//{
		//	if (pawn.jobs?.curDriver is JobDriver_SexBaseInitiator)
		//	{
		//		(pawn.jobs.curDriver as JobDriver_SexBaseInitiator).increase_time(duration);
		//		Sexprops = SexUtility.SelectSextype(pawn, Partner, isRape, isWhoring, Partner);
		//		sexType = Sexprops.SexType;
		//		SexUtility.LogSextype(Sexprops.Giver, Sexprops.Reciever, Sexprops.RulePack, Sexprops.DictionaryKey);
		//	}
		//	if (Partner.jobs?.curDriver is JobDriver_SexBaseReciever)
		//	{
		//		(Partner.jobs.curDriver as JobDriver_SexBaseReciever).increase_time(duration);
		//		Sexprops = SexUtility.SelectSextype(pawn, Partner, isRape, isWhoring, Partner);
		//		sexType = Sexprops.SexType;
		//		SexUtility.LogSextype(Sexprops.Giver, Sexprops.Reciever, Sexprops.RulePack, Sexprops.DictionaryKey);
		//	}
		//	sexType = sexType
		//}

		public void End()
		{
			if (xxx.is_human(pawn))
				pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
			if (Partner?.jobs?.curDriver is JobDriver_SexBaseReciever)
			{
				(Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Remove(pawn);
			}
		}

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			ModLog.Message("shouldreserve " + shouldreserve);
			if (shouldreserve && Target != null)
				return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, stackCount, null, errorOnFailed);
			else if (shouldreserve && Bed != null)
				return pawn.Reserve(Bed, job, Bed.SleepingSlotsCount, 0, null, errorOnFailed);
			else
				return true; // No reservations needed.

			//return this.pawn.Reserve(this.Partner, this.job, 1, 0, null) && this.pawn.Reserve(this.Bed, this.job, 1, 0, null);
		}
	}
}


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

namespace rjw
{
	public class JobDriver_SexBaseReciever : JobDriver_Sex
	{
		//give this poor driver some love other than (Partner.jobs?.curDriver is JobDriver_SexBaseReciever)
		public List<Pawn> parteners = new List<Pawn>();

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Collections.Look(ref parteners, "parteners", LookMode.Reference);
		}
	}
}


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

namespace rjw
{
	public class JobDriver_SexBaseRecieverLoved : JobDriver_SexBaseReciever
	{
		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();
			parteners.Add(Partner);// add job starter, so this wont fail, before Initiator starts his job
			//--ModLog.Message("JobDriver_GettinLoved::MakeNewToils is called");
			//ModLog.Message("" + Partner.CurJob.def);

			float partner_ability = xxx.get_sex_ability(Partner);

			// More/less hearts based on partner ability.
			if (partner_ability < 0.8f)
				ticks_between_thrusts += 120;
			else if (partner_ability > 2.0f)
				ticks_between_thrusts -= 30;

			// More/less hearts based on opinion.
			if (pawn.relations.OpinionOf(Partner) < 0)
				ticks_between_hearts += 50;
			else if (pawn.relations.OpinionOf(Partner) > 60)
				ticks_between_hearts -= 25;

			this.FailOnDespawnedOrNull(iTarget);
			this.FailOn(() => !Partner.health.capacities.CanBeAwake);
			this.FailOn(() => pawn.Drafted);
			this.FailOn(() => Partner.Drafted);

			if (Partner.CurJob.def == xxx.casual_sex) // sex in bed
			{
				this.KeepLyingDown(iBed);
				yield return Toils_Reserve.Reserve(iTarget, 1, 0);
				yield return Toils_Reserve.Reserve(iBed, Bed.SleepingSlotsCount, 0);

				var get_loved = MakeSexToil();
				get_loved.FailOn(() => Partner.CurJob.def != xxx.casual_sex);
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.quick_sex)
			{
				yield return Toils_Reserve.Reserve(iTarget, 1, 0);

				var get_loved = MakeSexToil();
				get_loved.handlingFacing = false;
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.whore_is_serving_visitors)
			{
				this.FailOn(() => Partner.CurJob == null);
				yield return Toils_Reserve.Reserve(iTarget, 1, 0);

				var get_loved = MakeSexToil();
				get_loved.FailOn(() => (Partner.CurJob.def != xxx.whore_is_serving_visitors));
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.bestialityForFemale)
			{
				this.FailOn(() => Partner.CurJob == null);
				yield return Toils_Reserve.Reserve(iTarget, 1, 0);

				var get_loved = MakeSexToil();
				get_loved.FailOn(() => (Partner.CurJob.def != xxx.bestialityForFemale));
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.animalMate)
			{
				this.FailOn(() => Partner.CurJob == null);
				yield return Toils_Reserve.Reserve(iTarget, 1, 0);

				var get_loved = MakeSexToil();
				get_loved.FailOn(() => (Partner.CurJob.def != xxx.animalMate));
				yield return get_loved;
			}
		}

		private Toil MakeSexToil()
		{
			Toil get_loved = new Toil();
			if (Partner.CurJob.def == xxx.casual_sex) // sex in bed
				get_loved = Toils_LayDown.LayDown(iBed, true, false, false, false);
			get_loved.defaultCompleteMode = ToilCompleteMode.Never;
			get_loved.socialMode = RandomSocialMode.Off;
			get_loved.handlingFacing = true;
			//get_loved.initAction = delegate
			//{
			//};
			get_loved.tickAction = delegate
			{
				--ticks_remaining;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
			};
			get_loved.AddEndCondition(new Func<JobCondition>(() =>
			{
				if ((ticks_remaining <= 0) || (parteners.Count <= 0))
				{
					return JobCondition.Succeeded;
				}
				return JobCondition.Ongoing;
			}));
			get_loved.AddFinishAction(delegate
			{
				if (xxx.is_human(pawn))
					pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
			});
			get_loved.socialMode = RandomSocialMode.Off;
			return get_loved;
		}
	}
	public class JobDriver_SexBaseRecieverQuickie : JobDriver_SexBaseRecieverLoved
	{
	}
}


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

namespace rjw
{
	public class JobDriver_SexBaseRecieverRaped : JobDriver_SexBaseReciever
	{
		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();
			parteners.Add(Partner);// add job starter, so this wont fail, before Initiator starts his job

			var get_raped = new Toil();
			get_raped.defaultCompleteMode = ToilCompleteMode.Never;
			get_raped.handlingFacing = true;
			get_raped.initAction = delegate
			{
				pawn.pather.StopDead();
				pawn.jobs.curDriver.asleep = false;

				SexUtility.BeeingRapedAlert(Partner, pawn);
			};
			get_raped.tickAction = delegate
			{
				--ticks_remaining;
				if ((parteners.Count > 0) && (pawn.IsHashIntervalTick(ticks_between_hearts / parteners.Count)))
					if (pawn.IsHashIntervalTick(ticks_between_hearts))
						if (xxx.is_masochist(pawn))
							ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
						else
							ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
			};
			get_raped.AddEndCondition(new Func<JobCondition>(() =>
			{
				if ((ticks_remaining <= 0) || (parteners.Count <= 0))
				{
					return JobCondition.Succeeded;
				}
				return JobCondition.Ongoing;
			}));
			get_raped.AddFinishAction(delegate
			{
				if (xxx.is_human(pawn))
					pawn.Drawer.renderer.graphics.ResolveApparelGraphics();

				if (Bed != null && pawn.Downed)
				{
					Job tobed = JobMaker.MakeJob(JobDefOf.Rescue, pawn, Bed);
					tobed.count = 1;
					Partner.jobs.jobQueue.EnqueueFirst(tobed);
					//Log.Message(xxx.get_pawnname(Initiator) + ": job tobed:" + tobed);
				}
				else if (pawn.HostileTo(Partner))
					pawn.health.AddHediff(HediffDef.Named("Hediff_Submitting"));
				else
					pawn.stances.stunner.StunFor(600, pawn);
			});
			get_raped.socialMode = RandomSocialMode.Off;
			yield return get_raped;

		}
	}
}


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

namespace rjw
{
	public class JobDriver_JoinInBed : JobDriver_SexBaseInitiator
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//ModLog.Message("" + this.GetType().ToString() + "::MakeNewToils() called");

			setup_ticks();

			this.FailOnDespawnedOrNull(iTarget);
			this.FailOnDespawnedOrNull(iBed);
			this.FailOn(() => !Partner.health.capacities.CanBeAwake);
			this.FailOn(() => !(Partner.InBed() || xxx.in_same_bed(Partner, pawn)));
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Reserve.Reserve(iTarget, xxx.max_rapists_per_prisoner, 0);
			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				Job gettin_loved = JobMaker.MakeJob(xxx.gettin_loved, pawn, Bed);
				Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced);
			};
			yield return StartPartnerJob;

			Toil SexToil = new Toil();
			SexToil.FailOn(() => Partner.CurJob.def != xxx.gettin_loved);
			SexToil.defaultCompleteMode = ToilCompleteMode.Never;
			SexToil.socialMode = RandomSocialMode.Off;
			SexToil.handlingFacing = true;
			SexToil.initAction = delegate
			{
				usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Partner);
				Start();
			};
			SexToil.AddPreTickAction(delegate
			{
				--ticks_left;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				SexTick(pawn, Partner);
				SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
			});
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, whoring: isWhoring, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using Multiplayer.API;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;

namespace rjw
{
	public class JobDriver_SexQuick : JobDriver_SexBaseInitiator
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//ModLog.Message("" + this.GetType().ToString() + "::MakeNewToils() called");
			setup_ticks();
			var PartnerJob = xxx.getting_quickie;

			this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !Partner.health.capacities.CanBeAwake);
			this.FailOn(() => pawn.IsFighting());
			this.FailOn(() => Partner.IsFighting());
			this.FailOn(() => pawn.Drafted);

			yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

			Toil findQuickieSpot = new Toil();
			findQuickieSpot.defaultCompleteMode = ToilCompleteMode.PatherArrival;
			findQuickieSpot.initAction = delegate
			{
				//Needs this earlier to decide if current place is good enough
				var all_pawns = pawn.Map.mapPawns.AllPawnsSpawned.Where(x
				=> x.Position.DistanceTo(pawn.Position) < 100
				&& xxx.is_human(x)
				&& x != pawn
				&& x != Partner
				).ToList();

				FloatRange temperature = pawn.ComfortableTemperatureRange();
				float cellTemp = pawn.Position.GetTemperature(pawn.Map);

				if (Partner.IsPrisonerInPrisonCell() || (!CasualSex_Helper.MightBeSeen(all_pawns,pawn.Position,pawn,Partner) && (cellTemp > temperature.min && cellTemp < temperature.max)))
				{
					ReadyForNextToil();
				}
				else
				{
					var spot = CasualSex_Helper.FindSexLocation(pawn, Partner);
					pawn.pather.StartPath(spot, PathEndMode.OnCell);
					Partner.jobs.StopAll();
					Job job = JobMaker.MakeJob(JobDefOf.GotoMindControlled, spot);
					Partner.jobs.StartJob(job, JobCondition.InterruptForced);
				}
			};
			yield return findQuickieSpot;

			Toil WaitForPartner = new Toil();
			WaitForPartner.defaultCompleteMode = ToilCompleteMode.Delay;
			WaitForPartner.initAction = delegate
			{
				ticksLeftThisToil = 5000;
			};
			WaitForPartner.tickAction = delegate
			{
				pawn.GainComfortFromCellIfPossible();
				if (pawn.Position.DistanceTo(Partner.Position) <= 1f)
				{
					ReadyForNextToil();
				}
			};
			yield return WaitForPartner;

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				Job gettingQuickie = JobMaker.MakeJob(PartnerJob, pawn, Partner);
				Partner.jobs.StartJob(gettingQuickie, JobCondition.InterruptForced);
			};
			yield return StartPartnerJob;

			Toil SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Never;
			SexToil.socialMode = RandomSocialMode.Off;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Partner);
				Start();
			};
			SexToil.AddPreTickAction(delegate
			{
				--ticks_left;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				SexTick(pawn, Partner);
				SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 1);
				if (ticks_left <= 0)
					ReadyForNextToil();
			});
			SexToil.AddFinishAction(delegate
			{
				End();
			});
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					//// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Partner, usedCondom: usedCondom, rape: isRape, sextype: sexType);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using System.Collections.Generic;
using Multiplayer.API;

namespace rjw
{
	//Rape to Prisoner of QuestPrisonerWillingToJoin
	class JobGiver_AIRapePrisoner : ThinkNode_JobGiver
	{
		[SyncMethod]
		public static Pawn find_victim(Pawn pawn, Map m)
		{
			float min_fuckability = 0.10f;                          // Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;                             // Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();      // Valid pawns and their fuckability
			Pawn chosentarget = null;                               // Final target pawn

			IEnumerable<Pawn> targets = m.mapPawns.AllPawns.Where(x
				=> x != pawn
				&& IsPrisonerOf(x, pawn.Faction)
				&& xxx.can_get_raped(x)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.Position.IsForbidden(pawn)
				);

			foreach (Pawn target in targets)
			{
				if (!xxx.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				float fuc = SexAppraiser.would_fuck(pawn, target, true, true);

				if (fuc > min_fuckability)
					if (xxx.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			if (valid_targets.Any())
			{
				avg_fuckability = valid_targets.Average(x => x.Value);

				// choose pawns to fuck with above average fuckability
				var valid_targetsFiltered = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFiltered.Any())
					chosentarget = valid_targetsFiltered.RandomElement().Key;
			}

			return chosentarget;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_AIRapePrisoner::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called ");

			if (!xxx.can_rape(pawn)) return null;

			if (SexUtility.ReadyForLovin(pawn) || xxx.is_hornyorfrustrated(pawn))
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (xxx.is_healthy(pawn))
				{
					Pawn prisoner = find_victim(pawn, pawn.Map);

					if (prisoner != null)
					{
						if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - found victim " + xxx.get_pawnname(prisoner));
						return JobMaker.MakeJob(xxx.RapeRandom, prisoner);
					}
				}
			}

			return null;
		}

		protected static bool IsPrisonerOf(Pawn pawn,Faction faction)
		{
			if (pawn?.guest == null) return false;
			return pawn.guest.HostFaction == faction && pawn.guest.IsPrisoner;
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Pawn tries to find animal to do loving/raping.
	/// </summary>
	public class JobGiver_Bestiality : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn pawn)
		{
			if (pawn.Drafted) return null;

			// Most checks are now done in ThinkNode_ConditionalBestiality

			if (!SexUtility.ReadyForLovin(pawn) && !xxx.is_frustrated(pawn))
				return null;

			Pawn target = BreederHelper.find_breeder_animal(pawn, pawn.Map);

			if (target == null) return null;

			if (xxx.can_rape(pawn))
			{
				return JobMaker.MakeJob(xxx.bestiality, target);
			}

			Building_Bed bed = pawn.ownership.OwnedBed;
			if (!xxx.can_be_fucked(pawn) || bed == null || !target.CanReach(bed, PathEndMode.OnCell, Danger.Some) || target.Downed) return null;

			// TODO: Should rename this to BestialityInBed or somesuch, since it's not limited to females.
			return JobMaker.MakeJob(xxx.bestialityForFemale, target, bed);
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Attempts to give a breeding job to an eligible animal.
	/// </summary>
	public class JobGiver_Breed : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn animal)
		{
			//ModLog.Message(" JobGiver_Breed::TryGiveJob( " + xxx.get_pawnname(animal) + " ) ReadyForLovin " + (SexUtility.ReadyForLovin(animal)));

			if (!SexUtility.ReadyForLovin(animal))
				return null;

			//ModLog.Message(" ready to breed::is_healthy " + xxx.is_healthy(animal) + " can_rape " + xxx.can_rape(animal));
			if (xxx.is_healthy(animal) && xxx.can_rape(animal))
			{
				//search for desiganted target to sex
				Pawn designated_target = BreederHelper.find_designated_breeder(animal, animal.Map);
				if (designated_target != null)
				{
					return JobMaker.MakeJob(xxx.animalBreed, designated_target);
				}
			}
			return null;
		}
	}
}


code:
using Verse;
using Verse.AI;
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Multiplayer.API;

namespace rjw
{
	public class JobGiver_ComfortPrisonerRape : ThinkNode_JobGiver
	{
		[SyncMethod]
		public static Pawn find_targetCP(Pawn pawn, Map m)
		{
			if (!DesignatorsData.rjwComfort.Any()) return null;

			float min_fuckability = 0.10f;							// Don't rape prisoners with <10% fuckability
			float avg_fuckability = 0f;								// Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();		// Valid pawns and their fuckability
			Pawn chosentarget = null;               // Final target pawn

			string pawnName = xxx.get_pawnname(pawn);
			if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({pawnName})");

			IEnumerable<Pawn> targets = DesignatorsData.rjwComfort.Where(x
				=> x != pawn
				&& xxx.can_get_raped(x)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.IsForbidden(pawn)
				&& SexAppraiser.would_rape(pawn, x)
				);

			if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({pawnName}): found {targets.Count()}");

			if (xxx.is_animal(pawn))
			{
				// Animals only consider targets they can see, instead of seeking them out.
				targets = targets.Where(x => pawn.CanSee(x)).ToList();
			}

			foreach (Pawn target in targets)
			{
				if (!xxx.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				float fuc = 0f;
				if (xxx.is_animal(target))
					fuc = SexAppraiser.would_fuck_animal(pawn, target, true);
				else if (xxx.is_human(target))
					fuc = SexAppraiser.would_fuck(pawn, target, true);

				if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({pawnName}): {fuc} has to be over {min_fuckability}");

				if (fuc > min_fuckability)
					if (xxx.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			if (valid_targets.Any())
			{
				// avg_fuckability = valid_targets.Average(x => x.Value); // disabled for CP

				// choose pawns to fuck with above average fuckability
				var valid_targetsFiltered = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFiltered.Any())
					chosentarget = valid_targetsFiltered.RandomElement().Key;
			}

			return chosentarget;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			if (RJWSettings.DebugRape) ModLog.Message($"JobGiver_ComfortPrisonerRape::TryGiveJob({xxx.get_pawnname(pawn)}) called");

			if (!RJWSettings.WildMode)
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (RJWSettings.DebugRape) ModLog.Message($"JobGiver_ComfortPrisonerRape::TryGiveJob({xxx.get_pawnname(pawn)}): is healthy = {xxx.is_healthy(pawn)}, is cp = {pawn.IsDesignatedComfort()}, is ready = {SexUtility.ReadyForLovin(pawn)}, is frustrated = {xxx.is_frustrated(pawn)}");
				if (!xxx.is_healthy(pawn) || pawn.IsDesignatedComfort() || (!SexUtility.ReadyForLovin(pawn) && !xxx.is_frustrated(pawn))) return null;
			}

			if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({xxx.get_pawnname(pawn)}): can rape = {xxx.can_rape(pawn)}, is drafted = {pawn.Drafted}");
			if (pawn.Drafted || !xxx.can_rape(pawn)) return null;

			// It's unnecessary to include other job checks. Pawns seem to only look for new jobs when between jobs or laying down idle.
			if (!(pawn.jobs.curJob == null || pawn.jobs.curJob.def == JobDefOf.LayDown))
			{
				if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({xxx.get_pawnname(pawn)}): I already have a job ({pawn.CurJobDef})");
				return null;
			}

			// Faction check.
			if (!(pawn.Faction?.IsPlayer ?? false) && !pawn.IsPrisonerOfColony)
			{
				if (RJWSettings.DebugRape) ModLog.Message($"FindComfortPrisoner({xxx.get_pawnname(pawn)}): player faction: {pawn.Faction?.IsPlayer}, prisoner: {pawn.IsPrisonerOfColony}");
				return null;
			}

			Pawn target = find_targetCP(pawn, pawn.Map);
			if (RJWSettings.DebugRape) ModLog.Message($"JobGiver_ComfortPrisonerRape::TryGiveJob({xxx.get_pawnname(pawn)}): (" + ((target == null) ? "no target found" : xxx.get_pawnname(target))+") is the prisoner");

			if (target == null) return null;

			if (RJWSettings.DebugRape) ModLog.Message($"JobGiver_ComfortPrisonerRape::TryGiveJob({xxx.get_pawnname(pawn)}) with target {xxx.get_pawnname(target)}");
			if (xxx.is_animal(target))
				return JobMaker.MakeJob(xxx.bestiality, target);
			else
				return JobMaker.MakeJob(xxx.RapeCP, target);
		}
	}
}


code:
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	public class JobGiver_DoQuickie : ThinkNode_JobGiver
	{
		/// <summary> Checks all of our potential partners to see if anyone's eligible, returning the most attractive and convenient one. </summary>
		protected override Job TryGiveJob(Pawn pawn)
		{
			if (!RJWHookupSettings.HookupsEnabled || !RJWHookupSettings.QuickHookupsEnabled)
				return null;

			if (pawn.Drafted)
				return null;

			if (!SexUtility.ReadyForHookup(pawn))
				return null;

			// We increase the time right away to prevent the fairly expensive check from happening too frequently
			SexUtility.IncreaseTicksToNextHookup(pawn);

			// If the pawn is a whore, or recently had sex, skip the job unless they're really horny
			if (!xxx.is_frustrated(pawn) && (xxx.is_whore(pawn) || !SexUtility.ReadyForLovin(pawn)))
				return null;

			// This check attempts to keep groups leaving the map, like guests or traders, from turning around to hook up
			if (pawn.mindState?.duty?.def == DutyDefOf.TravelOrLeave)
			{
				// TODO: Some guest pawns keep the TravelOrLeave duty the whole time, I think the ones assigned to guard the pack animals.
				// That's probably ok, though it wasn't the intention.
				if (RJWSettings.DebugLogJoinInBed) ModLog.Message($" Quickie.TryGiveJob:({xxx.get_pawnname(pawn)}): has TravelOrLeave, no time for lovin!");
				return null;
			}

			if (pawn.CurJob == null)
			{
				//--Log.Message("   checking pawn and abilities");
				if (CasualSex_Helper.CanHaveSex(pawn))
				{
					//--Log.Message("   finding partner");
					Pawn partner = CasualSex_Helper.find_partner(pawn, pawn.Map, false);

					//--Log.Message("   checking partner");
					if (partner == null)
						return null;

					// Interrupt current job.
					if (pawn.CurJob != null && pawn.jobs.curDriver != null)
						pawn.jobs.curDriver.EndJobWith(JobCondition.InterruptForced);

					//--Log.Message("   returning job");
					return JobMaker.MakeJob(xxx.quick_sex, partner);
				}
			}

			return null;
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	public class JobGiver_JoinInBed : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn pawn)
		{
			if (!RJWHookupSettings.HookupsEnabled)
				return null;

			if (pawn.Drafted)
				return null;

			if (!SexUtility.ReadyForHookup(pawn))
				return null;

			// We increase the time right away to prevent the fairly expensive check from happening too frequently
			SexUtility.IncreaseTicksToNextHookup(pawn);

			// If the pawn is a whore, or recently had sex, skip the job unless they're really horny
			if (!xxx.is_frustrated(pawn) && (xxx.is_whore(pawn) || !SexUtility.ReadyForLovin(pawn)))
				return null;

			// This check attempts to keep groups leaving the map, like guests or traders, from turning around to hook up
			if (pawn.mindState?.duty?.def == DutyDefOf.TravelOrLeave)
			{
				// TODO: Some guest pawns keep the TravelOrLeave duty the whole time, I think the ones assigned to guard the pack animals.
				// That's probably ok, though it wasn't the intention.
				if (RJWSettings.DebugLogJoinInBed) ModLog.Message($"JoinInBed.TryGiveJob:({xxx.get_pawnname(pawn)}): has TravelOrLeave, no time for lovin!");
				return null;
			}

			if (pawn.CurJob == null || pawn.CurJob.def == JobDefOf.LayDown)
			{
				//--Log.Message("   checking pawn and abilities");
				if (CasualSex_Helper.CanHaveSex(pawn))
				{
					//--Log.Message("   finding partner");
					Pawn partner = CasualSex_Helper.find_partner(pawn, pawn.Map, true);

					//--Log.Message("   checking partner");
					if (partner == null)
						return null;

					// Can never be null, since find checks for bed.
					Building_Bed bed = partner.CurrentBed();

					// Interrupt current job.
					if (pawn.CurJob != null && pawn.jobs.curDriver != null)
						pawn.jobs.curDriver.EndJobWith(JobCondition.InterruptForced);

					//--Log.Message("   returning job");
					return JobMaker.MakeJob(xxx.casual_sex, partner, bed);
				}
			}

			return null;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Attempts to give a lay egg job to an eligible humanoid.
	/// </summary>
	public class JobGiver_LayEgg : RimWorld.JobGiver_LayEgg
	{

	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using System.Collections.Generic;
using System.Linq;
using Multiplayer.API;

namespace rjw
{
	public class JobGiver_Masturbate : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn pawn)
		{
			//--ModLog.Message(" JobGiver_Masturbate::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called");

			if (pawn.Drafted) return null;

			if (!xxx.can_be_fucked(pawn) && !xxx.can_fuck(pawn)) return null;

			// Whores only fap if frustrated, unless imprisoned.
			if ((SexUtility.ReadyForLovin(pawn) && (!xxx.is_whore(pawn) || pawn.IsPrisoner || xxx.is_slave(pawn))) || xxx.is_frustrated(pawn))
			{
				if (RJWPreferenceSettings.FapInBed && pawn.jobs.curDriver is JobDriver_LayDown)
				{
					Building_Bed bed = ((JobDriver_LayDown)pawn.jobs.curDriver).Bed;

					if (bed != null)
					{
						if ((xxx.is_frustrated(pawn) || xxx.has_quirk(pawn, "Exhibitionist")) || bed.GetRoom().Role == RoomRoleDefOf.Bedroom || bed.GetRoom().Role == RoomRoleDefOf.PrisonCell)
							return JobMaker.MakeJob(xxx.Masturbate, null, bed, bed.Position);
					}
				}
				else if (RJWPreferenceSettings.FapEverywhere && (xxx.is_frustrated(pawn) || xxx.has_quirk(pawn, "Exhibitionist")))
				{
					return JobMaker.MakeJob(xxx.Masturbate, null, null, CasualSex_Helper.FindSexLocation(pawn));
				}
			}
			return null;
		}
	}
}


code:
using System.Linq;
using RimWorld;
using Verse;
using Verse.AI;
using System.Collections.Generic;
using Multiplayer.API;

namespace rjw
{
	public class JobGiver_RandomRape : ThinkNode_JobGiver
	{
		[SyncMethod]
		public Pawn find_victim(Pawn pawn, Map m)
		{
			float min_fuckability = 0.10f;							// Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;                             // Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Pawn, float>();      // Valid pawns and their fuckability
			Pawn chosentarget = null;                               // Final target pawn

			// could be prisoner, colonist, or non-hostile outsider
			IEnumerable<Pawn> targets = m.mapPawns.AllPawnsSpawned.Where(x
				=> x != pawn
				&& xxx.is_not_dying(x)
				&& xxx.can_get_raped(x)
				&& !x.Suspended
				&& !x.Drafted
				&& !x.IsForbidden(pawn)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.HostileTo(pawn)
				);

			//Zoo rape Animal
			if (xxx.is_zoophile(pawn) && RJWSettings.bestiality_enabled)
			{
				foreach (Pawn target in targets.Where(x => xxx.is_animal(x)))
				{
					if (!xxx.cells_to_target_rape(pawn, target.Position))
						continue;// too far

					float fuc = SexAppraiser.would_fuck(pawn, target, true, true);

					if (fuc > min_fuckability)
						if (xxx.can_path_to_target(pawn, target.Position))
							valid_targets.Add(target, fuc);
				}

				if (valid_targets.Any())
				{
					avg_fuckability = valid_targets.Average(x => x.Value);

					// choose pawns to fuck with above average fuckability
					var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

					if (valid_targetsFilteredAnimals.Any())
						chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;

					return chosentarget;
				}
			}

			valid_targets = new Dictionary<Pawn, float>();
			// rape Humanlike
			foreach (Pawn target in targets.Where(x => !xxx.is_animal(x)))
			{
				if (!xxx.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				float fuc = SexAppraiser.would_fuck(pawn, target, true, true);

				if (fuc > min_fuckability)
					if (xxx.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			if (valid_targets.Any())
			{
				avg_fuckability = valid_targets.Average(x => x.Value);

				// choose pawns to fuck with above average fuckability
				var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFilteredAnimals.Any())
					chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
			}

			return chosentarget;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			//ModLog.Message(" JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called");
			if (!xxx.can_rape(pawn)) return null;

			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("Hediff_RapeEnemyCD"))) return null;
			pawn.health.AddHediff(HediffDef.Named("Hediff_RapeEnemyCD"), null, null, null);

			Pawn victim = find_victim(pawn, pawn.Map);
			if (victim == null) return null;

			//ModLog.Message(" JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - found victim " + xxx.get_pawnname(victim));
			return JobMaker.MakeJob(xxx.RapeRandom, victim);
		}
	}
}


code:
using Verse;
using Verse.AI;
using RimWorld;

namespace rjw
{
	/// <summary>
	/// Pawn try to find enemy to rape.
	/// </summary>
	public class JobGiver_RapeEnemy : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn pawn)
		{
			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called0");
			//ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 0 " + SexUtility.ReadyForLovin(pawn));
			//ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 1 " + (xxx.need_some_sex(pawn) <= 1f));
			//ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 2 " + !(SexUtility.ReadyForLovin(pawn) || xxx.need_some_sex(pawn) <= 1f));
			//ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 1 " + Find.TickManager.TicksGame);
			//ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 2 " + pawn.mindState.canLovinTick);

			if (pawn.Drafted) return null;

			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("Hediff_RapeEnemyCD")) || !pawn.health.capacities.CanBeAwake || !(SexUtility.ReadyForLovin(pawn) || xxx.need_some_sex(pawn) <= 1f))
			//if (pawn.health.hediffSet.HasHediff(HediffDef.Named("Hediff_RapeEnemyCD")) || !pawn.health.capacities.CanBeAwake || (SexUtility.ReadyForLovin(pawn) || xxx.is_human(pawn) ? xxx.need_some_sex(pawn) <= 1f : false))
				return null;

			if (!xxx.can_rape(pawn)) return null;
			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) can rape");

			JobDef_RapeEnemy rapeEnemyJobDef = null;
			int? highestPriority = null;
			foreach (JobDef_RapeEnemy job in DefDatabase<JobDef_RapeEnemy>.AllDefs)
			{
				if (job.CanUseThisJobForPawn(pawn))
				{
					if (highestPriority == null)
					{
						rapeEnemyJobDef = job;
						highestPriority = job.priority;
					}
					else if (job.priority > highestPriority)
					{
						rapeEnemyJobDef = job;
						highestPriority = job.priority;
					}
				}
			}

			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::ChoosedJobDef( " + xxx.get_pawnname(pawn) + " ) - " + rapeEnemyJobDef.ToString() + " choosen");
			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::ChoosedJobDef( " + xxx.get_pawnname(pawn) + " ) - " + rapeEnemyJobDef.ToString() + " choosen");
			Pawn victim = rapeEnemyJobDef?.FindVictim(pawn, pawn.Map);

			if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::FoundVictim( " + xxx.get_pawnname(victim) + " )");

			//prevents 10 job stacks error, no idea whats the prob with JobDriver_Rape
			//if (victim != null)
			pawn.health.AddHediff(HediffDef.Named("Hediff_RapeEnemyCD"), null, null, null);

			return victim != null ? JobMaker.MakeJob(rapeEnemyJobDef, victim) : null;
			/*
			else
			{
				if (RJWSettings.DebugRape) ModLog.Message("" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - unable to find victim");
				pawn.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
			}
			*/
			//else {  if (RJWSettings.DebugRape) ModLog.Message(" JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - too fast to play next"); }
		}
	}
}


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

namespace rjw
{
	public class JobGiver_ViolateCorpse : ThinkNode_JobGiver
	{
		public static Corpse find_corpse(Pawn pawn, Map m)
		{
			float min_fuckability = 0.10f;                            // Don't rape pawns with <10% fuckability
			float avg_fuckability = 0f;                               // Average targets fuckability, choose target higher than that
			var valid_targets = new Dictionary<Corpse, float>();      // Valid pawns and their fuckability
			Corpse chosentarget = null;                               // Final target pawn

			IEnumerable<Thing> targets = m.spawnedThings.Where(x
				=> x is Corpse
				&& pawn.CanReserveAndReach(x, PathEndMode.OnCell, Danger.Some)
				&& !x.IsForbidden(pawn)
				);

			foreach (Corpse target in targets)
			{
				if (!xxx.cells_to_target_rape(pawn, target.Position))
					continue;// too far

				// Filter out rotters if not necrophile.
				if (!xxx.is_necrophiliac(pawn) && target.CurRotDrawMode != RotDrawMode.Fresh)
					continue;

				float fuc = SexAppraiser.would_fuck(pawn, target, false, false);

				if (fuc > min_fuckability)
					if (xxx.can_path_to_target(pawn, target.Position))
						valid_targets.Add(target, fuc);
			}

			if (valid_targets.Any())
			{
				avg_fuckability = valid_targets.Average(x => x.Value);

				// choose pawns to fuck with above average fuckability
				var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

				if (valid_targetsFilteredAnimals.Any())
					chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
			}

			return chosentarget;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			// Most checks are done in ThinkNode_ConditionalNecro.

			// filter out necro for nymphs
			if (!RJWSettings.necrophilia_enabled) return null;

			if (pawn.Drafted) return null;

			//--ModLog.Message(" JobGiver_ViolateCorpse::TryGiveJob for ( " + xxx.get_pawnname(pawn) + " )");
			if (SexUtility.ReadyForLovin(pawn) || xxx.is_hornyorfrustrated(pawn))
			{
				//--ModLog.Message(" JobGiver_ViolateCorpse::TryGiveJob, can love ");
				if (!xxx.can_rape(pawn)) return null;

				var target = find_corpse(pawn, pawn.Map);
				//--ModLog.Message(" JobGiver_ViolateCorpse::TryGiveJob - target is " + (target == null ? "NULL" : "Found"));
				if (target != null)
				{
					return JobMaker.MakeJob(xxx.RapeCorpse, target);
				}
				// Ticks should only be increased after successful sex.
			}

			return null;
		}
	}
}


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

namespace rjw.MainTab
{
	public class MainTabWindow_Brothel : MainTabWindow_PawnTable
	{
		private static PawnTableDef pawnTableDef;

		protected override PawnTableDef PawnTableDef => pawnTableDef ?? (pawnTableDef = DefDatabase<PawnTableDef>.GetNamed("Brothel"));

		protected override IEnumerable<Pawn> Pawns => Find.CurrentMap.mapPawns.AllPawns.Where(p => xxx.is_human(p) && (p.IsColonist || p.IsPrisonerOfColony));

		public override void PostOpen()
		{
			base.PostOpen();
			Find.World.renderer.wantedMode = WorldRenderMode.None;
		}
	}
}


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

namespace rjw.MainTab
{
	public abstract class PawnColumnCheckbox_Whore : PawnColumnWorker
	{
		public const int HorizontalPadding = 2;

		public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
		{
			if (!this.HasCheckbox(pawn))
			{
				return;
			}
			int num = (int)((rect.width - 24f) / 2f);
			int num2 = Mathf.Max(3, 0);
			Vector2 vector = new Vector2(rect.x + (float)num, rect.y + (float)num2);
			Rect rect2 = new Rect(vector.x, vector.y, 24f, 24f);
			if (Find.TickManager.TicksGame % 60 == 0)
			{
				pawn.UpdatePermissions();
				//Log.Message("GetDisabled UpdateCanDesignateService for " + xxx.get_pawnname(pawn));
				//Log.Message("UpdateCanDesignateService " + pawn.UpdateCanDesignateService());
				//Log.Message("CanDesignateService " + pawn.CanDesignateService());
				//Log.Message("GetDisabled " + GetDisabled(pawn));
			}
			bool disabled = this.GetDisabled(pawn);
			bool value;
			if (disabled)
			{
				value = false;
			}
			else
			{
				value = this.GetValue(pawn);
			}

			bool flag = value;
			Vector2 topLeft = vector;
			WhoreCheckbox.Checkbox(topLeft, ref value, 24f, disabled, WhoreCheckbox.WhoreCheckboxOnTex, WhoreCheckbox.WhoreCheckboxOffTex, WhoreCheckbox.WhoreCheckboxDisabledTex);
			if (Mouse.IsOver(rect2))
			{
				string tip = this.GetTip(pawn);
				if (!tip.NullOrEmpty())
				{
					TooltipHandler.TipRegion(rect2, tip);
				}
			}
			if (value != flag)
			{
				this.SetValue(pawn, value);
			}
		}

		public override int GetMinWidth(PawnTable table)
		{
			return Mathf.Max(base.GetMinWidth(table), 28);
		}

		public override int GetMaxWidth(PawnTable table)
		{
			return Mathf.Min(base.GetMaxWidth(table), this.GetMinWidth(table));
		}

		public override int GetMinCellHeight(Pawn pawn)
		{
			return Mathf.Max(base.GetMinCellHeight(pawn), 24);
		}

		public override int Compare(Pawn a, Pawn b)
		{
			return this.GetValueToCompare(a).CompareTo(this.GetValueToCompare(b));
		}

		private int GetValueToCompare(Pawn pawn)
		{
			if (!this.HasCheckbox(pawn))
			{
				return 0;
			}
			if (!this.GetValue(pawn))
			{
				return 1;
			}
			return 2;
		}

		protected virtual string GetTip(Pawn pawn)
		{
			return null;
		}

		protected virtual bool HasCheckbox(Pawn pawn)
		{
			return true;
		}

		protected abstract bool GetValue(Pawn pawn);

		protected abstract void SetValue(Pawn pawn, bool value);

		protected abstract bool GetDisabled(Pawn pawn);

		protected override void HeaderClicked(Rect headerRect, PawnTable table)
		{
			base.HeaderClicked(headerRect, table);
			if (Event.current.shift)
			{
				List<Pawn> pawnsListForReading = table.PawnsListForReading;
				for (int i = 0; i < pawnsListForReading.Count; i++)
				{
					if (this.HasCheckbox(pawnsListForReading[i]))
					{
						if (Event.current.button == 0)
						{
							if (!this.GetValue(pawnsListForReading[i]))
							{
								this.SetValue(pawnsListForReading[i], true);
							}
						}
						else if (Event.current.button == 1 && this.GetValue(pawnsListForReading[i]))
						{
							this.SetValue(pawnsListForReading[i], false);
						}
					}
				}
				if (Event.current.button == 0)
				{
					SoundDefOf.Checkbox_TurnedOn.PlayOneShotOnCamera(null);
				}
				else if (Event.current.button == 1)
				{
					SoundDefOf.Checkbox_TurnedOff.PlayOneShotOnCamera(null);
				}
			}
		}

		protected override string GetHeaderTip(PawnTable table)
		{
			return base.GetHeaderTip(table) + "\n" + "CheckboxShiftClickTip".Translate();
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_AverageMoneyByWhore : PawnColumnWorker_TextCenter
	{
		protected override string GetTextFor(Pawn pawn)
		{
			return ((int)GetValueToCompare(pawn)).ToString();
		}

		public override int Compare(Pawn a, Pawn b)
		{
			return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
		}

		private float GetValueToCompare(Pawn pawn)
		{
			float total = pawn.records.GetValue(xxx.EarnedMoneyByWhore);
			float count = pawn.records.GetValue(xxx.CountOfWhore);
			if ((int)count == 0)
			{
				return 0;
			}
			return (total / count);
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_CountOfWhore : PawnColumnWorker_TextCenter
	{
		protected override string GetTextFor(Pawn pawn)
		{
			return GetValueToCompare(pawn).ToString();
		}

		public override int Compare(Pawn a, Pawn b)
		{
			return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
		}

		private int GetValueToCompare(Pawn pawn)
		{
			return pawn.records.GetAsInt(xxx.CountOfWhore);
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_EarnedMoneyByWhore : PawnColumnWorker_TextCenter
	{
		protected override string GetTextFor(Pawn pawn)
		{
			return GetValueToCompare(pawn).ToString();
		}

		public override int Compare(Pawn a, Pawn b)
		{
			return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
		}

		private int GetValueToCompare(Pawn pawn)
		{
			return pawn.records.GetAsInt(xxx.EarnedMoneyByWhore);
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsPrisoner : PawnColumnWorker_Icon
	{
		private static readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_on");
		private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_off");

		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return pawn.IsPrisonerOfColony || xxx.is_slave(pawn) ? comfortOn : null;
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsWhore : PawnColumnCheckbox_Whore
	{
		protected override bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateService();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedService() && xxx.is_human(pawn);
		}

		protected override void SetValue(Pawn pawn, bool value)
		{
			if (value == this.GetValue(pawn)) return;

			pawn.ToggleService();
		}
		/*
		private static readonly Texture2D serviceOn = ContentFinder<Texture2D>.Get("UI/Tab/Service_on");
		private static readonly Texture2D serviceOff = ContentFinder<Texture2D>.Get("UI/Tab/Service_off");

		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return pawn.IsDesignatedService() ? serviceOn : null;
		}*/
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_Mood : PawnColumnWorker_TextCenter
	{
		protected override string GetTextFor(Pawn pawn)
		{
			return GetValueToCompare(pawn).ToStringPercent();
		}

		public override int Compare(Pawn a, Pawn b)
		{
			return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
		}

		private float GetValueToCompare(Pawn pawn)
		{
			return pawn.needs.mood.CurLevelPercentage;
		}
	}
}