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 bool usedCondom;

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

		public static void roll_to_hit(Pawn pawn, Pawn partner)
		{
			SexUtility.Sex_Beatings(pawn, partner, true);
		}

		[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.RapeTargetAlert(pawn, Target);

			usedCondom = CondomUtility.TryUseCondom(pawn);

			//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, usedCondom, true);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	internal class JobDef_RapeEnemy : JobDef
	{
		public List<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)
		{
			return instance.FindVictim(rapist, m);
		}
	}

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

		//override can_rape mechanics
		protected bool requireCanRape = true;

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

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

		[SyncMethod]
		public virtual Pawn FindVictim(Pawn rapist, Map m)
		{
			//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 (requireCanRape && !xxx.can_rape(rapist)) return null;
			Pawn best_target = null;
			List<Pawn> animalTargets = new List<Pawn>();
			float best_fuckability = 0.20f; // Don't rape pawns with <20% fuckability

			var targets = m.mapPawns.AllPawnsSpawned
				.Where(target => !target.IsForbidden(rapist) && target != rapist && target.HostileTo(rapist))
				.Where(target => xxx.can_path_to_target(rapist, target.Position))
				.ToList();

			if (targets.Any(target => IsBlocking(rapist, target)))
			{
				return null;
			}

			var validTargets = targets
				.Where(target => IsValidTarget(rapist, target))
				.ToList();

			foreach (var target in validTargets)
			{
				float fuc = GetFuckability(rapist, target);

				if (fuc > best_fuckability)
				{
					if (xxx.is_animal(rapist))
					{
						animalTargets.Add(target);
					}
					else
					{
						best_target = target;
						best_fuckability = fuc;
					}
				}
			}
			//Log.Message("[RJW]"+this.GetType().ToString()+"::TryGiveJob( " + xxx.get_pawnname(rapist) + " -> " + xxx.get_pawnname(best_rapee) + " ) - fuckability:" + best_fuckability + " ");
			return animalTargets.Any() ? animalTargets.RandomElement() : best_target;
		}

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

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

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

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

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

		public virtual float GetFuckability(Pawn rapist, Pawn target)
		{
			//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 * SexAppraiser.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
			}
			return !SexAppraiser.would_rape(rapist, target) ? 0f
				: SexAppraiser.would_fuck(rapist, target, invert_opinion: true, ignore_bleeding: true, ignore_gender: true);
		}

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

license: mit


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) && !x.fertilized) select x).Count() > 0)
				if ((from x in target.health.hediffSet.GetHediffs<Hediff_InsectEgg>() where x.IsParent(rapist) select x).Count() > 0)
				{
					return 1f;
				}
			}
			return 0f;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	internal class JobDriver_RapeEnemyByMech : JobDriver_RapeEnemy
	{
		//public 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.requireCanRape = 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 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, xxx.max_rapists_per_prisoner, 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(xxx.get_pawnname(pawn) + " is trying to rape a corpse of " + xxx.get_pawnname(Target.InnerPawn), 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, false, 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 pawn, Map m)
		{
			IEnumerable<Pawn> targets = m.mapPawns.AllPawns.Where(x
				=> x != pawn
				&& IsPrisonerOf(x, pawn.Faction)
				&& xxx.can_get_raped(x)
				&& !x.Position.IsForbidden(pawn)
				);

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

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

				//--Log.Message(xxx.get_pawnname(pawn) + "->" + xxx.get_pawnname(target) + ":" + fuc);
				if (SexAppraiser.would_fuck(pawn, 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 pawn)
		{
			//Log.Message("[RJW] JobGiver_AIRapePrisoner::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called ");

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

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

					if (prisoner != null)
					{
						//--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 pawn, Map m)
		{
			if (!DesignatorsData.rjwComfort.Any()) return null;
			Pawn best_rapee = null;
			float best_fuckability = 0.10f; // Don't rape prisoners with <10% fuckability
			IEnumerable<Pawn> targets = DesignatorsData.rjwComfort.Where(x
				=> x != pawn
				&& xxx.can_get_raped(x)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, xxx.max_rapists_per_prisoner, 0)
				&& !x.IsForbidden(pawn)
				&& SexAppraiser.would_rape(pawn, x)
				);

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

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

				float fuc = 0.0f;
				if (xxx.is_animal(target))
					fuc = SexAppraiser.would_fuck_animal(pawn, target, true);
				else if (xxx.is_human(target))
					fuc = SexAppraiser.would_fuck(pawn, target, true);
				//--Log.Message(pawn.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 pawn)
		{
			//Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called0");

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

			if (pawn.Drafted) return null;

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

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

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

			Pawn target = find_prisoner_to_rape(pawn, pawn.Map);
			//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) 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 pawn)
		{
			IntVec3 position = pawn.Position;
			int bestPosition = -100;
			IntVec3 cell = pawn.Position;
			int maxDistance = 40;

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

			//Log.Message("[RJW] Pawn is " + xxx.get_pawnname(pawn) + ", 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(pawn.Map)
				&& x.InAllowedArea(pawn)
				&& x.GetDangerFor(pawn, pawn.Map) != Danger.Deadly
				&& !x.ContainsTrap(pawn.Map)
				&& !x.ContainsStaticFire(pawn.Map)
				).Distinct().ToList();

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

			foreach (IntVec3 random_cell in random_cells)
			{
				if (!xxx.can_path_to_target(pawn, random_cell))
					continue;// too far

				int score = 0;
				Room room = random_cell.GetRoom(pawn.Map);

				bool might_be_seen = all_pawns.Any(x
					=> GenSight.LineOfSight(x.Position, random_cell, pawn.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, pawn.Map)
						&& x.Position.DistanceTo(random_cell) < 6
						&& !x.Awake()
						))
						score += 50;
				}

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

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

				if (room == pawn.Position.GetRoom(pawn.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(pawn))
					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 pawn)
		{
			//--Log.Message("[RJW] JobGiver_DoFappin::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called");

			if (pawn.Drafted) return null;

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

			// Whores only fap if frustrated, unless imprisoned.
			if ((SexUtility.ReadyForLovin(pawn) && (!xxx.is_whore(pawn) || pawn.IsPrisoner )) || xxx.is_frustrated(pawn))
			{
				if (pawn.jobs.curDriver is JobDriver_LayDown && RJWPreferenceSettings.FapInBed)
				{
					Building_Bed bed = ((JobDriver_LayDown)pawn.jobs.curDriver).Bed;
					if (bed != null) return new Job(xxx.fappin, bed);
				}
				else if ((xxx.is_frustrated(pawn) || xxx.has_quirk(pawn, "Exhibitionist")) && RJWPreferenceSettings.FapEverywhere)
				{
					return new Job(xxx.quickfap, FindFapLocation(pawn));
				}
			}
			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 = SexAppraiser.would_fuck(pawn, target); // 0.0 to 1.0
			if (fuckability < RJWHookupSettings.MinimumFuckabilityToHookup)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] roll_to_skip(I, {xxx.get_pawnname(pawn)} won't fuck {xxx.get_pawnname(target)}), ({fuckability})");
				return false;
			}

			float reciprocity = xxx.is_animal(target) ? 1.0f : SexAppraiser.would_fuck(target, pawn);
			if (reciprocity < RJWHookupSettings.MinimumFuckabilityToHookup)
			{
				if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] roll_to_skip({xxx.get_pawnname(target)} won't fuck me, {xxx.get_pawnname(pawn)}), ({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)) && RJWSettings.bestiality_enabled;

			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_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)
						&& SexAppraiser.would_fuck(pawn, target) > 0.1f
						&& SexAppraiser.would_fuck(target, pawn) > 0.1f)
					{
						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 (RJWHookupSettings.NymphosCanHomewreckReverse && xxx.is_nympho(targetPawn) && xxx.is_frustrated(targetPawn))
					{
						// 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");

				if (!xxx.is_animal(targetPawn))
				{
					float relations = pawn.relations.OpinionOf(targetPawn);
					if (relations < RJWHookupSettings.MinimumRelationshipToHookup)
					{
						if (!(relations > 0 && xxx.is_nympho(pawn)))
						{
							if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)}, i dont like them:({relations})");
							continue;
						}
					}

					relations = targetPawn.relations.OpinionOf(pawn);
					if (relations < RJWHookupSettings.MinimumRelationshipToHookup)
					{
						if (!(relations > 0 && xxx.is_nympho(targetPawn)))
						{
							if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)}, dont like me:({relations})");
							continue;
						}
					}

					float attraction = pawn.relations.SecondaryRomanceChanceFactor(targetPawn);
					if (attraction < RJWHookupSettings.MinimumAttractivenessToHookup)
					{
						if (!(attraction > 0 && xxx.is_nympho(pawn)))
						{
							if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)}, i dont find them attractive:({attraction})");
							continue;
						}
					}
					attraction = targetPawn.relations.SecondaryRomanceChanceFactor(pawn);
					if (attraction < RJWHookupSettings.MinimumAttractivenessToHookup)
					{
						if (!(attraction > 0 && xxx.is_nympho(targetPawn)))
						{
							if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] FindBestPartner({pawnName}): hookup {xxx.get_pawnname(targetPawn)}, doesnt find me attractive:({attraction})");
							continue;
						}
					}
				}

				// 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?
			if (pawn.IsPrisonerOfColony || pawn.IsPrisoner)
			{
				if (!RJWHookupSettings.PrisonersCanHookupWithNonPrisoner && !(targetPawn.IsPrisonerOfColony || targetPawn.IsPrisoner))
				{
					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 PrisonersCanHookupWithNonPrisoner");
					return false;
				}

				if (!RJWHookupSettings.PrisonersCanHookupWithPrisoner && (targetPawn.IsPrisonerOfColony || targetPawn.IsPrisoner))
				{
					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 PrisonersCanHookupWithPrisoner");
					return false;
				}
			}
			else
			{
				// Can non prisoners hook up with prisoners?
				if (!RJWHookupSettings.CanHookupWithPrisoner && (targetPawn.IsPrisonerOfColony || targetPawn.IsPrisoner))
				{
					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 CanHookupWithPrisoner");
					return false;
				}
			}

			// Can colonist hook up with visitors?
			if (pawn.IsFreeColonist)
			{
				if (!RJWHookupSettings.ColonistsCanHookupWithVisitor && targetPawn.Faction != Faction.OfPlayer && !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;
				}
			}

			// Can visitors hook up?
			if (pawn.Faction != Faction.OfPlayer && !pawn.IsPrisonerOfColony)
			{
				// visitors vs colonist
				if (!RJWHookupSettings.VisitorsCanHookupWithColonists && targetPawn.IsColonist)
				{
					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;
				}

				// visitors vs visitors
				if (!RJWHookupSettings.VisitorsCanHookupWithVisitors && targetPawn.Faction != Faction.OfPlayer)
				{
					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 VisitorsCanHookupWithVisitors");
					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 Pawn find_victim(Pawn pawn, Map m)
		{
			Pawn victim = null;

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

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

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

					float fuc = SexAppraiser.would_fuck(pawn, target, true, true);
					if (fuc > best_fuckability)
					{
						best_fuckability = fuc;
						victim = target;
					}
				}

				if (victim != null)
					return victim;
			}

			// Humanlike rape - could be prisoner, colonist, or non-hostile outsider
			foreach (Pawn target in targets.Where(x => !xxx.is_animal(x)))
			{
				if (!xxx.can_path_to_target(pawn, target.Position))
					continue;// too far

				float fuc = SexAppraiser.would_fuck(pawn, target, true, true);
				if (fuc > best_fuckability)
				{
					best_fuckability = fuc;
					victim = target;
				}
			}

			return victim;
		}

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

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

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

			//Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - found victim " + xxx.get_pawnname(victim));
			return new Job(xxx.random_rape, victim);
		}
	}
}


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

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

			if (pawn.Drafted) return null;

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

			if (!xxx.can_rape(pawn)) return null;
			//Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) can rape");

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

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

			//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)
			pawn.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(pawn) + " ) - unable to find victim");
				pawn.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
			}
			*/
			//else {  //--Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) - too fast to play next"); }
		}
	}
}


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

namespace rjw
{
	public class JobGiver_ViolateCorpse : ThinkNode_JobGiver
	{
		public static Corpse find_corpse(Pawn pawn, Map m)
		{
			//Log.Message("JobGiver_ViolateCorpse::find_corpse( " + xxx.get_pawnname(pawn) + " ) called");
			Corpse found = null;
			float best_fuckability = 0.1f;

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

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

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

				float fuc = SexAppraiser.would_fuck(pawn, target, false, false);
				//Log.Message("   " + xxx.get_pawnname(target.InnerPawn) + " =  " + fuc + ",  best =  " + best_fuckability);
				if (!(fuc > best_fuckability)) continue;

				found = target;
				best_fuckability = fuc;
			}
			return found;
		}

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

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

			if (pawn.Drafted) return null;

			//--Log.Message("[RJW] JobGiver_ViolateCorpse::TryGiveJob for ( " + xxx.get_pawnname(pawn) + " )");
			if (SexUtility.ReadyForLovin(pawn) || xxx.need_some_sex(pawn) > 1f)
			{
				//--Log.Message("[RJW] JobGiver_ViolateCorpse::TryGiveJob, can love ");
				if (!xxx.can_rape(pawn)) return null;

				var target = find_corpse(pawn, pawn.Map);
				//--Log.Message("[RJW] JobGiver_ViolateCorpse::TryGiveJob - target is " + (target == null ? "NULL" : "Found"));
				if (target != null)
				{
					return new Job(xxx.violate_corpse, target);
				}
				// Ticks should only be increased after successful sex.
			}

			return null;
		}
	}
}


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

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

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

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

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


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_AverageMoneyByWhore : PawnColumnWorker_TextCenter
	{
		public static readonly RecordDef EarnedMoneyByWhore = DefDatabase<RecordDef>.GetNamed("EarnedMoneyByWhore");
		public static readonly RecordDef CountOfWhore = DefDatabase<RecordDef>.GetNamed("CountOfWhore");

		protected internal float score;

		protected override string GetTextFor(Pawn pawn)
		{
			float total = pawn.records.GetValue(EarnedMoneyByWhore);
			float count = pawn.records.GetValue(CountOfWhore);
			if ((int)count == 0)
			{
				return "-";
			}
			score = (total / count);
			return ((int)score).ToString();
		}

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

		private float GetValueToCompare(Pawn pawn)
		{
			return score;
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_CountOfWhore : PawnColumnWorker_TextCenter
	{
		public static readonly RecordDef CountOfWhore = DefDatabase<RecordDef>.GetNamed("CountOfWhore");

		protected internal int score;

		protected override string GetTextFor(Pawn pawn)
		{
			score = pawn.records.GetAsInt(CountOfWhore);
			return pawn.records.GetValue(CountOfWhore).ToString();
		}

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

		private int GetValueToCompare(Pawn pawn)
		{
			return score;
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_EarnedMoneyByWhore : PawnColumnWorker_TextCenter
	{
		public static readonly RecordDef EarnedMoneyByWhore = DefDatabase<RecordDef>.GetNamed("EarnedMoneyByWhore");

		protected internal int score;

		protected override string GetTextFor(Pawn pawn)
		{
			score = pawn.records.GetAsInt(EarnedMoneyByWhore);
			return pawn.records.GetValue(EarnedMoneyByWhore).ToString();
		}

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

		private int GetValueToCompare(Pawn pawn)
		{
			return score;
		}
	}
}


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

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

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


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsWhore : PawnColumnWorker_Icon
	{
		private static readonly Texture2D serviceOn = ContentFinder<Texture2D>.Get("UI/Tab/Service_on");
		private static readonly Texture2D serviceOff = ContentFinder<Texture2D>.Get("UI/Tab/Service_off");

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


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_Mood : PawnColumnWorker_TextCenter
	{
		protected internal float score;

		protected override string GetTextFor(Pawn pawn)
		{
			score = pawn.needs.mood.CurLevelPercentage;
			return score.ToStringPercent();
		}

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

		private float GetValueToCompare(Pawn pawn)
		{
			return score;
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_PriceRangeOfWhore : PawnColumnWorker_TextCenter
	{
		protected internal int min;
		protected internal int max;

		protected override string GetTextFor(Pawn pawn)
		{
			min = WhoringHelper.WhoreMinPrice(pawn);
			max = WhoringHelper.WhoreMaxPrice(pawn);
			return string.Format("{0} - {1}", min, max);
		}

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

		private int GetValueToCompare(Pawn pawn)
		{
			return min;
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_RoomAdjustmentOfWhore : PawnColumnWorker_TextCenter
	{
		protected internal float score;

		protected override string GetTextFor(Pawn pawn)
		{
			score = WhoringHelper.WhoreRoomAdjustment(pawn);
			Room ownedRoom = pawn.ownership.OwnedRoom;
			int scoreStageIndex;
			if (ownedRoom == null)
			{
				scoreStageIndex = 0;
			}
			else
			{
				scoreStageIndex = RoomStatDefOf.Impressiveness.GetScoreStageIndex(ownedRoom.GetStat(RoomStatDefOf.Impressiveness));
			}
			return string.Format("{0} -> {1}", scoreStageIndex, score.ToStringPercent());
		}

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

		private float GetValueToCompare(Pawn pawn)
		{
			return score;
		}
	}
}


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

namespace rjw.MainTab
{
	public abstract class PawnColumnWorker_TextCenter : PawnColumnWorker_Text
	{
		public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
		{
			Rect rect2 = new Rect(rect.x, rect.y, rect.width, Mathf.Min(rect.height, 30f));
			string textFor = GetTextFor(pawn);
			if (textFor != null)
			{
				Text.Font = GameFont.Small;
				Text.Anchor = TextAnchor.MiddleCenter;
				Text.WordWrap = false;
				Widgets.Label(rect2, textFor);
				Text.WordWrap = true;
				Text.Anchor = TextAnchor.UpperLeft;
				string tip = GetTip(pawn);
				if (!tip.NullOrEmpty())
				{
					TooltipHandler.TipRegion(rect2, tip);
				}
			}
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_WhoreExperience : PawnColumnWorker_TextCenter
	{
		public static readonly RecordDef CountOfWhore = DefDatabase<RecordDef>.GetNamed("CountOfWhore");

		public static readonly HashSet<string> backstories = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("WhoreBackstories").strings);

		protected override string GetTextFor(Pawn pawn)
		{

			int b = backstories.Contains(pawn.story?.adulthood?.titleShort) ? 30 : 0;
			int score = pawn.records.GetAsInt(CountOfWhore);
			return (score + b).ToString();
		}
	}
}


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

namespace rjw.MainTab
{
	public class PawnTable_Whores : PawnTable
	{
		public PawnTable_Whores(PawnTableDef def, Func<IEnumerable<Pawn>> pawnsGetter, int uiWidth, int uiHeight) : base(def, pawnsGetter, uiWidth, uiHeight) { }

		protected override IEnumerable<Pawn> LabelSortFunction(IEnumerable<Pawn> input)
		{
			//return input.OrderBy(p => p.Name?.Numerical != false).ThenBy(p => (p.Name as NameSingle)?.Number ?? 0).ThenBy(p => p.def.label);
			return input.OrderBy(p => xxx.get_pawnname(p));
		}

		protected override IEnumerable<Pawn> PrimarySortFunction(IEnumerable<Pawn> input)
		{
			///return input.OrderByDescending(p => p.Faction?.Name);
			//return input.OrderBy(p => xxx.get_pawnname(p));
			return input.OrderByDescending(p => p.IsColonist);
		}
	}
}


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

namespace rjw
{
	public class MentalState_RandomRape : SexualMentalState
	{

		public override void PostStart(string reason)
		{
			base.PostStart(reason);
			this.pawn.mindState.canLovinTick = -1;
		}
		public override bool ForceHostileTo(Thing t)
		{
			/*
			//planning random raper hostile to other colonists. but now, injured raper wont rape.
			if ((this.pawn.jobs != null) &&
				(this.pawn.jobs.curDriver != null) &&
				(this.pawn.jobs.curDriver as JobDriver_Rape != null))
			{
				return true;
			}*/
			return false;
		}

		public override bool ForceHostileTo(Faction f)
		{
			/*
			if ((this.pawn.jobs != null) &&
				(this.pawn.jobs.curDriver != null) &&
				(this.pawn.jobs.curDriver as JobDriver_Rape != null))
			{
				return true;
			}*/
			return false;
		}

		public override RandomSocialMode SocialModeMax()
		{
			return RandomSocialMode.Off;
		}
	}
}


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

namespace rjw
{
	public class SexualMentalState : MentalState
	{
		public override void MentalStateTick()
		{
			if (this.pawn.IsHashIntervalTick(150))
			{
				if (xxx.need_some_sex(pawn) < 2f)
				{
					this.RecoverFromState();
					return;
				}
			}
			base.MentalStateTick();
		}
	}

	public class SexualMentalStateWorker : MentalStateWorker
	{
		public override bool StateCanOccur(Pawn pawn)
		{
			SexualMentalStateDef d = this.def as SexualMentalStateDef;
			if (d == null)
			{
				return base.StateCanOccur(pawn);
			}
			else
			{
				return base.StateCanOccur(pawn) &&
					(!d.requireCanFuck || xxx.can_fuck(pawn)) &&
					(!d.requireCanBeFuck || xxx.can_be_fucked(pawn)) &&
					(!d.requireCanRape || xxx.can_rape(pawn)) &&
					(!d.requireCanGetRaped || xxx.can_get_raped(pawn) );
			}
		}
	}

	public class SexualMentalBreakWorker : MentalBreakWorker
	{
		public override float CommonalityFor(Pawn pawn)
		{
			SexualMentalBreakDef d = this.def as SexualMentalBreakDef;
			if (d == null)
			{
				return base.CommonalityFor(pawn);
			}
			else
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				return base.CommonalityFor(pawn) * d.commonalityMultiplierBySexNeed.Evaluate(need_sex.CurLevelPercentage*100f);
			}
		}
	}
	public class SexualMentalStateDef : MentalStateDef
	{
		public bool requireCanFuck = false;
		public bool requireCanBeFuck = false;
		public bool requireCanRape = false;
		public bool requireCanGetRaped = false;
	}
	public class SexualMentalBreakDef : MentalBreakDef
	{
		public SimpleCurve commonalityMultiplierBySexNeed;
	}
}


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

namespace rjw
{
	/// <summary>
	/// Helper class for ChJees's Androids mod.
	/// </summary>
	[StaticConstructorOnStartup]
	public static class AndroidsCompatibility
	{
		public static Type androidCompatType;
		public static readonly string typeName = "Androids.SexualizeAndroidRJW";
		private static bool foundType;

		static AndroidsCompatibility()
		{
			try
			{
				androidCompatType = Type.GetType(typeName);
				foundType = true;
				//Log.Message("Found Type: Androids.SexualizeAndroidRJW");
			}
			catch
			{
				foundType = false;
				//Log.Message("Did NOT find Type: Androids.SexualizeAndroidRJW");
			}
		}

		/*private static bool TestPredicate(DefModExtension extension)
		{
			if (extension == null)
				return false;

			Log.Message($"Predicate: {extension} : {extension.GetType()?.FullName}");
			return extension.GetType().FullName == typeName;
		}*/

		public static bool IsAndroid(ThingDef def)
		{
			if (def == null || !foundType)
			{
				return false;
			}

			return def.modExtensions != null && def.modExtensions.Any(extension => extension.GetType().FullName == typeName);
		}

		public static bool IsAndroid(Thing thing)
		{
			return IsAndroid(thing.def);
		}

		public static bool AndroidPenisFertility(Pawn pawn)
		{
			//androids only fertile with archotech parts
			BodyPartRecord Part = Genital_Helper.get_genitals(pawn);
			return (pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
				(hed.Part == Part) &&
				(hed.def == Genital_Helper.archotech_penis)
				));
		}

		public static bool AndroidVaginaFertility(Pawn pawn)
		{
			//androids only fertile with archotech parts
			BodyPartRecord Part = Genital_Helper.get_genitals(pawn);
			return (pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
				(hed.Part == Part) &&
				(hed.def == Genital_Helper.archotech_vagina)
				));
		}

	}
}


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

// Adds options to the right-click menu for bondage gear to equip the gear on prisoners/downed pawns
namespace rjw
{
	public class CompBondageGear : CompUsable
	{
		public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn pawn)
		{
			if ((pawn.Map != null) && (pawn.Map == Find.CurrentMap))// && (pawn.Map.mapPawns.PrisonersOfColonyCount > 0)
			{
				if (!pawn.CanReserve(parent))
					yield return new FloatMenuOption(FloatMenuOptionLabel + " on (" + "Reserved".Translate() + ")", null, MenuOptionPriority.DisabledOption);
				else if (pawn.CanReach(parent, PathEndMode.Touch, Danger.Some))
					foreach (Pawn other in pawn.Map.mapPawns.AllPawns)
						if ((other != pawn) && other.Spawned && (other.Downed || other.IsPrisonerOfColony))
							yield return this.make_option(FloatMenuOptionLabel + " on " + xxx.get_pawnname(other), pawn, other, other.IsPrisonerOfColony ? WorkTypeDefOf.Warden : null);
			}
		}
	}
}


code:
using System;
using RimWorld;
using Verse;
using Multiplayer.API;

namespace rjw
{
	public class CompCryptoStamped : ThingComp
	{
		public string name;
		public string key;

		[SyncMethod]
		public string random_hex_byte()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var rv = Rand.RangeInclusive(0x00, 0xFF);
			var padding = (rv < 0x10) ? "0" : "";
			return padding + rv.ToString("X");
		}

		public override void Initialize(CompProperties pro)
		{
			name = NameGenerator.GenerateName(RulePackDef.Named("EngravedName"));
			key = "";
			for (int i = 0; i < 16; ++i)
				key += random_hex_byte();
		}

		public override void PostExposeData()
		{
			base.PostExposeData();
			Scribe_Values.Look<string>(ref name, "engraved_name");
			Scribe_Values.Look<string>(ref key, "cryptostamp");
		}

		public override bool AllowStackWith(Thing t)
		{
			return false;
		}

		public override string CompInspectStringExtra()
		{
			var inspect_engraving = "Engraved with the name \"" + name + "\"";
			var inspect_key = "Cryptostamp: " + key;
			return base.CompInspectStringExtra() + inspect_engraving + "\n" + inspect_key;
		}

		public override string TransformLabel(string lab)
		{
			return lab + " \"" + name + "\"";
		}

		public bool matches(CompHoloCryptoStamped other)
		{
			return String.Equals(key, other.key);
		}

		public void copy_stamp_from(CompHoloCryptoStamped other)
		{
			name = other.name;
			key = other.key;
		}
	}

	public class CompProperties_CryptoStamped : CompProperties
	{
		public CompProperties_CryptoStamped()
		{
			compClass = typeof(CompHoloCryptoStamped);
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class CompGetBondageGear : CompUseEffect
	{
		public override float OrderPriority
		{
			get
			{
				return -79;
			}
		}

		public override void DoEffect(Pawn p)
		{
			base.DoEffect(p);
			var app = parent as Apparel;
			if ((p.apparel != null) && (app != null))
				p.apparel.Wear(app);
		}
	}
}


code:
using System;
using RimWorld;
using Verse;
using Multiplayer.API;

namespace rjw
{
	public class CompHoloCryptoStamped : ThingComp
	{
		public string name;
		public string key;

		[SyncMethod]
		public string random_hex_byte()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var rv = Rand.RangeInclusive(0x00, 0xFF);
			var padding = (rv < 0x10) ? "0" : "";
			return padding + rv.ToString("X");
		}

		public override void Initialize(CompProperties pro)
		{
			name = NameGenerator.GenerateName(RulePackDef.Named("EngravedName"));
			key = "";
			for (int i = 0; i < 16; ++i)
				key += random_hex_byte();
		}

		public override void PostExposeData()
		{
			base.PostExposeData();
			Scribe_Values.Look<string>(ref name, "engraved_name");
			Scribe_Values.Look<string>(ref key, "cryptostamp");
		}

		public override bool AllowStackWith(Thing t)
		{
			return false;
		}

		public override string CompInspectStringExtra()
		{
			var inspect_engraving = "Engraved with the name \"" + name + "\"";
			var inspect_key = "Cryptostamp: " + key;
			return base.CompInspectStringExtra() + inspect_engraving + "\n" + inspect_key;
		}

		public override string TransformLabel(string lab)
		{
			return lab + " \"" + name + "\"";
		}

		public bool matches(CompHoloCryptoStamped other)
		{
			return String.Equals(key, other.key);
		}

		public void copy_stamp_from(CompHoloCryptoStamped other)
		{
			name = other.name;
			key = other.key;
		}
	}

	public class CompProperties_HoloCryptoStamped : CompProperties
	{
		public CompProperties_HoloCryptoStamped()
		{
			compClass = typeof(CompHoloCryptoStamped);
		}
	}
}


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

// Adds unlock options to right-click menu for holokeys.
namespace rjw
{
	public class CompStampedApparelKey : CompUsable
	{
		protected string make_label(Pawn pawn, Pawn other)
		{
			return FloatMenuOptionLabel + " on " + ((other == null) ? "self" : xxx.get_pawnname(other));
		}

		public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn pawn)
		{
			if (!pawn.CanReserve(parent))
				yield return new FloatMenuOption(FloatMenuOptionLabel + " (" + "Reserved".Translate() + ")", null, MenuOptionPriority.DisabledOption);
			else if (pawn.CanReach(parent, PathEndMode.Touch, Danger.Some))
			{
				// Option for the pawn to use the key on themself
				if (!pawn.is_wearing_locked_apparel())
					yield return new FloatMenuOption("Not wearing locked apparel", null, MenuOptionPriority.DisabledOption);
				else
					yield return this.make_option(make_label(pawn, null), pawn, null, null);

				if ((pawn.Map != null) && (pawn.Map == Find.CurrentMap))
				{
					// Options for use on colonists
					foreach (var other in pawn.Map.mapPawns.FreeColonists)
						if ((other != pawn) && other.is_wearing_locked_apparel())
							yield return this.make_option(make_label(pawn, other), pawn, other, null);

					// Options for use on prisoners
					foreach (var prisoner in pawn.Map.mapPawns.PrisonersOfColony)
						if (prisoner.is_wearing_locked_apparel())
							yield return this.make_option(make_label(pawn, prisoner), pawn, prisoner, WorkTypeDefOf.Warden);

					// Options for use on corpses
					foreach (var q in pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.Corpse))
					{
						var corpse = q as Corpse;
						if (corpse.InnerPawn.is_wearing_locked_apparel())
							yield return this.make_option(make_label(pawn, corpse.InnerPawn), pawn, corpse, null);
					}
				}
			}
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class CompUnlockBondageGear : CompUseEffect
	{
		public override float OrderPriority
		{
			get
			{
				return -69;
			}
		}

		public override void DoEffect(Pawn p)
		{
			base.DoEffect(p);
			var key_stamp = parent.GetComp<CompHoloCryptoStamped>();

			if ((key_stamp == null) || (p.MapHeld == null) || (p.apparel == null))
				return;

			Apparel locked_app = null;
			var any_locked = false;
			{
				foreach (var app in p.apparel.WornApparel)
				{
					var app_stamp = app.GetComp<CompHoloCryptoStamped>();
					if (app_stamp != null)
					{
						any_locked = true;
						if (app_stamp.matches(key_stamp))
						{
							locked_app = app;
							break;
						}
					}
				}
			}

			if (locked_app != null)
			{
				//locked_app.Notify_Stripped (p); // TODO This was removed. Necessary?

				p.apparel.Remove(locked_app);
				Thing dropped = null;
				GenThing.TryDropAndSetForbidden(locked_app, p.Position, p.MapHeld, ThingPlaceMode.Near, out dropped, false); //this will create a new key somehow.
				if (dropped != null)
				{
					Messages.Message("Unlocked " + locked_app.def.label, p, MessageTypeDefOf.SilentInput);
					IntVec3 keyPostition = parent.Position;
					parent.Destroy();
				}
				else if (PawnUtility.ShouldSendNotificationAbout(p))
				{
					Messages.Message("Couldn't drop " + locked_app.def.label, p, MessageTypeDefOf.NegativeEvent);
				}
			}
			else if (any_locked)
				Messages.Message("The key doesn't fit!", p, MessageTypeDefOf.NegativeEvent);
		}
	}
}


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

namespace rjw
{
	public class JobDriver_StruggleInBondageGear : JobDriver
	{
		public Apparel target_gear
		{
			get
			{
				return (Apparel)TargetA.Thing;
			}
		}

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

		protected override IEnumerable<Toil> MakeNewToils()
		{
			yield return new Toil
			{
				initAction = delegate
				{
					pawn.pather.StopDead();
				},
				defaultCompleteMode = ToilCompleteMode.Delay,
				defaultDuration = 60
			};
			yield return new Toil
			{
				initAction = delegate
				{
					if (PawnUtility.ShouldSendNotificationAbout(pawn))
					{
						var pro = (pawn.gender == Gender.Male) ? "his" : "her";
						Messages.Message(xxx.get_pawnname(pawn) + " struggles to remove " + pro + " " + target_gear.def.label + ". It's no use!", pawn, MessageTypeDefOf.NegativeEvent);
					}
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_UseItemOn : JobDriver_UseItem
	{
		public static Toil pickup_item(Pawn p, Thing item)
		{
			return new Toil
			{
				initAction = delegate
				{
					p.carryTracker.TryStartCarry(item, 1);
					if (item.Spawned) // If the item is still spawned that means the pawn failed to pick it up
						p.jobs.curDriver.EndJobWith(JobCondition.Incompletable);
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}

		protected TargetIndex iitem = TargetIndex.A;

		protected TargetIndex itar = TargetIndex.B;

		protected Thing item
		{
			get
			{
				return base.job.GetTarget(iitem).Thing;
			}
		}

		protected Thing tar
		{
			get
			{
				return base.job.GetTarget(itar).Thing;
			}
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			if (tar == null)
				foreach (var toil in base.MakeNewToils())
					yield return toil;
			else
			{
				// Find the pawn to use the item on.
				Pawn other;
				{
					var corpse = tar as Corpse;
					other = (corpse == null) ? (Pawn)tar : corpse.InnerPawn;
				}

				this.FailOnDespawnedNullOrForbidden(itar);
				if (!other.Dead)
					this.FailOnAggroMentalState(itar);
				yield return Toils_Reserve.Reserve(itar);

				if ((pawn.inventory != null) && pawn.inventory.Contains(item))
				{
					yield return Toils_Misc.TakeItemFromInventoryToCarrier(pawn, iitem);
				}
				else
				{
					yield return Toils_Reserve.Reserve(iitem);
					yield return Toils_Goto.GotoThing(iitem, PathEndMode.ClosestTouch).FailOnForbidden(iitem);
					yield return pickup_item(pawn, item);
				}

				yield return Toils_Goto.GotoThing(itar, PathEndMode.Touch);

				yield return new Toil
				{
					initAction = delegate
					{
						if (!other.Dead)
							PawnUtility.ForceWait(other, 60);
					},
					defaultCompleteMode = ToilCompleteMode.Delay,
					defaultDuration = 60
				};

				yield return new Toil
				{
					initAction = delegate
					{
						var effective_item = item;

						// Drop the item if it's some kind of apparel. This is because ApparelTracker.Wear only works properly
						// if the apparel to wear is spawned. (I'm just assuming that DoEffect for apparel wears it, which is
						// true for bondage gear)
						if ((effective_item as Apparel) != null)
						{
							Thing dropped_thing;
							if (pawn.carryTracker.TryDropCarriedThing(pawn.Position, ThingPlaceMode.Near, out dropped_thing))
								effective_item = dropped_thing as Apparel;
							else
							{
								Log.Error("Unable to drop " + effective_item.Label + " for use on " + xxx.get_pawnname(other) + " (apparel must be dropped before use)");
								effective_item = null;
							}
						}

						if (effective_item != null)
						{
							var eff = effective_item.TryGetComp<CompUseEffect>();
							if (eff != null)
								eff.DoEffect(other);
							else
								Log.Error("Unable to get CompUseEffect for use of " + effective_item.Label + " on " + xxx.get_pawnname(other) + " by " + xxx.get_pawnname(pawn));
						}
					},
					defaultCompleteMode = ToilCompleteMode.Instant
				};
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallChastityBelt : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			return base.GetPartsToApplyOn(p, r);
		}
	}

	public class Recipe_UnlockChastityBelt : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			return base.GetPartsToApplyOn(p, r);
		}
	}
}


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

namespace rjw
{
	public class Recipe_ForceOffGear : Recipe_Surgery
	{
		public static bool is_wearing(Pawn p, ThingDef apparel_def)
		{
			if (p.apparel != null)
				foreach (var app in p.apparel.WornApparel)
					if (app.def == apparel_def)
						return true;
			return false;
		}

		public static BodyPartRecord find_part_record(BodyPartDef part_def, Pawn p)
		{
			return p.RaceProps.body.AllParts.Find((BodyPartRecord bpr) => bpr.def == part_def);
		}

		// Puts the recipe in the operations list only if "p" is wearing the relevant apparel. The little trick here is that yielding
		// null causes the game to put the recipe in the list but not actually apply it to a body part.
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef generic_def)
		{
			var r = (force_off_gear_def)generic_def;
			if (is_wearing(p, r.removes_apparel))
				yield return null;
		}

		[SyncMethod]
		public static void apply_burns(Pawn p, List<BodyPartDef> parts, float min_severity, float max_severity)
		{
			foreach (var part in parts)
			{
				var rec = find_part_record(part, p);
				if (rec != null)
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					var to_deal = Rand.Range(min_severity, max_severity) * part.GetMaxHealth(p);
					var dealt = 0.0f;
					var counter = 0;
					while ((counter < 100) && (dealt < to_deal) && (!p.health.hediffSet.PartIsMissing(rec)))
					{
						var dam = Rand.RangeInclusive(3, 5);
						p.TakeDamage(new DamageInfo(DamageDefOf.Burn, dam, 999, -1.0f, null, rec, null));
						++counter;
						dealt += (float)dam;
					}
				}
			}
		}

		[SyncMethod]
		public override void ApplyOnPawn(Pawn p, BodyPartRecord null_part, Pawn surgeon, List<Thing> ingredients,Bill bill)
		{
			var r = (force_off_gear_def)recipe;
			if ((surgeon != null) &&
				(p.apparel != null) &&
				(!CheckSurgeryFail(surgeon, p, ingredients, find_part_record(r.failure_affects, p),bill)))
			{
				// Remove apparel
				foreach (var app in p.apparel.WornApparel)
					if (app.def == r.removes_apparel)
					{
						p.apparel.Remove(app);
						break;
					}

				// Destroy parts
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				var def_to_destroy = r.destroys_one_of.RandomElement<BodyPartDef>();
				if (def_to_destroy != null)
				{
					var record_to_destroy = find_part_record(def_to_destroy, p);
					if (record_to_destroy != null)
					{
						var dam = (int)(1.5f * def_to_destroy.GetMaxHealth(p));
						p.TakeDamage(new DamageInfo(DamageDefOf.Burn, dam, 999, -1.0f, null, record_to_destroy, null));
					}
				}

				if (r.major_burns_on != null)
					apply_burns(p, r.major_burns_on, 0.30f, 0.60f);
				if (r.minor_burns_on != null)
					apply_burns(p, r.minor_burns_on, 0.15f, 0.35f);
			}
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_Bound : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			if (p.apparel != null)
			{
				bool bound = false, gagged = false;
				foreach (var app in p.apparel.WornApparel)
				{
					var gear_def = app.def as bondage_gear_def;
					if (gear_def != null)
					{
						bound |= gear_def.gives_bound_moodlet;
						gagged |= gear_def.gives_gagged_moodlet;
					}
				}

				if (bound && gagged)
					return ThoughtState.ActiveAtStage(2);
				else if (gagged)
					return ThoughtState.ActiveAtStage(1);
				else if (bound)
					return ThoughtState.ActiveAtStage(0);
			}
			return ThoughtState.Inactive;
		}
	}
}