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

using RimWorld;
using Verse;

namespace rjw
{
	internal class InteractionWorker_AnalSexAttempt : InteractionWorker
	{
		//initiator - rapist
		//recipient - victim
		public static bool AttemptAnalSex(Pawn initiator, Pawn recipient)
		{
			//--Log.Message(xxx.get_pawnname(initiator) + " is attempting to anally rape " + xxx.get_pawnname(recipient));
			return true;
		}

		public override float RandomSelectionWeight(Pawn initiator, Pawn recipient)
		{
			// this interaction is triggered by the jobdriver
			if (initiator == null || recipient == null)
				return 0.0f;

			return 0.0f; // base.RandomSelectionWeight(initiator, recipient);
		}

		public override void Interacted(Pawn initiator, Pawn recipient, List<RulePackDef> extraSentencePacks, out string letterText, out string letterLabel, out LetterDef letterDef)
		{
			//add something fancy here later?
			letterText = null;
			letterLabel = null;
			letterDef = null;
			//Find.LetterStack.ReceiveLetter("Rape attempt", "A wandering nymph has decided to join your colony.", LetterDefOf.NegativeEvent, recipient);

			if (initiator == null || recipient == null)
				return;
			//--Log.Message("[RJW] InteractionWorker_AnalRapeAttempt::Interacted( " + xxx.get_pawnname(initiator) + ", " + xxx.get_pawnname(recipient) + " ) called");
			AttemptAnalSex(initiator, recipient);
		}
	}

	internal class InteractionWorker_VaginalSexAttempt : InteractionWorker
	{
		//initiator - rapist
		//recipient - victim
		public static bool AttemptAnalSex(Pawn initiator, Pawn recipient)
		{
			//--Log.Message(xxx.get_pawnname(initiator) + " is attempting to anally rape " + xxx.get_pawnname(recipient));
			return false;
		}

		public override float RandomSelectionWeight(Pawn initiator, Pawn recipient)
		{
			// this interaction is triggered by the jobdriver
			if (initiator == null || recipient == null)
				return 0.0f;

			return 0.0f; // base.RandomSelectionWeight(initiator, recipient);
		}

		public override void Interacted(Pawn initiator, Pawn recipient, List<RulePackDef> extraSentencePacks, out string letterText, out string letterLabel, out LetterDef letterDef)
		{
			//add something fancy here later?
			letterText = null;
			letterLabel = null;
			letterDef = null;
			//Find.LetterStack.ReceiveLetter("Rape attempt", "A wandering nymph has decided to join your colony.", LetterDefOf.NegativeEvent, recipient);

			if (initiator == null || recipient == null)
				return;
			//--Log.Message("[RJW] InteractionWorker_AnalRapeAttempt::Interacted( " + xxx.get_pawnname(initiator) + ", " + xxx.get_pawnname(recipient) + " ) called");
			AttemptAnalSex(initiator, recipient);
		}
	}

	internal class InteractionWorker_OtherSexAttempt : InteractionWorker
	{
		//initiator - rapist
		//recipient - victim
		public static bool AttemptAnalSex(Pawn initiator, Pawn recipient)
		{
			//--Log.Message(xxx.get_pawnname(initiator) + " is attempting to anally rape " + xxx.get_pawnname(recipient));
			return false;
		}

		public override float RandomSelectionWeight(Pawn initiator, Pawn recipient)
		{
			// this interaction is triggered by the jobdriver
			if (initiator == null || recipient == null)
				return 0.0f;

			return 0.0f; // base.RandomSelectionWeight(initiator, recipient);
		}

		public override void Interacted(Pawn initiator, Pawn recipient, List<RulePackDef> extraSentencePacks, out string letterText, out string letterLabel, out LetterDef letterDef)
		{
			//add something fancy here later?
			letterText = null;
			letterLabel = null;
			letterDef = null;
			//Find.LetterStack.ReceiveLetter("Rape attempt", "A wandering nymph has decided to join your colony.", LetterDefOf.NegativeEvent, recipient);

			if (initiator == null || recipient == null)
				return;
			//--Log.Message("[RJW] InteractionWorker_AnalRapeAttempt::Interacted( " + xxx.get_pawnname(initiator) + ", " + xxx.get_pawnname(recipient) + " ) called");
			AttemptAnalSex(initiator, recipient);
		}
	}
}


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

namespace rjw
{
	public class JobDriver_BestialityForFemale : JobDriver
	{
		private readonly TargetIndex PartnerInd = TargetIndex.A;
		private readonly TargetIndex BedInd = TargetIndex.B;
		private readonly TargetIndex SlotInd = TargetIndex.C;
		private int ticks_left = 200;
		private const int ticks_between_hearts = 100;

		public Pawn Actor => GetActor();
		public Pawn Partner => (Pawn)(job.GetTarget(PartnerInd));
		public Building_Bed Bed => (Building_Bed)(job.GetTarget(BedInd));
		public IntVec3 SleepSpot => (IntVec3)job.GetTarget(SlotInd);

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref ticks_left, "ticksLeft", 0, false);
		}

		private 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 pawn.Reserve(Partner, job, 1, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			this.FailOnDespawnedOrNull(PartnerInd);
			this.FailOnDespawnedNullOrForbidden(BedInd);
			this.FailOn(() => Actor is null || !Actor.CanReserveAndReach(Partner, PathEndMode.Touch, Danger.Deadly));
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Reserve.Reserve(PartnerInd, 1, 0);
			//yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);
			Toil gotoAnimal = Toils_Goto.GotoThing(PartnerInd, PathEndMode.Touch);
			yield return gotoAnimal;

			bool partnerHasPenis = Genital_Helper.has_penis(Partner) || Genital_Helper.has_penis_infertile(Partner);

			Toil gotoBed = new Toil
			{
				initAction = delegate
				{
					Actor.pather.StartPath(SleepSpot, PathEndMode.OnCell);
					Partner.pather.StartPath(SleepSpot, PathEndMode.OnCell);
				},
				defaultCompleteMode = ToilCompleteMode.PatherArrival
			};
			gotoBed.FailOnBedNoLongerUsable(BedInd);
			gotoBed.AddFailCondition(() => Partner.Downed);
			yield return gotoBed;
			gotoBed.AddFinishAction(delegate
			{
				var gettin_loved = new Job(xxx.gettin_loved, Actor, Bed);
				Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced, null, false, true, null);
			});

			Toil waitInBed = new Toil
			{
				initAction = delegate
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					ticksLeftThisToil = 5000;
					ticks_left = (int)(2000.0f * Rand.Range(0.30f, 1.30f));
				},
				tickAction = delegate
				{
					Actor.GainComfortFromCellIfPossible();
					if (IsInOrByBed(Bed, Partner))
					{
						ticksLeftThisToil = 0;
					}
				},
				defaultCompleteMode = ToilCompleteMode.Delay,
			};
			waitInBed.FailOn(() => pawn.GetRoom(RegionType.Set_Passable) == null);
			yield return waitInBed;

			Toil loveToil = new Toil
			{
				initAction = delegate
				{
					if (!partnerHasPenis)
						Actor.rotationTracker.Face(Partner.DrawPos);
				},
				defaultCompleteMode = ToilCompleteMode.Never, //Changed from Delay
			};
			loveToil.AddPreTickAction(delegate
			{
				//Actor.Reserve(Partner, 1, 0);
				--ticks_left;
				xxx.reduce_rest(Actor, 1);
				xxx.reduce_rest(Partner, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
				else if (pawn.IsHashIntervalTick(ticks_between_hearts))
				{
					MoteMaker.ThrowMetaIcon(Actor.Position, Actor.Map, ThingDefOf.Mote_Heart);
				}
				Actor.GainComfortFromCellIfPossible();
				Partner.GainComfortFromCellIfPossible();
			});
			loveToil.AddFailCondition(() => Partner.Dead || !IsInOrByBed(Bed, Partner));
			loveToil.socialMode = RandomSocialMode.Off;
			yield return loveToil;

			Toil afterSex = new Toil
			{
				initAction = delegate
				{
					//Log.Message("JobDriver_BestialityForFemale::MakeNewToils() - Calling aftersex");
					// Trying to add some interactions and social logs
					SexUtility.ProcessSex(Partner, pawn);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
			yield return afterSex;
		}
	}
}

repo_name: randomtyping/rjw

license: mit


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
	{
		private int duration;
		private int ticks_between_hearts;
		private int ticks_between_hits = 50;
		private int ticks_between_thrusts;
		protected TargetIndex target_animal = TargetIndex.A;

		protected Pawn animal => (Pawn)(job.GetTarget(target_animal));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(animal, job, 1, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() called");
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			duration = (int)(2500.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);
			ticks_between_thrusts = 100;

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

			//this.FailOn (() => (!animal.health.capacities.CanBeAwake) || (!comfort_prisoners.is_designated (animal)));
			// Fail if someone else reserves the prisoner before the pawn arrives or colonist can't reach animal
			this.FailOn(() => !pawn.CanReserveAndReach(animal, PathEndMode.Touch, Danger.Deadly));
			this.FailOn(() => animal.HostileTo(pawn));
			this.FailOnDespawnedNullOrForbidden(target_animal);
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Reserve.Reserve(target_animal, 1, 0);
			//Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() - moving towards animal");
			yield return Toils_Goto.GotoThing(target_animal, PathEndMode.Touch);
			yield return Toils_Interpersonal.WaitToBeAbleToInteract(pawn);
			yield return Toils_Interpersonal.GotoInteractablePosition(target_animal);
			if (xxx.is_kind(pawn)
				|| (xxx.CTIsActive && xxx.has_traits(pawn) && pawn.story.traits.HasTrait(TraitDef.Named("RCT_AnimalLover"))))
			{
				yield return TalkToAnimal(pawn, animal);
				yield return TalkToAnimal(pawn, animal);
			}
			if (Rand.Chance(0.6f))
				yield return TalkToAnimal(pawn, animal);
			yield return Toils_Goto.GotoThing(target_animal, PathEndMode.OnCell);

			SexUtility.RapeAttemptAlert(pawn, animal);

			Toil rape = new Toil();
			rape.initAction = delegate
			{
				//--Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() - reserving animal");

				//--Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() - Setting animal job driver");
				if (!(animal.jobs.curDriver is JobDriver_GettinRaped dri))
				{
					//wild animals may flee or attack
					if (pawn.Faction != animal.Faction && animal.RaceProps.wildness > Rand.Range(0.22f, 1.0f)
						&& !(pawn.TicksPerMoveCardinal < (animal.TicksPerMoveCardinal / 2) && !animal.Downed && xxx.is_not_dying(animal)))
					{
						animal.jobs.StopAll(); // Wake up if sleeping.

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

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

								foreach (Pawn packmate in packmates)
								{
									packmate.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.Manhunter);
								}
							}
							Messages.Message(pawn.Name.ToStringShort + " is being attacked by " + xxx.get_pawnname(animal) + ".", pawn, MessageTypeDefOf.ThreatSmall);
						}
						else
						{
							MoteMaker.ThrowMetaIcon(animal.Position, animal.Map, ThingDefOf.Mote_ColonistFleeing);
							LifeStageUtility.PlayNearestLifestageSound(animal, (ls) => ls.soundCall);
							animal.mindState.StartFleeingBecauseOfPawnAction(pawn);
							animal.mindState.mentalStateHandler.TryStartMentalState(DefDatabase<MentalStateDef>.GetNamed("PanicFlee"));
						}
						pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
					}
					else
					{
						Job gettin_bred = new Job(xxx.gettin_bred, pawn, animal);
						animal.jobs.StartJob(gettin_bred, JobCondition.InterruptForced, null, true);
						(animal.jobs.curDriver as JobDriver_GettinRaped)?.increase_time(duration);
					}
				}
				else
				{
					dri.rapist_count += 1;
					dri.increase_time(duration);
				}
			};
			rape.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_thrusts))
					xxx.sexTick(pawn, animal, false);
				/*
				if (pawn.IsHashIntervalTick (ticks_between_hits))
					roll_to_hit (pawn, animal);
					*/
				xxx.reduce_rest(animal, 1);
				xxx.reduce_rest(pawn, 2);

			};
			rape.AddFinishAction(delegate
			{
				//--Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() - finished violating");
				if (animal.jobs?.curDriver is JobDriver_GettinRaped)
					(animal.jobs.curDriver as JobDriver_GettinRaped).rapist_count -= 1;

				if (xxx.is_human(pawn))
					pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
			});
			rape.defaultCompleteMode = ToilCompleteMode.Delay;
			rape.defaultDuration = duration;
			yield return rape;

			yield return new Toil
			{
				initAction = delegate
				{
					//Log.Message("[RJW] JobDriver_BestialityForMale::MakeNewToils() - creating aftersex toil");
					SexUtility.ProcessSex(pawn, animal);
				},
				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;
		}
	}
}


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
	{
		protected TargetIndex PartnerIndex = TargetIndex.A;

		public Pawn Target => (Pawn)(job.GetTarget(PartnerIndex));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, BreederHelper.max_animals_at_once, 0);
		}

		[SyncMethod]
		public virtual void roll_to_hit(Pawn pawn, Pawn partner)
		{
			if (!RJWSettings.rape_beating || !xxx.is_human(pawn))
				return;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float rand_value = Rand.Value;
			float victim_pain = partner.health.hediffSet.PainTotal;
			float chance_to_hit = xxx.config.base_chance_to_hit_prisoner/5;
			float threshold = xxx.config.minor_pain_threshold;

			if ((victim_pain < threshold && rand_value < chance_to_hit))
			{
				if (InteractionUtility.TryGetRandomVerbForSocialFight(pawn, out Verb v))
					pawn.meleeVerbs.TryMeleeAttack(partner, v);
			}
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			int duration = (int)(2000.0f * Rand.Range(0.50f, 0.90f));
			int ticks_between_hearts = Rand.RangeInclusive(70, 130);
			int ticks_between_hits = Rand.Range(xxx.config.min_ticks_between_hits, xxx.config.max_ticks_between_hits);
			int ticks_between_thrusts = 100;

			//--Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - setting fail conditions");
			this.FailOnDespawnedNullOrForbidden(PartnerIndex);
			this.FailOn(() => !pawn.CanReserve(Target, BreederHelper.max_animals_at_once, 0)); // Fail if someone else reserves the target before the animal arrives.
			this.FailOn(() => !pawn.CanReach(Target, PathEndMode.Touch, Danger.Some)); // Fail if animal cannot reach target.
			this.FailOn(() => !(Target.IsDesignatedBreeding() || (RJWSettings.animal_on_animal_enabled && xxx.is_animal(Target)))); // Fail if not designated and not animal-on-animal
			this.FailOn(() => Target.CurJob == null);
			this.FailOn(() => pawn.Drafted);

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

			SexUtility.RapeAttemptAlert(pawn, Target);

			// Breed target
			var breed = new Toil();
			breed.initAction = delegate
			{
				//Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - Setting victim job driver");
				Job currentJob = Target.jobs.curJob;

				if (currentJob.def != xxx.gettin_raped)
				{
					Job gettin_raped = new Job(xxx.gettin_raped, pawn, Target);
					Building_Bed Bed = null;
					//Log.Message(xxx.get_pawnname(pawn) + " LayingInBed:" + pawn.GetPosture());
					//Log.Message(xxx.get_pawnname(Target) + " LayingInBed:" + Target.GetPosture());
					if (Target.GetPosture() == PawnPosture.LayingInBed)
					{
						Bed = Target.CurrentBed();
						//Log.Message(xxx.get_pawnname(Target) + ": bed:" + Bed);
					}

					Target.jobs.StartJob(gettin_raped, JobCondition.InterruptForced);

					//var dri = Target.jobs.curDriver as JobDriver_GettinRaped;
					//if (xxx.is_animal(pawn) && xxx.is_animal(Target)) // No alert spam for animal-on-animal
						//dri.disable_alert = true;

					(Target.jobs.curDriver as JobDriver_GettinRaped).increase_time(duration);
					if (Bed != null)
						(Target.jobs.curDriver as JobDriver_GettinRaped)?.set_bed(Bed);
				}
				else
				{
					if (Target.jobs.curDriver is JobDriver_GettinRaped dri)
					{
						dri.rapist_count += 1;
						dri.increase_time(duration);
					}
				}
			};

			breed.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_thrusts))
					xxx.sexTick(pawn, Target);
				if (!xxx.is_zoophile(Target) && pawn.IsHashIntervalTick(ticks_between_hits))
					roll_to_hit(pawn, Target);

				if (!Target.Dead)
					xxx.reduce_rest(Target, 1);
				xxx.reduce_rest(pawn, 2);

				if (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn))
				{
					// Face same direction, most of animal sex is likely doggystyle.
					Target.Rotation = pawn.Rotation;
				}
			};

			breed.AddFinishAction(delegate
			{
				if ((Target.jobs != null)
				&& (Target.jobs.curDriver != null)
				&& (Target.jobs.curDriver as JobDriver_GettinRaped != null))
				{
					(Target.jobs.curDriver as JobDriver_GettinRaped).rapist_count -= 1;
				}
				pawn.stances.StaggerFor(Rand.Range(0,50));
				Target.stances.StaggerFor(Rand.Range(10,300));
			});

			breed.defaultCompleteMode = ToilCompleteMode.Delay;
			breed.defaultDuration = duration;
			yield return breed;

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


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

namespace rjw
{
	public class JobDriver_Fappin : JobDriver
	{
		private const int ticks_between_hearts = 100;

		private int ticks_left;

		private readonly TargetIndex ibed = TargetIndex.A;
		private Building_Bed Bed => (Building_Bed)((Thing)job.GetTarget(ibed));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Bed, job, Bed.SleepingSlotsCount, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			// Faster fapping when frustrated.
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			ticks_left = (int)(xxx.need_some_sex(pawn) > 2f ? 2500.0f * Rand.Range(0.2f, 0.7f) : 2500.0f * Rand.Range(0.2f, 0.4f));

			this.FailOnDespawnedOrNull(ibed);
			this.FailOn(() => pawn.Drafted);
			this.KeepLyingDown(ibed);
			yield return Toils_Bed.ClaimBedIfNonMedical(ibed);
			yield return Toils_Bed.GotoBed(ibed);

			Toil do_fappin = Toils_LayDown.LayDown(ibed, true, false, false, false);
			do_fappin.AddPreTickAction(delegate
			{
				--ticks_left;
				xxx.reduce_rest(pawn, 1);
				if (ticks_left <= 0)
					ReadyForNextToil();
				else if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
			});
			do_fappin.AddFinishAction(delegate
			{
			    //Moved satisfy and tick increase to aftersex, since it works with solo acts now.
				SexUtility.Aftersex(pawn, xxx.rjwSextype.Masturbation);
				if (SexUtility.ConsiderCleaning(pawn))
				{
					LocalTargetInfo own_cum = pawn.PositionHeld.GetFirstThing<Filth>(pawn.Map);

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

					pawn.jobs.jobQueue.EnqueueFirst(clean);

				}
			});
			do_fappin.socialMode = RandomSocialMode.Off;
			yield return do_fappin;
		}
	}
}


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

namespace rjw
{
	public class JobDriver_GettinLoved : JobDriver
	{
		private TargetIndex ipartner = TargetIndex.A;
		private TargetIndex ibed = TargetIndex.B;

		private int tick_interval = 100;

		protected Pawn Partner => (Pawn)(job.GetTarget(ipartner));
		protected Building_Bed Bed => (Building_Bed)(job.GetTarget(ibed));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return true;
			//return this.pawn.Reserve(this.Partner, this.job, 1, 0, null) && this.pawn.Reserve(this.Bed, this.job, 1, 0, null);
		}

		public float CalculateSatisfactionPerTick()
		{
			return 1.0f;
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--Log.Message("[RJW]JobDriver_GettinLoved::MakeNewToils is called");

			float partner_ability = xxx.get_sex_ability(Partner);

			// More/less hearts based on partner ability.
			if (partner_ability < 0.8f)
				tick_interval += 100;
			else if (partner_ability > 2.0f)
				tick_interval -= 25;

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

			if (Partner.CurJob.def == xxx.casual_sex)
			{
				this.FailOnDespawnedOrNull(ipartner);
				this.FailOn(() => !Partner.health.capacities.CanBeAwake);
				this.FailOn(() => pawn.Drafted);
				this.KeepLyingDown(ibed);
				yield return Toils_Reserve.Reserve(ipartner, 1, 0);
				yield return Toils_Reserve.Reserve(ibed, Bed.SleepingSlotsCount, 0);
				Toil get_loved = Toils_LayDown.LayDown(ibed, true, false, false, false);
				get_loved.FailOn(() => Partner.CurJob == null || Partner.CurJob.def != xxx.casual_sex);
				get_loved.defaultCompleteMode = ToilCompleteMode.Never;
				get_loved.AddPreTickAction(delegate
				{
					if (pawn.IsHashIntervalTick(tick_interval))
					{
						MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
						xxx.sexTick(pawn, Partner);
					}
				});
				get_loved.socialMode = RandomSocialMode.Off;
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.whore_is_serving_visitors)
			{
				this.FailOnDespawnedOrNull(ipartner);
				this.FailOn(() => !Partner.health.capacities.CanBeAwake || Partner.CurJob == null);
				yield return Toils_Goto.GotoThing(ipartner, PathEndMode.OnCell);
				yield return Toils_Reserve.Reserve(ipartner, 1, 0);
				Toil get_loved = new Toil();
				get_loved.FailOn(() => (Partner.CurJob.def != xxx.whore_is_serving_visitors));
				get_loved.defaultCompleteMode = ToilCompleteMode.Never;
				get_loved.initAction = delegate
				{
					//--Log.Message("[RJW]JobDriver_GettinLoved::MakeNewToils - whore section is called");
				};
				get_loved.AddPreTickAction(delegate
				{
					if (pawn.IsHashIntervalTick(tick_interval))
					{
						MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
						xxx.sexTick(pawn, Partner);
					}
				});
				get_loved.socialMode = RandomSocialMode.Off;
				yield return get_loved;
			}
			else if (Partner.CurJob.def == xxx.bestialityForFemale)
			{
				this.FailOnDespawnedOrNull(ipartner);
				this.FailOn(() => !Partner.health.capacities.CanBeAwake || Partner.CurJob == null);
				yield return Toils_Goto.GotoThing(ipartner, PathEndMode.OnCell);
				yield return Toils_Reserve.Reserve(ipartner, 1, 0);
				Toil get_loved = new Toil();
				get_loved.FailOn(() => (Partner.CurJob.def != xxx.bestialityForFemale));
				get_loved.defaultCompleteMode = ToilCompleteMode.Never;
				get_loved.initAction = delegate
				{
					//--Log.Message("[RJW]JobDriver_GettinLoved::MakeNewToils - bestialityForFemale section is called");
				};
				get_loved.AddPreTickAction(delegate
				{
					if (pawn.IsHashIntervalTick(tick_interval))
					{
						MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
						xxx.sexTick(pawn, Partner);
					}
				});
				get_loved.socialMode = RandomSocialMode.Off;
				yield return get_loved;
			}
		}
	}
}


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

namespace rjw
{
	public class JobDriver_GettinRaped : JobDriver
	{
		private int ticks_between_hearts;

		private int ticks_remaining = 10;

		public int rapist_count = 1; // Defaults to 1 so the first rapist doesn't have to add themself

		private Pawn Initiator => (Pawn)(job.GetTarget(TargetIndex.A));
		private Pawn Receiver => (Pawn)(job.GetTarget(TargetIndex.B));

		//private bool was_laying_down;

		private Building_Bed Bed;

		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)
		{
			Bed = newBed;
		}

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return true;
		}

		private void RapeAlert(bool silent = false)
		{
			if (!silent)
				Messages.Message(xxx.get_pawnname(Receiver) + " is getting raped.", Receiver, MessageTypeDefOf.NegativeEvent);
			else
				Messages.Message(xxx.get_pawnname(Receiver) + " is getting raped.", Receiver, MessageTypeDefOf.SilentInput);
		}

		public float CalculateSatisfactionPerTick()
		{
			return 1.0f;
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			ticks_between_hearts = Rand.RangeInclusive(70, 130);
			//was_laying_down = pawn.GetPosture() != PawnPosture.Standing;
			//Log.Message(xxx.get_pawnname(Initiator) + ": was_laying_down:" + was_laying_down + " LayingInBed:" + Initiator.GetPosture());
			//Log.Message(xxx.get_pawnname(Receiver) + ": was_laying_down:" + was_laying_down + " LayingInBed:" + Receiver.GetPosture());
			//was_laying_down = (pawn.jobs.curDriver != null) && pawn.GetPosture() != PawnPosture.Standing;
			//Log.Message(xxx.get_pawnname(pawn) + ": bed:" + Bed);

			var get_raped = new Toil();
			get_raped.defaultCompleteMode = ToilCompleteMode.Never;
			get_raped.initAction = delegate
			{
				pawn.pather.StopDead();
				//pawn.jobs.posture = PawnPosture.Standing;
				pawn.jobs.curDriver.asleep = false;

				switch (RJWPreferenceSettings.rape_alert_sound)
				{
					case RJWPreferenceSettings.RapeAlert.Enabled:
						RapeAlert();
						break;
					case RJWPreferenceSettings.RapeAlert.Humanlikes:
						if (xxx.is_human(Receiver))
							RapeAlert();
						else
							RapeAlert(true);
						break;
					case RJWPreferenceSettings.RapeAlert.Colonists:
						if (Receiver.Faction == Faction.OfPlayer)
							RapeAlert();
						else
							RapeAlert(true);
						break;
					default:
						RapeAlert(true);
						break;
				}

				//Messages.Message("GetinRapedNow".Translate(new object[] { pawn.LabelIndefinite() }).CapitalizeFirst(), pawn, MessageTypeDefOf.NegativeEvent);

				if (Initiator == null || Receiver == null) return;
				bool partnerHasHands = Receiver.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand));

				// Hand check is for monstergirls and other bipedal 'animals'.
				if ((!xxx.is_animal(Initiator) && partnerHasHands) || Rand.Chance(0.3f)) // 30% chance of face-to-face regardless, for variety.
				{ // Face-to-face
					Initiator.rotationTracker.Face(Receiver.DrawPos);
					Receiver.rotationTracker.Face(Initiator.DrawPos);
				}
				else
				{ // From behind / animal stuff should mostly use this
					Initiator.rotationTracker.Face(Receiver.DrawPos);
					Receiver.Rotation = Initiator.Rotation;
				}
				// TODO: The above works, but something is forcing the partners to face each other during sex. Need to figure it out.

				//prevent Receiver standing up and interrupting rape, probably
				if (Receiver.health.hediffSet.HasHediff(HediffDef.Named("Hediff_Submitting")))
					Receiver.health.AddHediff(HediffDef.Named("Hediff_Submitting"));
			};
			get_raped.tickAction = delegate
			{
				--ticks_remaining;
				/*
				if ((ticks_remaining <= 0) || (rapist_count <= 0))
					ReadyForNextToil();
				*/
				if ((rapist_count > 0) && (pawn.IsHashIntervalTick(ticks_between_hearts / rapist_count)))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);

			};
			get_raped.AddEndCondition(new Func<JobCondition>(() =>
			{
				if ((ticks_remaining <= 0) || (rapist_count <= 0))
					return JobCondition.Succeeded;
				return JobCondition.Ongoing;
			}));
			get_raped.AddFinishAction(delegate
			{
				if (Bed != null && pawn.Downed)
				{
					Job tobed = new Job(JobDefOf.Rescue, pawn, Bed);
					tobed.count = 1;
					Initiator.jobs.jobQueue.EnqueueFirst(tobed);
					//Log.Message(xxx.get_pawnname(Initiator) + ": job tobed:" + tobed);
				}
			});
			get_raped.socialMode = RandomSocialMode.Off;
			yield return get_raped;
		}
	}
}


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

namespace rjw
{
	public class JobDriver_JoinInBed : JobDriver
	{
		private const int ticks_between_hearts = 100;

		private int ticks_left;

		private readonly TargetIndex ipawn = TargetIndex.A;
		private readonly TargetIndex ipartner = TargetIndex.B;
		private readonly TargetIndex ibed = TargetIndex.C;

		protected Pawn Top => (Pawn)(job.GetTarget(ipawn));
		protected Pawn Partner => (Pawn)(job.GetTarget(ipartner));
		protected Building_Bed Bed => (Building_Bed)(job.GetTarget(ibed));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return Top.Reserve(Partner, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--Log.Message("JobDriver_JoinInBed::MakeNewToils() called");
			this.FailOnDespawnedOrNull(ipartner);
			this.FailOnDespawnedOrNull(ibed);
			this.FailOn(() => !Partner.health.capacities.CanBeAwake);
			this.FailOn(() => !(xxx.is_laying_down_alone(Partner) || xxx.in_same_bed(Partner, Top)));
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Reserve.Reserve(ipartner, xxx.max_rapists_per_prisoner, 0);
			yield return Toils_Goto.GotoThing(ipartner, PathEndMode.OnCell);
			yield return new Toil
			{
				initAction = delegate
				{
					//--Log.Message("JobDriver_JoinInBed::MakeNewToils() - setting initAction");
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					ticks_left = (int)(2500.0f * Rand.Range(0.30f, 1.30f));
					Job gettin_loved = new Job(xxx.gettin_loved, Top, Bed);
					Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
			Toil do_lovin = new Toil {defaultCompleteMode = ToilCompleteMode.Never};
			do_lovin.FailOn(() => (Partner.CurJob == null) || (Partner.CurJob.def != xxx.gettin_loved));
			do_lovin.AddPreTickAction(delegate
			{
				--ticks_left;
				xxx.reduce_rest(Partner, 1);
				xxx.reduce_rest(Top, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
				else if (Top.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(Top.Position, Top.Map, ThingDefOf.Mote_Heart);
			});
			do_lovin.socialMode = RandomSocialMode.Off;
			yield return do_lovin;
			yield return new Toil
			{
				initAction = delegate
				{
					// Trying to add some interactions and social logs
					SexUtility.ProcessSex(Top, Partner, false /*rape*/, false/*isCoreLovin*/, false /*whoring*/);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_PrisonerComfortRapin : JobDriver
	{
		protected int duration;
		protected int ticks_between_hearts;
		protected int ticks_between_hits = 50;
		protected int ticks_between_thrusts;

		protected TargetIndex iprisoner = TargetIndex.A;

		protected virtual float chance_to_hit_prisoner => xxx.config.base_chance_to_hit_prisoner;

		protected Pawn Target => (Pawn)(job.GetTarget(iprisoner));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		[SyncMethod]
		public virtual void roll_to_hit(Pawn rapist, Pawn p)
		{
			if (!RJWSettings.rape_beating)
			{
				return;
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float rand_value = Rand.Value;
			float victim_pain = p.health.hediffSet.PainTotal;
			// bloodlust makes the aggressor more likely to hit the prisoner
			float beating_chance = chance_to_hit_prisoner * (xxx.is_bloodlust(rapist) ? 1.25f : 1.0f);
			// psychopath makes the aggressor more likely to hit the prisoner past the significant_pain_threshold
			float beating_threshold = xxx.is_psychopath(rapist) ? xxx.config.extreme_pain_threshold : xxx.config.significant_pain_threshold;

			//--Log.Message("roll_to_hit:  rand = " + rand_value + ", beating_chance = " + beating_chance + ", victim_pain = " + victim_pain + ", beating_threshold = " + beating_threshold);

			//if ((victim_pain < beating_threshold && rand_value < beating_chance) || (rand_value < (beating_chance / 2)))
			if ((victim_pain < beating_threshold && rand_value < beating_chance) || (rand_value < (beating_chance / 2) && xxx.is_bloodlust(rapist)))
			{
				//--Log.Message("   done told her twice already...");
				if (InteractionUtility.TryGetRandomVerbForSocialFight(rapist, out Verb v))
				{
					rapist.meleeVerbs.TryMeleeAttack(p, v);
				}
			}

			/*
			//if (p.health.hediffSet.PainTotal < xxx.config.significant_pain_threshold)
			if ((Rand.Value < 0.50f) &&
				((Rand.Value < 0.33f) || (p.health.hediffSet.PainTotal < xxx.config.significant_pain_threshold) ||
				 (xxx.is_bloodlust (rapist) || xxx.is_psychopath (rapist)))) {
				Verb v;
				if (InteractionUtility.TryGetRandomVerbForSocialFight (rapist, out v))
					rapist.meleeVerbs.TryMeleeAttack (p, v);
			}
			*/
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			duration = (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);
			ticks_between_thrusts = 100;
			bool pawnHasPenis = Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn);

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

			//--Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - setting fail conditions");
			this.FailOnDespawnedNullOrForbidden(iprisoner);
			//this.FailOn(() => (!Target.health.capacities.CanBeAwake) || (!comfort_prisoners.is_designated(Target)));//this is wrong
			this.FailOn(() => (!Target.IsDesignatedComfort()));
			this.FailOn(() => !pawn.CanReserve(Target, 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(iprisoner, PathEndMode.OnCell);

			SexUtility.RapeAttemptAlert(pawn, Target);

			Toil rape = new Toil();
			rape.initAction = delegate
			{
				//--Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - reserving prisoner");
				//pawn.Reserve(Target, comfort_prisoners.max_rapists_per_prisoner, 0);
				if (!pawnHasPenis)
					Target.rotationTracker.Face(pawn.DrawPos);

				//--Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - Setting victim job driver");
				JobDriver_GettinRaped dri = Target.jobs.curDriver as JobDriver_GettinRaped;
				if (dri == null)
				{
					Job gettin_raped = new Job(xxx.gettin_raped, pawn, Target);
					Building_Bed Bed = null;
					//Log.Message(xxx.get_pawnname(pawn) + " LayingInBed:" + pawn.GetPosture());
					//Log.Message(xxx.get_pawnname(Target) + " LayingInBed:" + Target.GetPosture());
					if (Target.GetPosture() == PawnPosture.LayingInBed)
					{
						Bed = Target.CurrentBed();
						//Log.Message(xxx.get_pawnname(Target) + ": bed:" + Bed);
					}

					Target.jobs.StartJob(gettin_raped, JobCondition.InterruptForced, null, false, true, null);
					(Target.jobs.curDriver as JobDriver_GettinRaped)?.increase_time(duration);
					if (Bed != null)
						(Target.jobs.curDriver as JobDriver_GettinRaped)?.set_bed(Bed);
				}
				else
				{
					dri.rapist_count += 1;
					dri.increase_time(duration);
				}
			};
			rape.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_thrusts))
					xxx.sexTick(pawn, Target);
				if (pawn.IsHashIntervalTick(ticks_between_hits))
					roll_to_hit(pawn, Target);
				xxx.reduce_rest(Target, 1);
				xxx.reduce_rest(pawn, 2);
			};
			rape.AddFinishAction(delegate
			{
				//Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - Clearing victim job");
				if (Target.jobs?.curDriver is JobDriver_GettinRaped)
				{
					//Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - Victim present");
					(Target.jobs.curDriver as JobDriver_GettinRaped).rapist_count -= 1;
				}
			});
			rape.defaultCompleteMode = ToilCompleteMode.Delay;
			rape.defaultDuration = duration;
			yield return rape;

			yield return new Toil
			{
				initAction = delegate
				{
					// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Target, true);
					Target.records.Increment(xxx.GetRapedAsComfortPrisoner);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_QuickFap : JobDriver
	{
		private const int ticks_between_hearts = 100;
		private int ticks_left;

		public IntVec3 cell => (IntVec3)job.GetTarget(TargetIndex.A);

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

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//this.FailOn(() => PawnUtility.PlayerForcedJobNowOrSoon(pawn));
			this.FailOn(() => pawn.health.Downed);
			this.FailOn(() => pawn.IsBurning());
			this.FailOn(() => pawn.IsFighting());
			this.FailOn(() => pawn.Drafted);

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			// Faster fapping when frustrated.
			ticks_left = (int)(xxx.need_some_sex(pawn) > 2f ? 2500.0f * Rand.Range(0.2f, 0.7f) : 2500.0f * Rand.Range(0.2f, 0.4f));

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

			//Log.Message("[RJW] Making new toil for QuickFap.");

			Toil fap = Toils_General.Wait(ticks_left);
			fap.tickAction = delegate
			{
				--ticks_left;
				xxx.reduce_rest(pawn, 1);
				if (ticks_left <= 0)
					ReadyForNextToil();
				else if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
			};
			fap.AddFinishAction(delegate
			{
				SexUtility.Aftersex(pawn, xxx.rjwSextype.Masturbation);
				if (!SexUtility.ConsiderCleaning(pawn)) return;

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

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

				pawn.jobs.jobQueue.EnqueueFirst(clean);
			});
			yield return fap;
		}
	}
}


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;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	public class JobDriver_Rape : JobDriver
	{
		protected int duration;

		protected int ticks_between_hearts;
		protected int ticks_between_hits = 50;
		protected int ticks_between_thrusts;

		protected TargetIndex iTarget = TargetIndex.A;
		public Pawn Target => (Pawn)(job.GetTarget(iTarget));

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
		}

		[SyncMethod]
		public static void roll_to_hit(Pawn rapist, Pawn p)
		{
			if (!RJWSettings.rape_beating)
				return;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float rand_value = Rand.Value;
			float victim_pain = p.health.hediffSet.PainTotal;
			// bloodlust makes the aggressor more likely to hit the prisoner
			float beating_chance = xxx.config.base_chance_to_hit_prisoner * (xxx.is_bloodlust(rapist) ? 1.25f : 1.0f);
			// psychopath makes the aggressor more likely to hit the prisoner past the significant_pain_threshold
			float beating_threshold = xxx.is_psychopath(rapist) ? xxx.config.extreme_pain_threshold : xxx.config.significant_pain_threshold;

			//--Log.Message("roll_to_hit:  rand = " + rand_value + ", beating_chance = " + beating_chance + ", victim_pain = " + victim_pain + ", beating_threshold = " + beating_threshold);
			if ((victim_pain < beating_threshold && rand_value < beating_chance) || (rand_value < (beating_chance / 2) && xxx.is_bloodlust(rapist)))
			{
				//--Log.Message("   done told her twice already...");
				if (InteractionUtility.TryGetRandomVerbForSocialFight(rapist, out Verb v))
				{
					rapist.meleeVerbs.TryMeleeAttack(p, v);
				}
			}

			/*
			//if (p.health.hediffSet.PainTotal < xxx.config.significant_pain_threshold)
			if ((Rand.Value < 0.50f) &&
				((Rand.Value < 0.33f) || (p.health.hediffSet.PainTotal < xxx.config.significant_pain_threshold) ||
				 (xxx.is_bloodlust (rapist) || xxx.is_psychopath (rapist)))) {
				Verb v;
				if (InteractionUtility.TryGetRandomVerbForSocialFight (rapist, out v))
					rapist.meleeVerbs.TryMeleeAttack (p, v);
			}
			*/
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			//Log.Message("[RJW]" + this.GetType().ToString() + "::MakeNewToils() called");
			duration = (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);
			ticks_between_thrusts = 100;

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

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

			SexUtility.RapeAttemptAlert(pawn, Target);

			//Log.Message("[RJW] JobDriver_Rape::make toils() called");
			var rape = new Toil();
			rape.initAction = delegate
			{
				//Log.Message("[RJW]" + this.GetType().ToString() + "::initAction() called");
				//pawn.Reserve(Target, comfort_prisoners.max_rapists_per_prisoner, 0);
				//if (!pawnHasPenis)
				//	Target.rotationTracker.Face(pawn.DrawPos);
				JobDriver_GettinRaped dri = Target.jobs.curDriver as JobDriver_GettinRaped;
				if (dri == null)
				{
					Job gettin_raped = new Job(xxx.gettin_raped, pawn, Target);
					Building_Bed Bed = null;
					//Log.Message(xxx.get_pawnname(pawn) + " LayingInBed:" + pawn.GetPosture());
					//Log.Message(xxx.get_pawnname(Target) + " LayingInBed:" + Target.GetPosture());
					if (Target.GetPosture() == PawnPosture.LayingInBed)
					{
						Bed = Target.CurrentBed();
						//Log.Message(xxx.get_pawnname(Target) + ": bed:" + Bed);
					}
					Target.jobs.StartJob(gettin_raped, JobCondition.InterruptForced, null, false, true, null);
					(Target.jobs.curDriver as JobDriver_GettinRaped)?.increase_time(duration);
					if (Bed !=null)
						(Target.jobs.curDriver as JobDriver_GettinRaped)?.set_bed(Bed);
				}
				else
				{
					dri.rapist_count += 1;
					dri.increase_time(duration);
				}
				rape.FailOn(() => Target.CurJob == null || Target.CurJob.def != xxx.gettin_raped || Target.IsFighting() || pawn.IsFighting());
			};
			rape.tickAction = delegate
			{
				//Log.Message("[RJW] JobDriver_Rape::tickAction() called");
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_thrusts))
					xxx.sexTick(pawn, Target, false);
				if (pawn.IsHashIntervalTick(ticks_between_hits))
					roll_to_hit(pawn, Target);
				xxx.reduce_rest(Target, 1);
				xxx.reduce_rest(pawn, 2);
			};
			rape.AddFinishAction(delegate
			{
				if (Target.jobs?.curDriver is JobDriver_GettinRaped)
				{
					(Target.jobs.curDriver as JobDriver_GettinRaped).rapist_count -= 1;
				}
			});
			rape.defaultCompleteMode = ToilCompleteMode.Delay;
			rape.defaultDuration = duration;
			yield return rape;

			yield return new Toil
			{
				initAction = delegate
				{
					//Log.Message("[RJW] JobDriver_Rape::aftersex() called");
					//// Trying to add some interactions and social logs
					SexUtility.ProcessSex(pawn, Target, true);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	internal class JobDef_RapeEnemy : JobDef
	{
		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 virtual bool CanUseThisJobForPawn(Pawn rapist)
		{
			if (rapist.CurJob != null && rapist.CurJob.def != JobDefOf.LayDown)
				return false;

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

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

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

		//override can_rapemechanics
		protected bool requierCanRape = 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;

		public virtual Pawn FindVictim(Pawn rapist, Map m, float targetAcquireRadius)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) map " + m?.ToString());
			if (rapist == null || m == null) return null;
			//Log.Message("[RJW]" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) can rape " + xxx.can_rape(rapist));
			if (!xxx.can_rape(rapist) && requierCanRape) return null;
			Pawn best_rapee = null;
			float best_fuckability = 0.20f; // Don't rape pawns with <20% fuckability

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

			//Log.Message("[RJW]" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) found " + targets.Count() + "targets");
			foreach (Pawn target in targets)
			{
				//Log.Message("[RJW]" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) target: " + xxx.get_pawnname(target) );

				if (target.Position.DistanceTo(rapist.Position) >= targetAcquireRadius) continue; //Too far to fuck target, ignore target.

				if (considerStillAliveEnemies)
				{
					if (rapist.CanSee(target) && !target.Downed)
					{
						//Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) +" ) enemies: "+ xxx.get_pawnname(target)+ " still alive" );
						return null; //Enemies still up. Kill them first.
					}
				}

				if (!RJWSettings.bestiality_enabled && xxx.is_animal(target) && !(xxx.is_animal(rapist) && RJWSettings.animal_on_animal_enabled)) continue; //zoo disabled, skip.

				//Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(target) + " ) - checking\nCanReserve:"+ rapist.CanReserve(target, xxx.max_rapists_per_prisoner, 0) + "\nCanReach:" + rapist.CanReach(target, PathEndMode.OnCell, Danger.None)+ "\nCan_rape_Easily:" + Can_rape_Easily(target));
				if (rapist.CanReserveAndReach(target, PathEndMode.OnCell, Danger.Some, xxx.max_rapists_per_prisoner, 0) && Can_rape_Easily(target) )
				{
					if (xxx.is_human(target) || xxx.is_animal(target))
					{
						float fuc = GetFuckability(rapist, target);
						//Log.Message("[RJW]"+this.GetType().ToString()+ "::FindVictim( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(target) + " ) - fuckability:" + fuc + " ");
						if (fuc > best_fuckability)
						{
							best_rapee = target;
							best_fuckability = fuc;
						}
						//else { Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(target) + " ) - is not good for me "+ "( " + fuc + " )"); }
					}
				}
				//else { Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(target) + " ) - is not good"); }
			}
			//Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(best_rapee) + " ) - fuckability:" + best_fuckability + " ");
			return best_rapee;
		}

		public virtual float GetFuckability(Pawn rapist, Pawn target)
		{
			//Log.Message("[RJW]JobDriver_RapeEnemy::GetFuckability(" + rapist.ToString() + "," + target.ToString() + ")");
			if (target.health.hediffSet.HasHediff(is_submitting))//it's not about attractiveness anymore, it's about showing who's whos bitch
			{
				return 2 * xxx.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
			}
			return !xxx.would_rape(rapist, target) ? 0f
				: xxx.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
		}

		protected bool Can_rape_Easily(Pawn p)
		{
			return xxx.can_get_raped(p) && !p.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) && (RJWSettings.bestiality_enabled || RJWSettings.animal_on_animal_enabled);
		}
	}
}


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
	{
		//override mechanics

		//public JobDriver_RapeEnemyByInsect()
		//{
		//	this.requierCanRape = 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;

			//someday add check for insect-insect breeding
			return xxx.is_insect(rapist) && RJWSettings.bestiality_enabled;
		}

		public override float GetFuckability(Pawn rapist, Pawn target)
		{
			//Plant Eggs to everyone.
			if (rapist.gender == Gender.Female)
			{
				return 1f;
			}
			//Feritlize eggs to everyone with planted eggs.
			else
			{
				if ((from x in target.health.hediffSet.GetHediffs<Hediff_InsectEgg>() where x.IsParent(rapist.def.defName) select x).Count() > 0)
				{
					return 1f;
				}
			}
			return 0f;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByMech : JobDriver_RapeEnemy
	{
		//public JobDriver_RapeEnemyByMech()
		//{
		//	this.requierCanRape = 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 xxx.is_mechanoid(rapist);
		}

		public override float GetFuckability(Pawn rapist, Pawn target)
		{
			//--Log.Message("[RJW]JobDriver_RapeEnemyByMech::GetFuckability("+ rapist.ToString()+","+ target.ToString() + ") - Force Rape");
			//Plant chips to humanlikes.
			if (xxx.is_human(target))
				return 1f;
			else
				return 0f;
		}

	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyToParasite : JobDriver_RapeEnemy
	{
		public JobDriver_RapeEnemyToParasite()
		{
			this.requierCanRape = false;
		}

		//not implemented
		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.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	public abstract class JobDriver_Sex : JobDriver
	{
		protected float satisfaction = 1.0f;

		public bool shouldreserve = true;

		public int maxPawns = 1;
		public int stackCount = 0;

		public int ticks_between_hearts;
		public int ticks_between_hits = 50;
		public int ticks_between_thrusts;

		public Thing Target = null;

		//private Building_Bed Bed;

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			Log.Message("shouldreserve " + shouldreserve);
			if (shouldreserve)
				return pawn.Reserve(Target, job, maxPawns, stackCount, 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);
		}

		public void CalculateSatisfactionPerTick()
		{
				satisfaction = 1.0f;
		}

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


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

namespace rjw
{
	public class JobDriver_ViolateCorpse : JobDriver
	{
		private int duration;
		private int ticks_between_hearts;
		private int ticks_between_hits = 50;
		private int ticks_between_thrusts;

		protected TargetIndex icorpse = TargetIndex.A;
		protected Corpse Target => (Corpse)(job.GetTarget(icorpse));

		public static void sexTick(Pawn pawn, Thing Target)
		{
			if (!xxx.has_quirk(pawn, "Endytophile"))
			{
				xxx.DrawNude(pawn, true);
			}

			if (RJWSettings.sounds_enabled)
				SoundDef.Named("Sex").PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));

			pawn.Drawer.Notify_MeleeAttackOn(Target);
			pawn.rotationTracker.FaceCell(Target.Position);
		}

		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Target, job, 1, -1, null, errorOnFailed);
		}

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() called");
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			duration = (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);
			ticks_between_thrusts = 100;

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

			this.FailOnDespawnedNullOrForbidden(icorpse);
			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);

			//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() - moving towards Target");
			yield return Toils_Goto.GotoThing(icorpse, PathEndMode.OnCell);

			var alert = RJWPreferenceSettings.rape_alert_sound == RJWPreferenceSettings.RapeAlert.Disabled ?
				MessageTypeDefOf.SilentInput : MessageTypeDefOf.NeutralEvent;
			Messages.Message(pawn.Name + " is trying to rape a Target.", pawn, alert);

			var rape = new Toil();
			rape.initAction = delegate
			{
				//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() - reserving Target");
				//pawn.Reserve(Target, 1, 0); // Target rapin seems like a solitary activity

				//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() - stripping Target");
				Target.Strip();
			};
			rape.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				if (pawn.IsHashIntervalTick(ticks_between_thrusts))
					sexTick(pawn, Target);
				/*
				if (pawn.IsHashIntervalTick (ticks_between_hits))
					roll_to_hit (pawn, Corpse);
					*/
				xxx.reduce_rest(pawn, 2);
			};
			rape.AddFinishAction(delegate
			{
				if (xxx.is_human(pawn))
					pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
			});
			rape.defaultCompleteMode = ToilCompleteMode.Delay;
			rape.defaultDuration = duration;
			yield return rape;

			yield return new Toil
			{
				initAction = delegate
				{
					//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() - creating aftersex toil");
					//Addded by nizhuan-jjr: Try to apply an aftersex process for the pawn and the Target
					if (Target.InnerPawn != null)
					{
						SexUtility.ProcessSex(pawn, Target.InnerPawn, true);
					}
				},
				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
	{
		private const float min_fuckability = 0.10f; // Don't rape prisoners with <10% fuckability

		[SyncMethod]
		public static Pawn find_victim(Pawn rapist, Map m)
		{
			IEnumerable<Pawn> possible_targets = m.mapPawns.AllPawns.Where(x
				=> IsPrisonerOf(x, rapist.Faction)
				&& x != rapist
				&& xxx.can_get_raped(x)
				&& !x.Position.IsForbidden(rapist)
				);

			List<Pawn> valid_targets = new List<Pawn>();

			foreach (Pawn target in possible_targets)
			{
				if (!rapist.CanReserve(target, xxx.max_rapists_per_prisoner, 0)) continue;

				//--Log.Message(xxx.get_pawnname(rapist) + "->" + xxx.get_pawnname(target) + ":" + fuc);
				if (xxx.would_fuck(rapist, target, true, true) > min_fuckability)
				{
					valid_targets.Add(target);
				}
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return valid_targets.Any() ? valid_targets.RandomElement() : null;
		}

		protected override Job TryGiveJob(Pawn rapist)
		{
			//Log.Message("[RJW] JobGiver_AIRapePrisoner::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called ");

			if (SexUtility.ReadyForLovin(rapist) || xxx.need_some_sex(rapist) > 1f)
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (xxx.is_healthy(rapist) && xxx.can_rape(rapist))
				{
					Pawn prisoner = find_victim(rapist, rapist.Map);

					if (prisoner != null)
					{
						//--Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(p) + " ) - found victim " + xxx.get_pawnname(prisoner));
						return new Job(xxx.random_rape, 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:
// #define TESTMODE // Uncomment to enable logging.

using System.Diagnostics;
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
	{
		[Conditional("TESTMODE")]
		private static void DebugText(string msg)
		{
			Log.Message(msg);
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			if (pawn.Drafted) return null;

			// Most checks are now done in ThinkNode_ConditionalBestiality
			DebugText("[RJW] JobGiver_Bestiality::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called");

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

			Pawn target = BreederHelper.find_breeder_animal(pawn, pawn.Map);
			DebugText("[RJW] JobGiver_Bestiality::TryGiveJob - target is " + (target == null ? "no target found" : xxx.get_pawnname(target)));

			if (target == null) return null;

			if (xxx.can_rape(pawn))
			{
				return new Job(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 new Job(xxx.bestialityForFemale, target, bed, bed.SleepPosOfAssignedPawn(pawn));
		}
	}
}


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

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

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

			if(xxx.is_healthy(animal) && xxx.can_rape(animal))
			{
				//Log.Message("[RJW] JobGiver_Breed::TryGiveJob( " + xxx.get_pawnname(animal) + " ) called2");
				List<Pawn> valid_targets = new List<Pawn>();

				//search for desiganted target to sex
				if (animal.IsDesignatedBreedingAnimal())
				{
					Pawn designated_target = BreederHelper.find_designated_breeder(animal, animal.Map);
					if (designated_target != null)
					{
						valid_targets.Add(designated_target);
					}
				}

				//some weird shit happens, animal tries to rape and fails, needs investigation someday
				/*
				//search for animal to sex
				if (RJWSettings.animal_on_animal_enabled)
				{
					//Using bestiality target finder, since it works best for this.
					//search for same race mate
					if (!valid_targets.Any())
					{
						Pawn animal_target = BreederHelper.find_breeder_animal(animal, animal.Map);
						if (animal_target != null)
						{
							valid_targets.Add(animal_target);
						}
					}

					//search for any other animal/human to sex
					if (!valid_targets.Any())
					{
						Pawn animal_target = BreederHelper.find_breeder_animal(animal, animal.Map, false);
						if (animal_target != null)
						{
							valid_targets.Add(animal_target);
						}
					}

				}
				*/

				//Log.Message("[RJW] JobGiver_Breed::TryGiveJob( " + xxx.get_pawnname(animal) + " ) called3 - (" + ((target == null) ? "no target found" : xxx.get_pawnname(target))+") is the prisoner");

				if (valid_targets != null && valid_targets.Any())
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					var target = valid_targets.RandomElement();
					//Log.Message("Target: " + xxx.get_pawnname(target));
					return new Job(DefDatabase<JobDef>.GetNamed("Breed"), target, animal);
				}
			}
			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_ComfortPrisonerRape : ThinkNode_JobGiver
	{
		public static Pawn find_prisoner_to_rape(Pawn rapist, Map m)
		{
			Pawn best_rapee = null;
			float best_fuckability = 0.10f; // Don't rape prisoners with <10% fuckability
			IEnumerable<Pawn> targets = m.AllComfortDesignations().Where(x
				=> x != rapist
				&& xxx.can_get_raped(x)
				&& rapist.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.IsForbidden(rapist)
				&& xxx.would_rape(rapist, x)
				);

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

			foreach (Pawn target in targets)
			{
				float fuc = 0.0f;
				if (xxx.is_animal(target))
					fuc = xxx.would_fuck_animal(rapist, target, true);
				else if (xxx.is_human(target))
					fuc = xxx.would_fuck(rapist, target, true);
				//--Log.Message(rapist.Name + " -> " + candidate.Name + " (" + fuc.ToString() + " / " + best_fuckability.ToString() + ")");

				if (fuc > best_fuckability)
				{
					best_rapee = target;
					best_fuckability = fuc;
				}
			}
			return best_rapee;
		}

		protected override Job TryGiveJob(Pawn rapist)
		{
			//Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called0");

			if (!RJWSettings.WildMode)
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (!xxx.is_healthy(rapist) || rapist.IsDesignatedComfort() || (!SexUtility.ReadyForLovin(rapist) && !xxx.is_frustrated(rapist))) return null;
			}

			if (rapist.Drafted) return null;

			//Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called1");
			if (!xxx.can_rape(rapist)) return null;

			// It's unnecessary to include other job checks. Pawns seem to only look for new jobs when between jobs or layind down idle.
			if (!(rapist.jobs.curJob == null || rapist.jobs.curJob.def == JobDefOf.LayDown)) return null;

			//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called2");
			// Faction check.
			if (!(rapist.Faction?.IsPlayer ?? false) && !rapist.IsPrisonerOfColony) return null;

			Pawn target = find_prisoner_to_rape(rapist, rapist.Map);
			//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called3 - (" + ((target == null) ? "no target found" : xxx.get_pawnname(target))+") is the prisoner");

			if (target == null) return null;

			//Log.Message("giving job to " + pawner + " with target " + target);
			if (xxx.is_animal(target))
				return new Job(xxx.bestiality, target);
			else
				return new Job(xxx.comfort_prisoner_rapin, target);
		}
	}
}


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

namespace rjw
{
	public class JobGiver_DoFappin : ThinkNode_JobGiver
	{
		[SyncMethod]
		public virtual IntVec3 FindFapLocation(Pawn p)
		{
			IntVec3 position = p.Position;
			int bestPosition = -100;
			IntVec3 cell = p.Position;
			int maxDistance = 40;

			FloatRange temperature = p.ComfortableTemperatureRange();
			bool is_somnophile = xxx.has_quirk(p, "Somnophile");
			bool is_exhibitionist = xxx.has_quirk(p, "Exhibitionist");
			List<Pawn> all_pawns = p.Map.mapPawns.AllPawnsSpawned.Where(x
				=> x.Position.DistanceTo(p.Position) < 100
				&& xxx.is_human(x)
				&& x != p
				).ToList();

			//Log.Message("[RJW] Pawn is " + xxx.get_pawnname(p) + ", current cell is " + cell);

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			List<IntVec3> random_cells = new List<IntVec3>();
			for (int loop = 0; loop < 50; ++loop)
			{
				random_cells.Add(position + IntVec3.FromVector3(Vector3Utility.HorizontalVectorFromAngle(Rand.Range(0, 360)) * Rand.RangeInclusive(1, maxDistance)));
			}

			random_cells = random_cells.Where(x
				=> x.Standable(p.Map)
				&& x.InAllowedArea(p)
				&& x.GetDangerFor(p, p.Map) != Danger.Deadly
				&& !x.ContainsTrap(p.Map)
				&& !x.ContainsStaticFire(p.Map)
				).Distinct().ToList();

			//Log.Message("[RJW] Found " + random_cells.Count + " valid cells.");

			foreach (IntVec3 random_cell in random_cells)
			{
				int score = 0;
				Room room = random_cell.GetRoom(p.Map);

				bool might_be_seen = all_pawns.Any(x
					=> GenSight.LineOfSight(x.Position, random_cell, p.Map)
					&& x.Position.DistanceTo(random_cell) < 50
					&& x.Awake()
					);

				if (is_exhibitionist)
				{
					if (might_be_seen)
						score += 5;
					else
						score -= 10;
				}
				else
				{
					if (might_be_seen)
						score -= 30;
				}
				if (is_somnophile) // Fap while Watching someone sleep. Not creepy at all!
				{
					if (all_pawns.Any(x
						=> GenSight.LineOfSight(random_cell, x.Position, p.Map)
						&& x.Position.DistanceTo(random_cell) < 6
						&& !x.Awake()
						))
						score += 50;
				}

				if (random_cell.GetTemperature(p.Map) > temperature.min && random_cell.GetTemperature(p.Map) < temperature.max)
					score += 20;
				else
					score -= 20;
				if (random_cell.Roofed(p.Map))
					score += 5;
				if (random_cell.HasEatSurface(p.Map))
					score += 5; // Hide in vegetation.
				if (random_cell.GetDangerFor(p, p.Map) == Danger.Some)
					score -= 25;
				else if (random_cell.GetDangerFor(p, p.Map) == Danger.None)
					score += 5;
				if (random_cell.GetTerrain(p.Map) == TerrainDefOf.WaterShallow ||
					random_cell.GetTerrain(p.Map) == TerrainDefOf.WaterMovingShallow ||
					random_cell.GetTerrain(p.Map) == TerrainDefOf.WaterOceanShallow)
					score -= 20;

				if (random_cell.GetThingList(p.Map).Any(x => x.def.IsWithinCategory(ThingCategoryDefOf.Corpses)) && !xxx.is_necrophiliac(p))
					score -= 20;
				if (random_cell.GetThingList(p.Map).Any(x => x.def.IsWithinCategory(ThingCategoryDefOf.Foods)))
					score -= 10;

				if (room == p.Position.GetRoom(p.MapHeld))
					score -= 10;
				if (room.PsychologicallyOutdoors)
					score += 5;
				if (room.isPrisonCell)
					score += 5;
				if (room.IsHuge)
					score -= 5;
				if (room.ContainedBeds.Any())
					score += 5;
				if (room.IsDoorway)
					score -= 10;
				if (!room.Owners.Any())
					score += 10;
				else if (room.Owners.Contains(p))
					score += 20;
				if (room.Role == RoomRoleDefOf.Bedroom || room.Role == RoomRoleDefOf.PrisonCell)
					score += 10;
				else if (room.Role == RoomRoleDefOf.Barracks || room.Role == RoomRoleDefOf.Laboratory || room.Role == RoomRoleDefOf.RecRoom)
					score += 2;
				else if (room.Role == RoomRoleDefOf.DiningRoom || room.Role == RoomRoleDefOf.Hospital || room.Role == RoomRoleDefOf.PrisonBarracks)
					score -= 5;
				if (room.GetStat(RoomStatDefOf.Cleanliness) < 0.01f)
					score -= 5;
				if (room.GetStat(RoomStatDefOf.GraveVisitingJoyGainFactor) > 0.1f)
					score -= 5;

				if (score <= bestPosition) continue;

				bestPosition = score;
				cell = random_cell;
			}

			return cell;

			//Log.Message("[RJW] Best cell is " + cell);
		}

		protected override Job TryGiveJob(Pawn fapper)
		{
			//--Log.Message("[RJW] JobGiver_DoFappin::TryGiveJob( " + xxx.get_pawnname(fapper) + " ) called");

			if (fapper.Drafted) return null;

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

			// Whores only fap if frustrated, unless imprisoned.
			if ((SexUtility.ReadyForLovin(fapper) && (!xxx.is_whore(fapper) || fapper.IsPrisoner )) || xxx.is_frustrated(fapper))
			{
				if (fapper.jobs.curDriver is JobDriver_LayDown)
				{
					Building_Bed bed = ((JobDriver_LayDown)fapper.jobs.curDriver).Bed;
					if (bed != null) return new Job(xxx.fappin, bed);
				}
				else if (xxx.is_frustrated(fapper) || xxx.has_quirk(fapper, "Exhibitionist"))
				{
					return new Job(xxx.quickfap, FindFapLocation(fapper));
				}
			}
			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_JoinInBed : ThinkNode_JobGiver
	{
		private const int MaxDistanceSquaredToFuck = 10000;

		private static bool CanFuck(Pawn target)
		{
			return xxx.can_fuck(target) || xxx.can_be_fucked(target);
		}

		[SyncMethod]
		private static bool roll_to_skip(Pawn pawn, Pawn target, out float fuckability)
		{
			fuckability = xxx.would_fuck(pawn, target); // 0.0 to 1.0
			if (fuckability < RJWHookupSettings.MinimumAttractivenessToHookup)
			{
				Log.Message($"[RJW] roll_to_skip({xxx.get_pawnname(pawn)},{xxx.get_pawnname(target)}) fuckability too low:({fuckability})");
				return false;
			}

			float reciprocity = xxx.is_animal(target) ? 1.0f : xxx.would_fuck(target, pawn);
			if (reciprocity < RJWHookupSettings.MinimumAttractivenessToHookup)
			{
				Log.Message($"[RJW] roll_to_skip({xxx.get_pawnname(pawn)},{xxx.get_pawnname(target)}) won't fuck me:({fuckability},{reciprocity})");
				return false;
			}

			float chance_to_skip = 0.9f - 0.7f * fuckability;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return Rand.Value < chance_to_skip;
		}

		[SyncMethod]
		public static Pawn find_pawn_to_fuck(Pawn pawn, Map map)
		{
			string pawnName = xxx.get_pawnname(pawn);
			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): starting.");

			bool pawnIsNympho = xxx.is_nympho(pawn);
			bool pawnCanPickAnyone = RJWSettings.WildMode || (pawnIsNympho && RJWHookupSettings.NymphosCanPickAnyone);
			bool pawnCanPickAnimals = pawnCanPickAnyone || xxx.is_zoophile(pawn);

			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): nympho:{pawnIsNympho}, ignores rules:{pawnCanPickAnyone}, zoo:{pawnCanPickAnimals}");

			if (!RJWHookupSettings.ColonistsCanHookup && pawn.IsFreeColonist && !pawnCanPickAnyone)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): is a colonist and colonist hookups are disabled in mod settings");
				return null;
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());

			// Check AllPawns, not just colonists, to include guests.
			List<Pawn> targets = map.mapPawns.AllPawns.Where(x
				=> x.InBed()
				&& x != pawn
				&& !x.Position.IsForbidden(pawn)
				&& xxx.IsTargetPawnOkay(x)
				&& CanFuck(x)
				&& x.Map == pawn.Map
				&& !x.HostileTo(pawn)
				&& (pawnCanPickAnimals || !xxx.is_animal(x))
				&& (xxx.is_laying_down_alone(x) || xxx.in_same_bed(x, pawn))
				).ToList();

			if (!targets.Any())
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): no eligible targets");
				return null;
			}

			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): considering {targets.Count} targets");

			// find lover/partner on same map
			List<Pawn> partners = targets.Where(x
				=> pawn.relations.DirectRelationExists(PawnRelationDefOf.Lover, x)
				|| pawn.relations.DirectRelationExists(PawnRelationDefOf.Fiance, x)
				|| pawn.relations.DirectRelationExists(PawnRelationDefOf.Spouse, x)
				).ToList();

			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): considering {partners.Count} partners");

			if (partners.Any())
			{
				partners.Shuffle(); //Randomize order.
				foreach (Pawn target in partners)
				{
					if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): checking lover {xxx.get_pawnname(target)}");

					if (pawn.Position.DistanceToSquared(target.Position) < MaxDistanceSquaredToFuck
						&& pawn.CanReserveAndReach(target, PathEndMode.OnCell, Danger.Some, 1, 0)
						&& target.CanReserve(pawn, 1, 0))
					{
						if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): banging lover {xxx.get_pawnname(target)}");
						return target;
					}
				}
			}

			// No lovers around... see if the pawn fancies a hookup.  Nymphos and frustrated pawns always do!
			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): no partners available.  checking canHookup");
			bool canHookup = pawnIsNympho || pawnCanPickAnyone || xxx.is_frustrated(pawn) || (xxx.is_horny(pawn) && Rand.Value > RJWHookupSettings.HookupChanceForNonNymphos);
			if (!canHookup)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): no hookup today");
				return null;
			}

			// No cheating from casual hookups... would probably make colony relationship management too annoying
			if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): checking canHookupWithoutCheating");
			bool hookupWouldBeCheating = xxx.HasNonPolyPartnerOnCurrentMap(pawn);
			if (hookupWouldBeCheating)
			{
				if (RJWHookupSettings.NymphosCanCheat && pawnIsNympho && xxx.is_frustrated(pawn))
				{
					if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): I'm a nympho and I'm so frustrated that I'm going to cheat");
					// No return here so they continue searching for hookup
				}
				else
				{
					if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({pawnName}): I want to bang but that's cheating");
					return null;
				}
			}

			Pawn best_fuckee = FindBestPartner(pawn, targets, pawnCanPickAnyone, pawnIsNympho);
			return best_fuckee;
		}

		/// <summary> Checks all of our potential partners to see if anyone's eligible, returning the most attractive and convenient one. </summary>
		protected static Pawn FindBestPartner(Pawn pawn, List<Pawn> targets, bool pawnCanPickAnyone, bool pawnIsNympho)
		{
			string pawnName = xxx.get_pawnname(pawn);

			Pawn best_fuckee = null;
			float best_fuckability_score = 0;

			foreach (Pawn targetPawn in targets)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): checking hookup {xxx.get_pawnname(targetPawn)}");

				// Check to see if the mod settings for hookups allow this pairing
				if (!pawnCanPickAnyone && !HookupAllowedViaSettings(pawn, targetPawn))
					continue;

				// Check for homewrecking (banging a pawn who's in a relationship)
				if (!xxx.is_animal(targetPawn) &&
					xxx.HasNonPolyPartnerOnCurrentMap(targetPawn))
				{
					if (RJWHookupSettings.NymphosCanHomewreck && pawnIsNympho && xxx.is_frustrated(pawn))
					{
						// Hookup allowed... rip colony mood
					}
					else
					{

						if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): not hooking up with {xxx.get_pawnname(targetPawn)} to avoid homewrecking");
						continue;
					}
				}

				// If the pawn has had sex recently and isn't horny right now, skip them.
				if (!SexUtility.ReadyForLovin(targetPawn) && !xxx.is_horny(targetPawn))
				{
					if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)} isn't ready for lovin'");
					continue;
				}

				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)} is sufficiently single");

				// Check to see if the two pawns are willing to bang, and if so remember how much attractive we find them
				float fuckability = 0f;
				if (pawn.CanReserveAndReach(targetPawn, PathEndMode.OnCell, Danger.Some, 1, 0) &&
					targetPawn.CanReserve(pawn, 1, 0) &&
					roll_to_skip(pawn, targetPawn, out fuckability)) // do NOT check pawnIgnoresRules here - these checks, particularly roll_to_skip, are critical
				{
					int dis = pawn.Position.DistanceToSquared(targetPawn.Position);

					if (dis <= 4)
					{
						// Right next to me (in my bed)?  You'll do.
						if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)} is right next to me.  we'll bang, ok?");
						best_fuckability_score = 1.0e6f;
						best_fuckee = targetPawn;
					}
					else if (dis > MaxDistanceSquaredToFuck)
					{
						// too far
						if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)} is too far... distance:{dis} max:{MaxDistanceSquaredToFuck}");
						continue;
					}
					else
					{
						// scaling fuckability by distance may give us more varied results and give the less attractive folks a chance
						float fuckability_score = fuckability / GenMath.Sqrt(GenMath.Sqrt(dis));
						if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)} is totally bangable.  attraction: {fuckability}, score:{fuckability_score}");

						if (fuckability_score > best_fuckability_score)
						{
							best_fuckee = targetPawn;
							best_fuckability_score = fuckability_score;
						}
					}
				}
			}

			if (best_fuckee == null)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): couldn't find anyone to bang");
			}
			else
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): found rando {xxx.get_pawnname(best_fuckee)} with score {best_fuckability_score}");
			}

			return best_fuckee;
		}

		/// <summary> Checks to see if the mod settings allow the two pawns to hookup. </summary>
		protected static bool HookupAllowedViaSettings(Pawn pawn, Pawn targetPawn)
		{
			// Can prisoners hook up with non-prisoners?
			if (!RJWHookupSettings.PrisonersCanHookupWithNonPrisoner && pawn.IsPrisonerOfColony && !targetPawn.IsPrisonerOfColony)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({xxx.get_pawnname(pawn)}): not hooking up with {xxx.get_pawnname(targetPawn)} due to mod setting PrisonersCanHookupWithColonists");
				return false;
			}

			// Can non-prisoners hook up with prisoners?
			if (!RJWHookupSettings.CanHookupWithPrisoner && !pawn.IsPrisonerOfColony && targetPawn.IsPrisonerOfColony)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({xxx.get_pawnname(pawn)}): not hooking up with {xxx.get_pawnname(targetPawn)} due to mod setting ColonistsCanHookupWithPrisoner");
				return false;
			}

			if (!RJWHookupSettings.ColonistsCanHookupWithVisitor && pawn.IsFreeColonist && !targetPawn.IsPrisonerOfColony)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({xxx.get_pawnname(pawn)}): not hooking up with {xxx.get_pawnname(targetPawn)} due to mod setting ColonistsCanHookupWithVisitor");
				return false;
			}

			if (!RJWHookupSettings.VisitorsCanHookupWithColonists && !pawn.IsPrisonerOfColony && targetPawn.IsFreeColonist && pawn.Faction != targetPawn.Faction )
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] find_pawn_to_fuck({xxx.get_pawnname(pawn)}): not hooking up with {xxx.get_pawnname(targetPawn)} due to mod setting VisitorsCanHookupWithColonists");
				return false;
			}

			// TODO: Not sure if this handles all the pawn-on-animal cases.

			return true;
		}

		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) Log.Message($"[RJW] 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 (xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn))
				{
					//--Log.Message("   finding partner");
					Pawn partner = find_pawn_to_fuck(pawn, pawn.Map);

					//--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 new Job(DefDatabase<JobDef>.GetNamed("JoinInBed"), pawn, partner, bed);
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_NymphJoinInBed : ThinkNode_JobGiver
	{
		private static bool roll_to_skip(Pawn nymph, Pawn target)
		{
			float fuckability = xxx.would_fuck(nymph, target); // 0.0 to 1.0
			if (fuckability < 0.1f)
				return false;
			float chance_to_skip = 0.9f - 0.7f * fuckability;
			return Rand.Value < chance_to_skip;
		}

		private static bool is_healthy(Pawn target)
		{
			return xxx.is_healthy(target) &&
			       (xxx.can_fuck(target) || xxx.can_be_fucked(target));
		}

		public static Pawn find_pawn_to_fuck(Pawn nymph, Map map)
		{
			Pawn best_fuckee = null;
			float best_distance = 1.0e6f;
			List<Pawn> valid_targets = new List<Pawn>();

			// find lover/partner on same map
			IEnumerable<Pawn> targets = map.mapPawns.AllPawnsSpawned.Where(x => x != nymph && x.InBed() && !x.Position.IsForbidden(nymph) && !x.Suspended && !x.Downed && is_healthy(x));

			foreach (DirectPawnRelation relation in nymph.relations.DirectRelations)
			{
				if (relation.def != PawnRelationDefOf.Lover
					&& relation.def != PawnRelationDefOf.Spouse
					&& relation.def != PawnRelationDefOf.Fiance)
					//|| (relation.def != PawnRelationDefOf.Bond && (!xxx.is_animal(relation.otherPawn) || (xxx.is_zoophile(nymph) && RJWSettings.bestiality_enabled))))
						continue;
				{
					//Log.Message("[RJW] find_pawn_to_fuck( " + xxx.get_pawnname(best_fuckee) + " ) Lover/Spouse/Fiance found");
					if (nymph.Position.DistanceToSquared(relation.otherPawn.Position) < 60 &&
						nymph.CanReserveAndReach(relation.otherPawn, PathEndMode.OnCell, Danger.Some, 1, 0) &&
						relation.otherPawn.CanReserve(nymph, 1, 0))

						if (targets.Contains(relation.otherPawn))
							valid_targets.Add(relation.otherPawn);
					//Log.Message("[RJW] find_pawn_to_fuck( " + xxx.get_pawnname(best_fuckee) + " ) Lover/Spouse/Fiance cant be fucked right now");
				}
			}

			if (valid_targets != null && valid_targets.Any())
			{
				foreach (Pawn q in valid_targets)
				{
					if (xxx.is_laying_down_alone(q) &&
						nymph.CanReserveAndReach(q, PathEndMode.OnCell, Danger.Some, 1, 0) &&
						q.CanReserve(nymph, 1, 0) &&
						roll_to_skip(nymph, q))
					{
						int dis = nymph.Position.DistanceToSquared(q.Position);
						if (dis < best_distance)
						{
							best_fuckee = q;
							best_distance = dis;
						}
					}
				}
				if (best_fuckee != null)
					return best_fuckee;
			}

			targets = targets.Where(x => xxx.is_human(x));
			foreach (Pawn q in targets)
			{
				if (xxx.is_laying_down_alone(q) &&
					nymph.CanReserveAndReach(q, PathEndMode.OnCell, Danger.Some, 1, 0) &&
				    q.CanReserve(nymph, 1, 0) &&
				    roll_to_skip(nymph, q))
				{
					int dis = nymph.Position.DistanceToSquared(q.Position);
					if (dis < best_distance)
					{
						best_fuckee = q;
						best_distance = dis;
					}
				}
			}
			return best_fuckee;
		}

		protected override Job TryGiveJob(Pawn nymph)
		{
			//--Log.Message("[RJW] JobGiver_NymphJoinInBed( " + xxx.get_pawnname(nymph) + " ) called");

			if ((nymph.CurJob == null || nymph.CurJob.def == JobDefOf.LayDown))
			{
				//--Log.Message("   checking nympho and abilities");
				if ((xxx.can_fuck(nymph) || xxx.can_be_fucked(nymph)))
				{
					//--Log.Message("   finding partner");
					Pawn partner = find_pawn_to_fuck(nymph, nymph.Map);

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

					Building_Bed bed;

					if (xxx.is_human(partner))
					{
						// Can never be null, since find checks for bed.
						bed = partner.CurrentBed();

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

						//--Log.Message("   returning job");
						return new Job(DefDatabase<JobDef>.GetNamed("NymphJoinInBed"), partner, bed);
					}
					/*
					else if (xxx.is_animal(partner) && xxx.is_zoophile(nymph) && RJWSettings.bestiality_enabled)
					{
						if (xxx.can_rape(nymph) && Rand.Value < 0.5f)
						{
							if (nymph.CurJob != null)
								nymph.jobs.curDriver.EndJobWith(JobCondition.InterruptForced);

								return new Job(xxx.bestiality, partner);
						}

						//not sure if animal can "wakeup" and go to nymph
						bed = nymph.ownership.OwnedBed;
						if (!xxx.can_be_fucked(nymph) || bed == null || !partner.CanReach(bed, PathEndMode.OnCell, Danger.Some)) return null;

						if (nymph.CurJob != null)
							nymph.jobs.curDriver.EndJobWith(JobCondition.InterruptForced);

						return new Job(xxx.bestialityForFemale, partner, bed, bed.SleepPosOfAssignedPawn(nymph));
					}
					*/
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_RandomRape : ThinkNode_JobGiver
	{
		public static Pawn find_victim(Pawn rapist, Map m)
		{
			Pawn victim = null;

			IEnumerable<Pawn> victims = m.mapPawns.AllPawnsSpawned.Where(x
				=> x != rapist
				&& xxx.is_not_dying(x)
				&& xxx.can_get_raped(x)
				&& !x.Suspended
				&& !x.IsForbidden(rapist)
				&& rapist.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.HostileTo(rapist)
				);

			float best_fuckability = 0.10f; // Don't rape pawns with <10% fuckability

			//Animal rape
			if (xxx.is_zoophile(rapist) && RJWSettings.bestiality_enabled)
			{
				foreach (var animal in victims.Where(x => xxx.is_animal(x)))
				{
					float fuc = xxx.would_fuck(rapist, animal, true, true);
					if (fuc > best_fuckability)
					{
						best_fuckability = fuc;
						victim = animal;
					}
				}

				if (victim != null)
					return victim;
			}

			// Humanlike rape - could be prisoner, colonist, or non-hostile outsider
			foreach (var human in victims.Where(x => !xxx.is_animal(x)))
			{
				float fuc = xxx.would_fuck(rapist, human, true, true);
				if (fuc > best_fuckability)
				{
					best_fuckability = fuc;
					victim = human;
				}
			}

			return victim;
		}

		protected override Job TryGiveJob(Pawn rapist)
		{
			//Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called");
			if (SexUtility.ReadyForLovin(rapist) || xxx.need_some_sex(rapist) > 1f)
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (xxx.is_healthy_enough(rapist) && xxx.can_rape(rapist))
				{
					Pawn victim = find_victim(rapist, rapist.Map);

					if (victim != null)
					{
						//Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) - found victim " + xxx.get_pawnname(victim));
						return new Job(xxx.random_rape, victim);
					}
				}
			}

			return null;
		}
	}
}


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
	{
		public float targetAcquireRadius = 60f;

		protected override Job TryGiveJob(Pawn rapist)
		{
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) called0");
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) 0 " + SexUtility.ReadyForLovin(rapist));
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) 1 " + (xxx.need_some_sex(rapist) <= 1f));
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) 2 " + !(SexUtility.ReadyForLovin(rapist) || xxx.need_some_sex(rapist) <= 1f));
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) 1 " + Find.TickManager.TicksGame);
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) 2 " + rapist.mindState.canLovinTick);

			if (rapist.Drafted) return null;

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

			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) can rape");

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

			//Log.Message("[RJW] JobGiver_RapeEnemy::ChoosedJobDef( " + xxx.get_pawnname(rapist) + " ) - " + rapeEnemyJobDef.ToString() + " choosed");
			Pawn victim = rapeEnemyJobDef?.FindVictim(rapist, rapist.Map, targetAcquireRadius);

			//Log.Message("[RJW] JobGiver_RapeEnemy::FoundVictim( " + xxx.get_pawnname(victim) + " )");

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

			return victim != null ? new Job(rapeEnemyJobDef, victim) : null;
			/*
			else
			{
				//--Log.Message("[RJW]" + this.GetType().ToString() + "::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) - unable to find victim");
				rapist.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
			}
			*/
			//else {  //--Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(rapist) + " ) - too fast to play next"); }
		}
	}
}