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

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

		public Pawn Actor
		{
			get
			{
				return GetActor();
			}
		}

		public Building_WhoreBed Bed
		{
			get
			{
				return (Building_WhoreBed)(job.GetTarget(BedInd));
			}
		}

		public Pawn Partner
		{
			get
			{
				return (Pawn)(job.GetTarget(PartnerInd));
			}
		}

		public IntVec3 WhoreSleepSpot
		{
			get
			{
				return (IntVec3)job.GetTarget(SlotInd);
			}
		}

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

		private static IntVec3 GetSleepingSpot(Building_WhoreBed bed)
		{
			for (int i = 0; i < bed.SleepingSlotsCount; i++)
			{
				if (bed.GetCurOccupant(i) == null)
				{
					return bed.GetSleepingSlotPos(i);
				}
			}
			return bed.GetSleepingSlotPos(0);
		}

		private static IntVec3 GetSleepingSpot(Building_WhoreBed bed, IntVec3 exceptPosition)
		{
			for (int i = 0; i < bed.SleepingSlotsCount; i++)
			{
				if (bed.GetCurOccupant(i) == null && bed.GetSleepingSlotPos(i) != exceptPosition)
				{
					return bed.GetSleepingSlotPos(i);
				}
			}
			return exceptPosition;
		}

		private static bool IsInOrByBed(Building_WhoreBed 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()
		{
			return this.pawn.Reserve(this.Partner, this.job, 1, 0, null);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			this.FailOnDespawnedOrNull(PartnerInd);
			this.FailOnDespawnedNullOrForbidden(BedInd);
			this.FailOn(() => Actor is null || !xxx.CanUse(Actor, Bed) || !Actor.CanReserve(Partner));
			int price = Bed.priceOfWhore;
			yield return Toils_Reserve.Reserve(PartnerInd, 1, 0);
			//yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);
			bool partnerHasPenis = Genital_Helper.has_penis(Partner);

			Toil gotoWhoreBed = new Toil
			{
				initAction = delegate
				 {
					 //--Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - gotoWhoreBed initAction is called");
					 Actor.pather.StartPath(WhoreSleepSpot, PathEndMode.OnCell);
					 //Actor.Reserve(Partner, 1, 0);
					 Partner.pather.StartPath(Actor, PathEndMode.Touch);
				 },
				tickAction = delegate
				{
					if (Partner.IsHashIntervalTick(150))
					{
						Partner.pather.StartPath(Actor, PathEndMode.Touch);
						//--Log.Message(Partner.NameStringShort + ": I'm following the whore");
					}
				},
				defaultCompleteMode = ToilCompleteMode.PatherArrival
			};
			gotoWhoreBed.FailOnWhorebedNoLongerUsable(BedInd, Bed);
			yield return gotoWhoreBed;

			Toil waitInBed = new Toil
			{
				initAction = delegate
				{
					//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - waitInBed, initAction is called");
					ticksLeftThisToil = 5000;
					ticks_left = (int)(2000.0f * Rand.Range(0.30f, 1.30f));
					//Actor.pather.StopDead();  //Let's just make whores standing at the bed
					//JobDriver curDriver = Actor.jobs.curDriver;
					//curDriver.layingDown = LayingDownState.LayingInBed;
					//curDriver.asleep = false;
					var gettin_loved = new Job(xxx.gettin_loved, Actor, Bed);
					Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced, null, false, true, null);
				},
				tickAction = delegate
				{
					Actor.GainComfortFromCellIfPossible();
					if (IsInOrByBed(Bed, Partner))
					{
						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - waitInBed, tickAction pass");
						ticksLeftThisToil = 0;
					}
				},
				defaultCompleteMode = ToilCompleteMode.Delay,
			};
			waitInBed.FailOn(() => pawn.GetRoom(RegionType.Set_Passable) == null);
			yield return waitInBed;

			bool canAfford = xxx.CanAfford(Partner, Actor, price);
			if (canAfford)
			{
				Toil loveToil = new Toil
				{
					initAction = delegate
					{
						//Actor.jobs.curDriver.ticksLeftThisToil = 1200;
						//Using ticks_left to control the time of sex
						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - loveToil, setting initAction");
						if (xxx.HasNonPolyPartner(Actor))
						{
							Pawn pawn = LovePartnerRelationUtility.ExistingLovePartner(Actor);
							if (((Partner != pawn) && !pawn.Dead) && ((pawn.Map == Actor.Map) || (Rand.Value < 0.15)))
							{
								pawn.needs.mood.thoughts.memories.TryGainMemory(ThoughtDefOf.CheatedOnMe, Actor);
							}
						}
						if (xxx.HasNonPolyPartner(Partner))
						{
							Pawn pawn = LovePartnerRelationUtility.ExistingLovePartner(Partner);
							if (((Actor != pawn) && !pawn.Dead) && ((pawn.Map == Partner.Map) || (Rand.Value < 0.25)))
							{
								pawn.needs.mood.thoughts.memories.TryGainMemory(ThoughtDefOf.CheatedOnMe, Partner);
							}
						}
						if (!partnerHasPenis)
							Actor.rotationTracker.Face(Partner.DrawPos);
					},
					defaultCompleteMode = ToilCompleteMode.Never, //Changed from Delay
				};
				loveToil.AddPreTickAction(delegate
				{
					//Actor.Reserve(Partner, 1, 0);
					--ticks_left;
					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.AddFinishAction(delegate
				{
					//--Log.Message("[RJW] JobDriver_WhoreIsServingVisitors::MakeNewToils() - finished loveToil");
					//// Trying to add some interactions and social logs
					//xxx.processAnalSex(Partner, Actor, ref isAnalSex, partnerHasPenis);
				});
				loveToil.AddFailCondition(() => Partner.Dead || !IsInOrByBed(Bed, Partner));
				loveToil.socialMode = RandomSocialMode.Off;
				yield return loveToil;

				Toil afterSex = new Toil
				{
					initAction = delegate
					{
						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - calling aftersex in afterSex.initAction");
						xxx.aftersex(Partner, Actor, false, false, isAnalSex);
						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - setting mindstate in second initAction");
						Actor.mindState.canLovinTick = Find.TickManager.TicksGame + xxx.generate_min_ticks_to_next_lovin(Actor);
						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - setting mindstate again in second initAction");
						Partner.mindState.canLovinTick = Find.TickManager.TicksGame + xxx.generate_min_ticks_to_next_lovin(Partner);

						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Partner should pay the price now in afterSex.initAction");
						int remainPrice = xxx.PayPriceToWhore(Partner, price, Actor);
						if (remainPrice <= 0)
						{
							//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Paying price is success");
						}
						else
						{
							//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Paying price failed");
						}
						xxx.UpdateRecords(Actor, price-remainPrice);
					},
					defaultCompleteMode = ToilCompleteMode.Instant
				};
				yield return afterSex;
			}
		}
	}
}


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

namespace rjw
{
	public class JobGiver_Beastiality : ThinkNode_JobGiver
	{
		public static Pawn find_target(Pawn pawn, Map m)
		{
			//--Log.Message("JobGiver_Beastiality::find_target( " + pawn.NameStringShort + " ) called");
			Pawn found = null;
			var best_distance = 50f;
			var best_fuckability = 0.1f; // Don't rape animals with 0% fuckability

			foreach (Pawn target in m.mapPawns.AllPawns.Where(x => xxx.is_animal(x) && xxx.can_get_raped(x) && pawn.CanReserve(x, 1, 0) && (x.gender != pawn.gender) && (Rand.Value > 0.5f))) //Added randomizer. We don't need to check all animals on the map, do we?
			{
				if (target != null && !target.Position.IsForbidden(pawn))
				{
					float tamed = (target.Faction == pawn.Faction ? 1f : 0f);
					float wildness = target.RaceProps.wildness;
					float petness = target.RaceProps.petness;
					//float temperment = (petness <= 0 ? wildness / 0.1f : wildness / petness);
					//--Log.Message("[RJW]JobGiver_Beastiality::find_target wildness is " + wildness);
					//--Log.Message("[RJW]JobGiver_Beastiality::find_target petness is " + petness);

					float distance = pawn.Position.DistanceToSquared(target.Position);
					//--Log.Message("[RJW]JobGiver_Beastiality::find_target distance is " + distance);

					var fuc = xxx.would_fuck(pawn, target);
					fuc = fuc + (fuc * petness) - (fuc * wildness) + tamed;

					if ((xxx.config.zoophis_always_rape || fuc > best_fuckability) && distance < best_distance)
					{
						found = target;
						best_distance = distance;
						best_fuckability = fuc;
					}
				}
			}

			return found;
		}

		protected override Job TryGiveJob(Pawn p)
		{
			//--Log.Message("[RJW] JobGiver_Beastiality::TryGiveJob( " + p.NameStringShort + " ) called");
			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) && (p.CurJob == null))
			{
				if (xxx.is_healthy(p) && xxx.can_rape(p, true))
				{
					var target = find_target(p, p.Map);
					//--Log.Message("[RJW] JobGiver_Beastiality::TryGiveJob - target is " + (target == null ? "NULL" : target.NameStringShort));
					if (target != null)
					{

						return new Job(xxx.beastiality, target);
					}
					else
					{
						if (xxx.config.zoophis_always_rape)
							p.mindState.canLovinTick = Find.TickManager.TicksGame + 5;
						else
							p.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(150, 300);
					}
				}
			}

			return null;
		}
	}
}

repo_name: houndofset/RimJobWorld

license: mit


code:
using Verse;
using Verse.AI;

namespace rjw
{
	public class JobGiver_ComfortPrisonerRape : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn p)
		{
			bool wildmode = Mod_Settings.WildMode;
			//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + p.NameStringShort + " ) called0");
			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) && (p.CurJob == null))
			{
				//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + p.NameStringShort + " ) called1");
				// don't allow pawns marked as comfort prisoners to rape others
				if (p != null)
				{
					if (!comfort_prisoners.is_designated(p)
						&& ((xxx.is_healthy(p)
						&& xxx.can_rape(p, true)
						&& xxx.is_nympho_or_rapist_or_zoophiliac(p)
						&& p.Faction.IsPlayer) || wildmode))
					{
						//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + p.NameStringShort + " ) called2");
						Pawn target = xxx.find_prisoner_to_rape(p, p.Map);
						//--Log.Message("[RJW] JobGiver_ComfortPrisonerRape::TryGiveJob( " + p.NameStringShort + " ) called3 - (" + ((target == null) ? "NULL" : target.NameStringShort)+") is the prisoner");
						if (target != null)
						{
							if (((!xxx.is_animal(target) && (xxx.is_rapist(p) || xxx.is_nympho(p))) ||
							     (xxx.is_animal(target) && xxx.is_zoophiliac(p))) || wildmode) //TODO: make a designation target for animals
							{
								return new Job(xxx.comfort_prisoner_rapin, target);
							}
							else if (xxx.config.pawns_always_rapeCP)
							{
								p.mindState.canLovinTick = Find.TickManager.TicksGame + 5;
							}
							else
							{
								p.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
							}
						}
					}
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_DoFappin : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn p)
		{
			//--Log.Message("[RJW] JobGiver_DoFappin::TryGiveJob( " + p.NameStringShort + " ) called");
			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) &&
				(p.CurJob != null))
			{
				Building_Bed bed = null;

				if (p.jobs.curDriver is JobDriver_LayDown)
				{
					bed = ((JobDriver_LayDown)p.jobs.curDriver).Bed;
				}

				if (p.jobs.curDriver.layingDown != LayingDownState.NotLaying &&
					(bed != null) && //!bed.Medical &&
					xxx.is_healthy_enough(p) &&
					xxx.can_be_fucked(p))
				{
					var no_partner = xxx.is_laying_down_alone(p);
					bool is_frustrated;
					{
						is_frustrated = xxx.need_some_sex(p) == 3f;
					}

					if (xxx.config.pawns_always_do_fapping || (no_partner || is_frustrated))
					{
						//p.mindState.awokeVoluntarily = true;
						return new Job(xxx.fappin, bed);
					}
				}
			}
			return null;
		}
	}
}


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

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

		private static bool is_healthy(Pawn p)
		{
			return (!p.Dead) &&
				p.health.capacities.CanBeAwake &&
				(p.health.hediffSet.BleedRateTotal <= 0.0f) &&
				(p.health.hediffSet.PainTotal < xxx.config.significant_pain_threshold) &&
				xxx.can_fuck(p);
		}

		public static Pawn find_pawn_to_fuck(Pawn nymph, Map map)
		{
			Pawn best_fuckee = null;
			float best_distance = 1.0e6f;
			foreach (var q in map.mapPawns.FreeColonistsAndPrisoners)
				if ((q != nymph) &&
					xxx.is_laying_down_alone(q) &&
					nymph.CanReserve(q, 1, 0) &&
					q.CanReserve(nymph, 1, 0) &&
					roll_to_skip(nymph, q) &&
					(!q.Position.IsForbidden(nymph)) &&
					is_healthy(q))
				{
					var dis = nymph.Position.DistanceToSquared(q.Position);
					if (dis < best_distance)
					{
						best_fuckee = q;
						best_distance = dis;
					}
				}
			return best_fuckee;
		}

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

			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) && (p.CurJob == null))
			{
				//--Log.Message("   checking nympho and abilities");
				if (xxx.is_nympho(p) && p.health.capacities.CanBeAwake && xxx.can_fuck(p))
				{
					//--Log.Message("   finding partner");
					var partner = find_pawn_to_fuck(p, p.Map);

					Building_Bed bed = null;

					if (partner != null && partner.jobs.curDriver is JobDriver_LayDown)
					{
						bed = ((JobDriver_LayDown)partner.jobs.curDriver).Bed;
					}

					//--Log.Message("   checking partner");
					if (partner != null)
					{
						//--Log.Message("   checking partner's job");
						if (partner.CurJob != null)
						{
							//--Log.Message("   checking partner again");
							if ((partner != null))
							{
								//--Log.Message("   checking bed");
								if ((bed != null))
								{
									//--Log.Message("   returning job");
									return new Job(DefDatabase<JobDef>.GetNamed("NymphJoinInBed"), partner, bed);
								}
								else
								{
									//--Log.Message("   resetting ticks");
									p.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
								}
							}
						}
					}
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_RandomRape : ThinkNode_JobGiver
	{
		public static Pawn find_victim(Pawn rapist, Map m)
		{
			Pawn best_rapee = null;
			var best_fuckability = 0.10f; // Don't rape prisoners with <10% fuckability
			foreach (var target in m.mapPawns.AllPawns.Where(x => x != rapist && xxx.is_healthy_enough(x) && xxx.can_get_raped(x)))
			{
				if (rapist.CanReserve(target, comfort_prisoners.max_rapists_per_prisoner, 0) && !target.Position.IsForbidden(rapist))
				{
					if (xxx.config.rapists_always_rape || (!xxx.is_animal(target) || xxx.is_zoophiliac(rapist)))
					{
						var fuc = xxx.would_fuck(rapist, target, true,true);
						if ((fuc > best_fuckability) && (Rand.Value < fuc))
						{
							best_rapee = target;
							best_fuckability = fuc;
						}
					}
				}
			}
			return best_rapee;
		}

		protected override Job TryGiveJob(Pawn p)
		{
			//--Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + p.NameStringShort + " ) called");

			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) && (p.CurJob == null))
			{
				// don't allow pawns marked as comfort prisoners to rape others
				if (xxx.is_healthy(p) && xxx.can_rape(p, true))
				{
					var prisoner = find_victim(p, p.Map);

					if (prisoner != null)
					{
						//--Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + p.NameStringShort + " ) - found victim " + prisoner.NameStringShort);

						return new Job(xxx.random_rape, prisoner);
					}
					else
					{
						//TODO: Remove this later (Hoge's recommendation)
						//--Log.Message("[RJW] JobGiver_RandomRape::TryGiveJob( " + p.NameStringShort + " ) - unable to find victim");
						if (xxx.config.rapists_always_rape)
							p.mindState.canLovinTick = Find.TickManager.TicksGame + 5;
						else p.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
					}
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_RapeEnemy : ThinkNode_JobGiver
	{
		protected override Job TryGiveJob(Pawn p)
		{
			//--Log.Message("[RJW] JobGiver_RapeEnemy::TryGiveJob( " + p.NameStringShort + " ) called");
			if (!p.health.capacities.CanBeAwake || Find.TickManager.TicksGame < p.mindState.canLovinTick || p.CurJob != null) return null;

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

			if (rapeEnemyJobDef == null)
			{
				//Log.Warning("[RJW] JobGiver_RapeEnemy::ChoosedJobDef( " + p.ToString()+ " ) no defined JobDef_RapeEnemy for him.");
				return null;
			}
			//--Log.Message("[RJW] JobGiver_RapeEnemy::ChoosedJobDef( " + p.ToString() + " ) - " + rapeEnemyJobDef.ToString() + " choosed");
			var downedPlayer = rapeEnemyJobDef.FindVictim(p, p.Map);

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

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_ViolateCorpse : ThinkNode_JobGiver
	{
		public static Corpse find_corpse(Pawn rapist, Map m)
		{
			//--Log.Message("JobGiver_ViolateCorpse::find_corpse( " + rapist.NameStringShort + " ) called");
			Corpse found = null;
			var best_distance = 100f;
			int best_freshness = 10;

			foreach (Corpse corpse in m.listerThings.ThingsOfDef(ThingDef.Named("Human_Corpse")))
			{
				//--Log.Message(rapist.NameStringShort + " found a corpse with id " + corpse.Label);
				if (rapist.CanReserve(corpse, 1, 0) && !corpse.IsForbidden(rapist))
				{
					int freshness = corpse.GetRotStage().ChangeType<int>();
					var distance = rapist.Position.DistanceToSquared(corpse.Position);
					//--Log.Message("   " + corpse.InnerPawn.NameStringShort + " =  " + freshness + "/" + distance + ",  best =  " + best_freshness + "/" + best_distance);
					if (freshness > best_freshness && distance < best_distance)
					{
						found = corpse;
						best_freshness = freshness;
						best_distance = distance;
					}
				}
			}

			return found;
		}

		protected override Job TryGiveJob(Pawn p)
		{
			//--Log.Message("[RJW] JobGiver_ViolateCorpse::TryGiveJob( " + p.NameStringShort + " ) called");
			if ((Find.TickManager.TicksGame >= p.mindState.canLovinTick) && (p.CurJob == null))
			{
				if (xxx.is_healthy(p) && xxx.can_rape(p) && !comfort_prisoners.is_designated(p))
				{
					var target = find_corpse(p, p.Map);
					//--Log.Message("[RJW] JobGiver_ViolateCorpse::TryGiveJob - target is " + (target == null ? "NULL" : "Found"));
					if (target != null)
					{

						return new Job(xxx.violate_corpse, target);
					}
					else
					{
						p.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(150, 300);
					}
				}
			}

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_WhoreInvitingVisitors : ThinkNode_JobGiver
	{
		public static int percentRate = 100;

		private static bool Roll_to_skip(Pawn p, Pawn whore)
		{
			float fuckability = xxx.would_fuck(p, whore); // 0.0 to 1.
			return xxx.config.whores_always_findjob || (fuckability >= 0.1f && Rand.Value <= 0.75f);
		}

		/*
        public static Pawn Find_pawn_to_fuck(Pawn whore, Map map)
        {
            Pawn best_fuckee = null;
            float best_distance = 1.0e6f;
            foreach (Pawn q in map.mapPawns.FreeColonists)
                if ((q != whore) &&
                    xxx.need_some_sex(q)>0 &&
                    whore.CanReserve(q, 1, 0) &&
                    q.CanReserve(whore, 1, 0) &&
                    Roll_to_skip(whore, q) &&
                    (!q.Position.IsForbidden(whore)) &&
                    xxx.is_healthy(q))
                {
                    var dis = whore.Position.DistanceToSquared(q.Position);
                    if (dis < best_distance)
                    {
                        best_fuckee = q;
                        best_distance = dis;
                    }
                }
            return best_fuckee;
        }
        */

		private sealed class FindAttractivePawnHelper
		{
			internal Pawn p1;

			internal bool TraitCheckFail(Pawn p)
			{
				if (!xxx.has_traits(p))
					return true;
				if (!xxx.can_fuck(p) || !xxx.is_healthy(p))
					return true;
				bool result = false;
				if (xxx.RomanceDiversifiedIsActive)
				{
					result = p.story.traits.HasTrait(xxx.asexual) ||
						((p.story.traits.HasTrait(xxx.straight) || p1.story.traits.HasTrait(xxx.straight))
						&& (p.gender == p1.gender));
				}
				if ((p.story.traits.HasTrait(TraitDefOf.Gay) || p1.story.traits.HasTrait(TraitDefOf.Gay)) && (p.gender != p1.gender))
				{
					return true;
				}
				return result;
			}

			//Use this check when p is not in the same faction as the whore
			internal bool FactionCheckPass(Pawn p)
			{
				return ((p.Map == p1.Map) && (p.Faction != null && p.Faction != p1.Faction) && !p.IsPrisoner && !p.HostileTo(p1));
			}

			//Use this check when p is in the same faction as the whore
			internal bool RelationCheckPass(Pawn p)
			{
				if (xxx.isSingleOrPartnerNotHere(p) || (Rand.Value < 0.9f))
				{
					if (p != LovePartnerRelationUtility.ExistingLovePartner(p1))
					{
						return (p != p1) & (p.Map == p1.Map) && (p.Faction == p1.Faction) && p.IsColonist && xxx.IsHookupAppealing(p1, p);
					}
				}
				return false;
			}
		}

		public static Pawn FindAttractivePawn(Pawn p1, out int price)
		{
			price = 0;
			if (p1 == null)
			{
				//--Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn - p1 is null");
				return null;
			}
			FindAttractivePawnHelper findPawnHelper = new FindAttractivePawnHelper
			{
				p1 = p1
			};
			if (xxx.RomanceDiversifiedIsActive && findPawnHelper.p1.story.traits.HasTrait(xxx.asexual))
			{
				return null;
			}
			price = xxx.PriceOfWhore(p1);

			int priceOfWhore = price;
			IEnumerable<Pawn> guestsSpawned = p1.Map.mapPawns.AllPawnsSpawned;
			guestsSpawned = (guestsSpawned.Except(guestsSpawned.Where(findPawnHelper.TraitCheckFail)).Where(findPawnHelper.FactionCheckPass));
			if (guestsSpawned.Count() > 0)
				guestsSpawned = guestsSpawned.Where((x => !x.Position.IsForbidden(p1) && (!LovePartnerRelationUtility.HasAnyLovePartner(x) || x != LovePartnerRelationUtility.ExistingLovePartner(p1)) && Roll_to_skip(x, p1) && xxx.CanAfford(x, p1, priceOfWhore) && p1.CanReserve(x, 1, 0)));
			Pawn whoreTarget = null;
			if (guestsSpawned.Count() > 0)
			{
				guestsSpawned.TryRandomElement(out whoreTarget);
			}
			if (whoreTarget != null)
			{
				return whoreTarget;
			}
			//--Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn - found no visitors");

			if (!xxx.WillPawnTryHookup(p1))
			{
				return null;
			}
			whoreTarget = null;
			IEnumerable<Pawn> freeColonistsSpawned = findPawnHelper.p1.Map.mapPawns.FreeColonistsSpawned;
			freeColonistsSpawned = (freeColonistsSpawned.Except(freeColonistsSpawned.Where(findPawnHelper.TraitCheckFail)).Where(x => findPawnHelper.RelationCheckPass(x) && !x.Position.IsForbidden(p1) && Roll_to_skip(x, p1) && p1.CanReserve(x, 1, 0)));
			if (freeColonistsSpawned == null || !freeColonistsSpawned.Any())
			{
				return null;
			}
			freeColonistsSpawned.TryRandomElement(out whoreTarget);
			if (whoreTarget == null)
			{
				return null;
			}
			if (p1.relations.SecondaryRomanceChanceFactor(whoreTarget) < 0.05f)
			{
				return null;
			}
			return whoreTarget;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			//--Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + pawn.NameStringShort + " ) called0");
			if (pawn == null || !InteractionUtility.CanInitiateInteraction(pawn))
			{
				return null;
			}
			if (PawnUtility.WillSoonHaveBasicNeed(pawn) || !xxx.is_healthy(pawn) || !xxx.can_be_fucked(pawn)) //As long as pawns is older than minimum sex age, they can be assigned as whores.
			{
				return null;
			}

			if (Find.TickManager.TicksGame >= pawn.mindState.canLovinTick && pawn.CurJob == null)
			{
				int price;
				Pawn pawn2 = FindAttractivePawn(pawn, out price);
				//--Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + pawn.NameStringShort + " ) called1 - pawn2 is " + (pawn2 == null ? "NULL" : pawn2.NameStringShort));
				if (pawn2 == null)
				{
					return null;
				}
				Building_WhoreBed whorebed = xxx.FindWhoreBed(pawn);
				if ((whorebed == null) || !xxx.CanUse(pawn, whorebed) || (100f * Rand.Value) > percentRate)
				{
					//--Log.Message("resetting ticks");
					if (xxx.config.whores_always_findjob)
						pawn.mindState.canLovinTick = Find.TickManager.TicksGame + 5;
					else pawn.mindState.canLovinTick = Find.TickManager.TicksGame + Rand.Range(75, 150);
					return null;
				}
				//--Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + pawn.NameStringShort + " ) called2 - " + pawn2.NameStringShort + " is pawn2.");
				whorebed.priceOfWhore = price;
				return new Job(xxx.inviting_visitors, pawn2, whorebed);
			}
			return null;
		}
	}
}


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.Collections.Generic;
using RimWorld;
using Verse;

namespace rjw
{
	public class Need_Sex : Need_Seeker
	{
		private bool isHuman => xxx.is_human(pawn);

		private bool isFemale => xxx.is_female(pawn);

		private bool isNympho => xxx.is_nympho(pawn);

		private bool isInvisible => pawn.Map == null;

		private bool BootStrapTriggered = false;

		//private bool isSexualized = false;

		private int needsex_tick = needsex_tick_timer;
		private static int needsex_tick_timer = 10;
		private static float decay_per_day = 0.3f;
		private float decay_rate_modifier = Mod_Settings.sexneed_decay_rate;

		private static float sex_min_age = (float)Mod_Settings.sex_minimum_age;

		//private int startInterval = Find.TickManager.TicksGame;
		//private static int tickInterval = 10;

		//private int std_tick = 1;

		private static SimpleCurve sex_need_factor_from_age = new SimpleCurve
		{
			new CurvePoint(sex_min_age, 0f),
			new CurvePoint(18f, 1.00f),
			new CurvePoint(28f, 1.00f),
			new CurvePoint(30f, 0.90f),
			new CurvePoint(40f, 0.75f),
			new CurvePoint(50f, 0.50f),
			new CurvePoint(60f, 0.20f),
  			new CurvePoint(80f, 0f)

			/* Edited by nizhuan-jjr
			new CurvePoint(5f,  0.25f),
			new CurvePoint(16f, 1.00f),
			new CurvePoint(22f, 1.00f),
			new CurvePoint(30f, 0.90f),
			new CurvePoint(40f, 0.75f),
			new CurvePoint(60f, 0.50f),
			new CurvePoint(80f, 0.25f)
			*/
		};

		/* Edited by nizhuan-jjr : Animals' Sex Need is removed now
        private static readonly SimpleCurve animal_sex_need_factor_from_age = new SimpleCurve
        {
            new CurvePoint(0f,  0.00f),
            new CurvePoint(1f,  1.00f),
            new CurvePoint(5f,  2.00f),
            new CurvePoint(10f, 1.00f),
            new CurvePoint(20f, 1.00f),
            new CurvePoint(30f, 0.90f),
            new CurvePoint(40f, 0.50f),
            new CurvePoint(60f, 0.20f),
        };
        */

		public float thresh_frustrated()
		{
			return 0.05f;
		}

		public float thresh_horny()
		{
			return 0.25f;
		}

		public float thresh_neutral()
		{
			return 0.50f;
		}

		public float thresh_satisfied()
		{
			return 0.75f;
		}

		public float thresh_ahegao()
		{
			return 0.95f;
		}

		public Need_Sex(Pawn pawn) : base(pawn)
		{
			//if (xxx.is_mechanoid(pawn)) return; //Added by nizhuan-jjr:Misc.Robots are not allowed to have sex, so they don't need sex actually.
			threshPercents = new List<float>
			{
				thresh_frustrated(),
				thresh_horny(),
				thresh_neutral(),
				thresh_satisfied(),
				thresh_ahegao()
			};
		}

		/*
        public static float balance_factor(float lev)
        {
            const float one_on_point_three = 1.0f / 0.30f;
            if (lev >= 0.70f)
                return 1.0f + one_on_point_three * (lev - 0.70f) * one_on_point_three;
            else if (lev >= 0.30f)
                return 1.0f;
            else
                return 1.0f - 0.5f * one_on_point_three * (0.30f - lev);
        }
        */

		public static float brokenbodyfactor(Pawn pawn)
		{
			//This adds in the broken body system
			float broken_body_factor = 1f;
			if (pawn.health.hediffSet.HasHediff(xxx.feelingBroken))
			{
				switch (pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken).CurStageIndex)
				{
					case 0:
						return 0.75f;

					case 1:
						return 1.4f;

					case 2:
						return 2f;
				}
			}
			return broken_body_factor;
		}

		public override void NeedInterval() //150 ticks between each calls
		{
			if (isInvisible) return;

			sex_min_age = (float)Mod_Settings.sex_minimum_age;
			float age = pawn.ageTracker.AgeBiologicalYearsFloat;

			if (needsex_tick <= 0 && age > Mod_Settings.sex_minimum_age)
			{
				//--Log.Message("[RJW]Need_Sex::NeedInterval is called0 - pawn is "+pawn.NameStringShort);
				needsex_tick = needsex_tick_timer;

				if (!def.freezeWhileSleeping || pawn.Awake())
				{
					decay_rate_modifier = Mod_Settings.sexneed_decay_rate;

					//every 200 calls will have a real functioning call
					var fall_per_tick =
						//def.fallPerDay *
						decay_per_day *
						(isNympho ? 3.0f : 1.0f) *
						brokenbodyfactor(pawn) *
						sex_need_factor_from_age.Evaluate(age) *
						(isFemale ? .95f : 1.0f) /
						60000.0f;
					var fall_per_call =
						150 *
						fall_per_tick *
						needsex_tick_timer;
					CurLevel -= fall_per_call * decay_rate_modifier;
					// Each day has 60000 ticks, each hour has 2500 ticks, so each hour has 50/3 calls, in other words, each call takes .06 hour.
					//--Log.Message("[RJW] " + pawn.NameStringShort + "'s sex need stats:: Decay/call: " + fall_per_call * decay_rate_modifier + ", Cur.lvl: " + CurLevel + ", Dec. rate: " + decay_rate_modifier);
				}

				// I just put this here so that it gets called on every pawn on a regular basis. There's probably a
				// better way to do this sort of thing, but whatever. This works.
				//--Log.Message("[RJW]Need_Sex::NeedInterval is called1");
				std.update(pawn);

				// the bootstrap of the mapInjector will only be triggered once per visible pawn.
				if (!BootStrapTriggered)
				{
					//--Log.Message("[RJW]Need_Sex::NeedInterval::calling boostrap - pawn is " + pawn.NameStringShort);
					xxx.bootstrap(pawn.Map);
					BootStrapTriggered = true;
				}
			}
			else
			{
				needsex_tick--;
				decay_rate_modifier = Mod_Settings.sexneed_decay_rate;
			}
			//--Log.Message("[RJW]Need_Sex::NeedInterval is called2 - needsex_tick is "+needsex_tick);
		}
	}
}


code:
using System.Collections.Generic;

using Verse;

namespace rjw
{
	public class PawnCapacityWorker_Fertility : PawnCapacityWorker
	{
		public override float CalculateCapacityLevel(HediffSet diffSet, List<PawnCapacityUtility.CapacityImpactor> impactors = null)
		{
			//return 1.0f;
			//--Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called0");
			//return 1f;
			Pawn p = diffSet.pawn;
			if (p == null)
				return 0f;
			if (xxx.is_animal(p))
				return 1f;
			else if (xxx.is_human(p))
			{
				if (p.ageTracker.AgeBiologicalYears > 50 || p.ageTracker.AgeBiologicalYears < 14)
				{
					return 0f;
				}
				float result = PawnCapacityUtility.CalculateTagEfficiency(diffSet, "RJW_FertilitySource", 1f, impactors); //This should be a value ranged in [0,1]. It seems always to be 1.
																														  //--Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called1 - result is "+ result);
				result *= GenMath.FlatHill(14f, 20f, 30f, 50f, p.ageTracker.AgeBiologicalYearsFloat); //This adds an aging factor to Fertility
																									  //--Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called2 - result is " + result);
				return result;
			}
			else return 0f;
		}

		public override bool CanHaveCapacity(BodyDef body)
		{
			//return true;
			return body.HasPartWithTag("RJW_FertilitySource");
		}
	}
}


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

namespace rjw
{
	public struct nymph_story
	{
		public Backstory child;
		public Backstory adult;
		public List<Trait> traits;
	}

	public struct nymph_passion_chances
	{
		public float major;
		public float minor;

		public nymph_passion_chances(float maj, float min)
		{
			major = maj;
			minor = min;
		}
	}

	public static class nymph_backstories
	{
		public struct child
		{
			public static Backstory vatgrown_sex_slave;
		};

		public struct adult
		{
			public static Backstory feisty;
			public static Backstory curious;
			public static Backstory tender;
			public static Backstory chatty;
			public static Backstory broken;
		};

		public static void init()
		{
			{
				Backstory bs = new Backstory();
				bs.identifier = ""; // identifier will be set by ResolveReferences
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_vatgrown_sex_slave");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Vat-Grown Sex Slave");
					bs.SetTitleShort("SexSlave");
					bs.baseDesc = "NAME is made as a sex machine, rather than a human.During HIS growing period in the factory, HE was taught plenty of social skills and sex skills, just to satisfy various sex demands.";
				}
				// bs.skillGains = new Dictionary<string, int> ();
				bs.skillGains.Add("Social", 8);
				// bs.skillGainsResolved = new Dictionary<SkillDef, int> (); // populated by ResolveReferences
				bs.workDisables = WorkTags.Intellectual;
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Childhood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				child.vatgrown_sex_slave = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed0");
			}
			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_feisty");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Feisty Nymph");
					bs.SetTitleShort("Nymph");
					bs.baseDesc = "NAME struts around the colony like a conqueror examining a newly annexed territory. You try to focus on your work but HE won't let you.\n" + "\"I heard what you said about me!\" HE accuses. You didn't say anything about HIM. HECAP knows that. " + "\"Wanna fight?\" HE challenges. You already know what HE means: a few minutes of play wrestling and then you take HIM, or, if you hesitate, HE takes you.\n" + "Though NAME can be hard to get along with you've come to appreciate how HE's more practical than the others.\n" + "You explain to HIM, \"See these things here? They need to be over there\" and \"Those people are trying to kill us, so let's kill them first.\" HECAP listens, and does what is necessary.";
				}
				bs.skillGains.Add("Social", -3);
				bs.skillGains.Add("Shooting", 4);
				bs.skillGains.Add("Melee", 6);
				bs.workDisables = (WorkTags.Cleaning | WorkTags.Animals | WorkTags.Caring | WorkTags.Artistic | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.feisty = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed1");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_curious");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Curious Nymph");
					bs.SetTitleShort("Nymph");
					bs.baseDesc = "Hold the covering plate back and place the actuator in slot C. Line the rod up. No, that won't do, it needs to be perfect. Now pop the JC-444 chip in place. \n" + "Solder the orange wire to pin #16 and the striped wire to pin #5. \n" + "Suddenly, there HE is. You aren't surprised to see NAME, you just didn't hear HIM approach. By now you're used to it. \n" + "You continue your work in silence as HE hovers over you, her large eyes fixated on the rapid, rythmic, deliberate motions of your hands.";
				}
				bs.skillGains.Add("Construction", 2);
				bs.skillGains.Add("Crafting", 6);
				bs.workDisables = (WorkTags.Animals | WorkTags.Artistic | WorkTags.Caring | WorkTags.Cooking | WorkTags.Mining | WorkTags.PlantWork | WorkTags.Violent | WorkTags.ManualDumb);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.curious = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed2");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_tender");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Tender Nymph");
					bs.SetTitleShort("Nymph");
					bs.baseDesc = "NAME has a pair of charming eyes, and HIS voice is soft and sweet. Due to HIS previous nurse job, HE tends people quite well.\n" + "HE's crouched in the corner, hands over HIS ears and tears in HIS eyes." + "You don't understand, then you notice it: the distant sound of the pained squeals of pigs being slaughtered.";
				}
				bs.skillGains.Add("Medicine", 4);
				bs.workDisables = (WorkTags.Animals | WorkTags.Artistic | WorkTags.Hauling | WorkTags.Violent | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.tender = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed3");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_chatty");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Chatty Nymph");
					bs.SetTitleShort("Nymph");
					bs.baseDesc = "No one can be more loquacious than NAME. HECAP has a flexible tongue, a sharp mind, and hot lips.";
				}
				bs.skillGains.Add("Social", 6);
				bs.workDisables = (WorkTags.Animals | WorkTags.Caring | WorkTags.Artistic | WorkTags.Violent | WorkTags.ManualDumb | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.chatty = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed4");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_broken");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label);
					bs.SetTitleShort(MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Broken Nymph");
					bs.SetTitleShort("Nymph");
					bs.baseDesc = "Maybe NAME suffered some terrible things, HE looks rather emaciated, and no one wants to speak with HIM.\n" + "HECAP only behaves a bit vivaciously while staring at some rod-like stuffs.";
				}
				bs.skillGains.Add("Social", -5);
				bs.skillGains.Add("Artistic", 8);
				bs.workDisables = (WorkTags.Cleaning | WorkTags.Animals | WorkTags.Caring | WorkTags.Violent | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I think)
				bs.bodyTypeGlobal = BodyType.Thin;
				bs.bodyTypeFemale = BodyType.Female;
				bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.broken = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed5");
			}
		}

		public static nymph_passion_chances get_passion_chances(Backstory child_bs, Backstory adult_bs, SkillDef skill_def)
		{
			var maj = 0.0f;
			var min = 0.0f;

			if (adult_bs == adult.feisty)
			{
				if (skill_def == SkillDefOf.Melee) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Shooting) { maj = 0.25f; min = 0.75f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.10f; min = 0.67f; }
			}
			else if (adult_bs == adult.curious)
			{
				if (skill_def == SkillDefOf.Construction) { maj = 0.15f; min = 0.40f; }
				else if (skill_def == SkillDefOf.Crafting) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.20f; min = 1.00f; }
			}
			else if (adult_bs == adult.tender)
			{
				if (skill_def == SkillDefOf.Medicine) { maj = 0.20f; min = 0.60f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.50f; min = 1.00f; }
			}
			else if (adult_bs == adult.chatty)
			{
				if (skill_def == SkillDefOf.Social) { maj = 1.00f; min = 1.00f; }
			}
			else if (adult_bs == adult.broken)
			{
				if (skill_def == SkillDefOf.Artistic) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.00f; min = 0.33f; }
			}

			return new nymph_passion_chances(maj, min);
		}

		// Randomly chooses backstories and traits for a nymph
		public static nymph_story generate()
		{
			var tr = new nymph_story();

			tr.child = child.vatgrown_sex_slave;

			tr.traits = new List<Trait>();
			tr.traits.Add(new Trait(xxx.nymphomaniac, 0, true));

			var beauty = 0;
			var rv = Rand.Value;
			var rv2 = Rand.Value;
			if (rv < 0.300)
			{
				tr.adult = adult.feisty;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Brawler));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.Bloodlust));
			}
			else if (rv < 0.475)
			{
				tr.adult = adult.curious;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDef.Named("Prosthophile")));
			}
			else if (rv < 0.650)
			{
				tr.adult = adult.tender;
				beauty = Rand.RangeInclusive(1, 2);
				if (rv2 < 0.50)
					tr.traits.Add(new Trait(TraitDefOf.Kind));
			}
			else if (rv < 0.825)
			{
				tr.adult = adult.chatty;
				beauty = 2;
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Greedy));
			}
			else
			{
				tr.adult = adult.broken;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 1));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 2));
			}

			if (beauty > 0)
				tr.traits.Add(new Trait(TraitDefOf.Beauty, beauty, false));

			return tr;
		}
	}
}


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

namespace rjw
{
	public static class nymph_generator
	{
		private static bool is_trait_conflicting_or_duplicate(Pawn p, Trait t)
		{
			foreach (var existing in p.story.traits.allTraits)
				if ((existing.def == t.def) || (t.def.ConflictsWith(existing)))
					return true;
			return false;
		}

		// Replaces a pawn's backstory and traits to turn it into a nymph
		public static void set_story(Pawn p)
		{
			var gen_sto = nymph_backstories.generate();

			p.story.childhood = gen_sto.child;
			p.story.adulthood = gen_sto.adult;

			//The mod More Trait Slots will adjust the max number of traits pawn can get, and therefore,
			//I need to collect pawns' traits and assign other_traits back to the pawn after adding the nymph_story traits.
			Stack<Trait> other_traits = new Stack<Trait>();
			int numberOfTotalTraits = 0;
			if (!p.story.traits.allTraits.NullOrEmpty())
			{
				foreach (Trait t in p.story.traits.allTraits)
				{
					other_traits.Push(t);
					++numberOfTotalTraits;
				}
			}

			p.story.traits.allTraits.Clear();
			var trait_count = 0;
			foreach (var t in gen_sto.traits)
			{
				p.story.traits.GainTrait(t);
				++trait_count;
			}
			while (trait_count < numberOfTotalTraits)
			{
				Trait t = other_traits.Pop();
				if (!is_trait_conflicting_or_duplicate(p, t))
					p.story.traits.GainTrait(t);
				++trait_count;
			}
		}

		private static int sum_previous_gains(SkillDef def, Pawn_StoryTracker sto, Pawn_AgeTracker age)
		{
			int total_gain = 0;
			int gain;

			// Gains from backstories
			if (sto.childhood.skillGainsResolved.TryGetValue(def, out gain))
				total_gain += gain;
			if (sto.adulthood.skillGainsResolved.TryGetValue(def, out gain))
				total_gain += gain;

			// Gains from traits
			foreach (var trait in sto.traits.allTraits)
				if (trait.CurrentData.skillGains.TryGetValue(def, out gain))
					total_gain += gain;

			// Gains from age
			var rgain = Rand.Value * (float)total_gain * 0.35f;
			var age_factor = Mathf.Clamp01((age.AgeBiologicalYearsFloat - 17.0f) / 10.0f); // Assume nymphs are 17~27
			total_gain += (int)(age_factor * rgain);

			return Mathf.Clamp(total_gain, 0, 20);
		}

		// Set a nymph's initial skills & passions from backstory, traits, and age
		public static void set_skills(Pawn p)
		{
			foreach (var skill_def in DefDatabase<SkillDef>.AllDefsListForReading)
			{
				var rec = p.skills.GetSkill(skill_def);
				if (!rec.TotallyDisabled)
				{
					rec.Level = sum_previous_gains(skill_def, p.story, p.ageTracker);
					rec.xpSinceLastLevel = rec.XpRequiredForLevelUp * Rand.Range(0.10f, 0.90f);

					var pas_cha = nymph_backstories.get_passion_chances(p.story.childhood, p.story.adulthood, skill_def);
					var rv = Rand.Value;
					if (rv < pas_cha.major) rec.passion = Passion.Major;
					else if (rv < pas_cha.minor) rec.passion = Passion.Minor;
					else rec.passion = Passion.None;
				}
				else
					rec.passion = Passion.None;
			}
		}

		public static Pawn spawn_new(IntVec3 around_loc, ref Map m)
		{
			PawnKindDef pkd;
			{
				pkd = PawnKindDef.Named("Nymph");
				pkd.minGenerationAge = 20;
				pkd.maxGenerationAge = 27;
			}
			var req = new PawnGenerationRequest(pkd,
												 Faction.OfPlayer,
												 PawnGenerationContext.NonPlayer,
												 m.Tile,    // tile(default is -1), since Inhabitant is true, then tile should have a value here; otherwise an error will pop
												 false, // Force generate new pawn
												 false, // Newborn
												 false, // Allow dead
												 false, // Allow downed
												 false, // Can generate pawn relations
												 false, // Must be capable of violence
												 0.0f, // Colonist relation chance factor
												 false, // Force add free warm layer if needed
												 true, // Allow gay
												 true, // Allow food
												 true, // Inhabitant
												 false, // Been in Cryosleep
												 false,
												 false,
												 c => (c.story.bodyType == BodyType.Female) || (c.story.bodyType == BodyType.Thin), // Validator
												 null,
												 null, // Fixed biological age
												 null, // Fixed chronological age
												 Gender.Female, // Fixed gender
												 null, // Fixed melanin
												 null); // Fixed last name

			IntVec3 spawn_loc = CellFinder.RandomClosewalkCellNear(around_loc, m, 6);//RandomSpawnCellForPawnNear could be an alternative
			var p = PawnGenerator.GeneratePawn(req);
			set_story(p);
			set_skills(p);
			GenSpawn.Spawn(p, spawn_loc, m);
			return p;
		}
	}
}


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

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

		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)
				{
					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, -1.0f, null, rec, null));
						++counter;
						dealt += (float)dam;
					}
				}
			}
		}

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

namespace rjw
{
	public class Recipe_InstallAnus : Recipe_InstallArtificialBodyPart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.anus_blocked(p);
			foreach (var part in base.GetPartsToApplyOn(p, r))
				if ((!gen_blo) || (part != xxx.anus))
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallBreasts_Female : Recipe_InstallArtificialBodyPart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.breasts_blocked(p);
			if (p.gender == Gender.Female)
			{
				foreach (var part in base.GetPartsToApplyOn(p, r))
					if ((!gen_blo) || (part != xxx.breasts))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallPrivates_Female : Recipe_InstallArtificialBodyPart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.genitals_blocked(p);
			if (p.gender == Gender.Female)
			{
				foreach (var part in base.GetPartsToApplyOn(p, r))
					if ((!gen_blo) || (part != xxx.genitals))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallPrivates_Male : Recipe_InstallArtificialBodyPart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.genitals_blocked(p);
			if (p.gender == Gender.Male)
			{
				foreach (var part in base.GetPartsToApplyOn(p, r))
					if ((!gen_blo) || (part != xxx.genitals))
						yield return part;
			}
			else if (xxx.is_female(p))  //This allows futas
			{
				foreach (var part in base.GetPartsToApplyOn(p, r))
					if ((!gen_blo) || (part != xxx.genitals))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_Nullify : rjw_CORE_EXPOSED.Recipe_RemoveBodyPart
	{
		// Quick and dirty method to guess whether the player is harvesting the genitals or amputating them
		// due to infection. The core code can't do this properly because it considers the private part
		// hediffs as "unclean".
		public bool is_harvest(Pawn p, BodyPartRecord part)
		{
			foreach (var hed in p.health.hediffSet.hediffs)
				if ((hed.Part == part) && hed.def.isBad && (hed.Severity >= 0.70f))
					return false;

			return true;
		}

		public override void ApplyOnPawn(Pawn p, BodyPartRecord part, Pawn doer, List<Thing> ingredients, Bill bill)
		{
			var har = is_harvest(p, part);

			base.ApplyOnPawn(p, part, doer, ingredients,bill);

			if (har)
			{
				if (!p.Dead)
					ThoughtUtility.GiveThoughtsForPawnOrganHarvested(p);
				else
					ThoughtUtility.GiveThoughtsForPawnExecuted(p, PawnExecutionKind.OrganHarvesting);
			}
		}

		public override string GetLabelWhenUsedOn(Pawn p, BodyPartRecord part)
		{
			return recipe.label.CapitalizeFirst();
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			foreach (var part in p.health.hediffSet.GetNotMissingParts())
				if (r.appliedOnFixedBodyParts.Contains(part.def) &&
					((part != xxx.genitals) || (!Genital_Helper.genitals_blocked(p))))
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveAnus : rjw_CORE_EXPOSED.Recipe_RemoveBodyPart
	{
		// Quick and dirty method to guess whether the player is harvesting the genitals or amputating them
		// due to infection. The core code can't do this properly because it considers the private part
		// hediffs as "unclean".
		public bool is_harvest(Pawn p, BodyPartRecord part)
		{
			foreach (var hed in p.health.hediffSet.hediffs)
				if ((hed.Part == part) && hed.def.isBad && (hed.Severity >= 0.70f))
					return false;

			return true;
		}

		public override void ApplyOnPawn(Pawn p, BodyPartRecord part, Pawn doer, List<Thing> ingredients,Bill bill)
		{
			var har = is_harvest(p, part);

			base.ApplyOnPawn(p, part, doer, ingredients,bill);

			if (har)
			{
				if (!p.Dead)
					ThoughtUtility.GiveThoughtsForPawnOrganHarvested(p);
				else
					ThoughtUtility.GiveThoughtsForPawnExecuted(p, PawnExecutionKind.OrganHarvesting);
			}
		}

		public override string GetLabelWhenUsedOn(Pawn p, BodyPartRecord part)
		{
			return recipe.label.CapitalizeFirst();
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			foreach (var part in p.health.hediffSet.GetNotMissingParts())
				if (r.appliedOnFixedBodyParts.Contains(part.def) &&
					((part != xxx.anus) || (!Genital_Helper.anus_blocked(p))))
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveBreasts : rjw_CORE_EXPOSED.Recipe_RemoveBodyPart
	{
		// Quick and dirty method to guess whether the player is harvesting the genitals or amputating them
		// due to infection. The core code can't do this properly because it considers the private part
		// hediffs as "unclean".
		public bool is_harvest(Pawn p, BodyPartRecord part)
		{
			foreach (var hed in p.health.hediffSet.hediffs)
				if ((hed.Part == part) && hed.def.isBad && (hed.Severity >= 0.70f))
					return false;

			return true;
		}

		public override void ApplyOnPawn(Pawn p, BodyPartRecord part, Pawn doer, List<Thing> ingredients,Bill bill)
		{
			var har = is_harvest(p, part);

			base.ApplyOnPawn(p, part, doer, ingredients,bill);

			if (har)
			{
				if (!p.Dead)
					ThoughtUtility.GiveThoughtsForPawnOrganHarvested(p);
				else
					ThoughtUtility.GiveThoughtsForPawnExecuted(p, PawnExecutionKind.OrganHarvesting);
			}
		}

		public override string GetLabelWhenUsedOn(Pawn p, BodyPartRecord part)
		{
			return recipe.label.CapitalizeFirst();
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (p.gender == Gender.Female)
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) &&
						((part != xxx.breasts) || (!Genital_Helper.breasts_blocked(p))))
						yield return part;
			}
		}
	}
}


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 System.Linq;
using System.Text;
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw
{
	public class Building_WhoreBed : Building_Bed
	{
		public int priceOfWhore = 0;

		private static readonly Color whoreFieldColor = new Color(170 / 255f, 79 / 255f, 255 / 255f);

		private static readonly Color sheetColorForWhores = new Color(121 / 255f, 55 / 255f, 89 / 255f);

		private static readonly List<IntVec3> whoreField = new List<IntVec3>();

		public Pawn CurOccupant
		{
			get
			{
				var list = Map.thingGrid.ThingsListAt(Position);
				return list.OfType<Pawn>()
					.Where(pawn => pawn.jobs.curJob != null)
					.FirstOrDefault(pawn => pawn.jobs.curJob.def == JobDefOf.LayDown && pawn.jobs.curJob.targetA.Thing == this);
			}
		}

		public override Color DrawColor
		{
			get
			{
				if (def.MadeFromStuff)
				{
					return base.DrawColor;
				}
				return DrawColorTwo;
			}
		}

		private bool PlayerCanSeeOwners
		{
			get
			{
				if (Faction == Faction.OfPlayer)
				{
					return true;
				}
				for (int i = 0; i < owners.Count; i++)
				{
					if ((owners[i].Faction == Faction.OfPlayer) || (owners[i].HostFaction == Faction.OfPlayer))
					{
						return true;
					}
				}
				return false;
			}
		}

		private Vector3 GetMultiOwnersLabelScreenPosFor(int slotIndex)
		{
			IntVec3 sleepingSlotPos = GetSleepingSlotPos(slotIndex);
			Vector3 drawPos = DrawPos;
			if (Rotation.IsHorizontal)
			{
				drawPos.z = sleepingSlotPos.z + 0.6f;
			}
			else
			{
				drawPos.x = sleepingSlotPos.x + 0.5f;
				drawPos.z += -0.4f;
			}
			Vector2 vector2 = drawPos.MapToUIPosition();
			if (!Rotation.IsHorizontal && (SleepingSlotsCount == 2))
			{
				vector2 = AdjustOwnerLabelPosToAvoidOverlapping((Vector3)vector2, slotIndex);
			}
			return (Vector3)vector2;
		}

		private Vector3 AdjustOwnerLabelPosToAvoidOverlapping(Vector3 screenPos, int slotIndex)
		{
			Text.Font = GameFont.Tiny;
			float num = Text.CalcSize(owners[slotIndex].NameStringShort).x + 1f;
			Vector2 vector = this.DrawPos.MapToUIPosition();
			float num2 = Mathf.Abs((float)(screenPos.x - vector.x));
			IntVec3 sleepingSlotPos = GetSleepingSlotPos(slotIndex);
			if (num > (num2 * 2f))
			{
				float x = 0f;
				if (slotIndex == 0)
				{
					x = GetSleepingSlotPos(1).x;
				}
				else
				{
					x = GetSleepingSlotPos(0).x;
				}
				if (sleepingSlotPos.x < x)
				{
					screenPos.x -= (num - (num2 * 2f)) / 2f;
					return screenPos;
				}
				screenPos.x += (num - (num2 * 2f)) / 2f;
			}
			return screenPos;
		}

		public new IEnumerable<Pawn> AssigningCandidates
		{
			get
			{
				if (!Spawned)
				{
					return Enumerable.Empty<Pawn>();
				}
				return from p in Find.VisibleMap.mapPawns.FreeColonists
					   where xxx.can_be_fucked(p)
					   select p; //Map.mapPawns.FreeColonists;
			}
		}

		public void TryAssignPawns(Pawn apawn)
		{
			if (apawn != null && apawn.IsFreeColonist && !apawn.Dead && xxx.can_be_fucked(apawn))
			{
				apawn.ownership.ClaimBedIfNonMedical(this);
				priceOfWhore = 0;
			}
		}

		public override void Draw()
		{
			base.Draw();
			if (Medical) Medical = false;
			if (ForPrisoners) ForPrisoners = false;
		}

		public override Color DrawColorTwo { get { return sheetColorForWhores; } }

		public override void DeSpawn()
		{
			foreach (var owner in owners.ToArray())
			{
				owner.ownership.UnclaimBed();
			}
			var room = Position.GetRoom(Map);
			base.DeSpawn();
			if (room != null)
			{
				room.Notify_RoomShapeOrContainedBedsChanged();
			}
		}

		//public override void DrawExtraSelectionOverlays()
		//{
		//    base.DrawExtraSelectionOverlays();
		//    var room = this.GetRoom();
		//    if (room == null) return;
		//    if (room.isPrisonCell) return;
		//
		//    if (room.RegionCount < 20 && !room.TouchesMapEdge)
		//    {
		//        foreach (var current in room.Cells)
		//        {
		//            whoreField.Add(current);
		//        }
		//        var color = whoreFieldColor;
		//        color.a = Pulser.PulseBrightness(1f, 0.6f);
		//        GenDraw.DrawFieldEdges(whoreField, color);
		//        whoreField.Clear();
		//    }
		//}

		public override string GetInspectString()
		{
			var stringBuilder = new StringBuilder();
			//stringBuilder.Append(base.GetInspectString());
			stringBuilder.Append(InspectStringPartsFromComps());
			stringBuilder.AppendLine();
			stringBuilder.Append("ForWhoreUse".Translate());

			stringBuilder.AppendLine();
			if (owners.Count == 0)
			{
				stringBuilder.Append("Owner".Translate() + ": " + "Nobody".Translate());
			}
			else if (owners.Count == 1)
			{
				stringBuilder.Append("Owner".Translate() + ": " + owners[0].LabelCap);
			}
			else
			{
				stringBuilder.Append("Owners".Translate() + ": ");
				bool notFirst = false;
				foreach (Pawn owner in owners)
				{
					if (notFirst)
					{
						stringBuilder.Append(", ");
					}
					notFirst = true;
					stringBuilder.Append(owner.Label);
				}
				//if(notFirst) stringBuilder.AppendLine();
			}
			return stringBuilder.ToString();
		}

		/* This is included in Building_Bed_Patch.cs where we adds a PostFix to the Building_Bed.GetGizmos method
        public override IEnumerable<Gizmo> GetGizmos()
        {
            // Get original gizmos from Building class
            var method = typeof(Building).GetMethod("GetGizmos"); //nizhuan-jjr:I think the type should be changed from Building to Building_Bed, but the Building_Bed_Patch.cs is doing the job.
            var ftn = method.MethodHandle.GetFunctionPointer();
            var func = (Func<IEnumerable<Gizmo>>)Activator.CreateInstance(typeof(Func<IEnumerable<Gizmo>>), this, ftn);

            foreach (var gizmo in func())
            {
                yield return gizmo;
            }

            if (xxx.config.whore_beds_enabled && def.building.bed_humanlike)
            {
                yield return
                    new Command_Toggle
                    {
                        defaultLabel = "CommandBedSetAsWhoreLabel".Translate(),
                        defaultDesc = "CommandBedSetAsWhoreDesc".Translate(),
                        icon = ContentFinder<Texture2D>.Get("UI/Commands/AsWhore"),
                        isActive = () => true,
                        toggleAction = () => Swap(this),
                        hotKey = KeyBindingDefOf.Misc4
                    };
            }
        }
        */

		public override void PostMake()
		{
			base.PostMake();
			PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDef.Named("WhoreBeds"), KnowledgeAmount.Total);
		}

		public override void DrawGUIOverlay()
		{
			//if (Find.CameraMap.CurrentZoom == CameraZoomRange.Closest)
			//{
			//    if (owner != null && owner.InBed() && owner.CurrentBed().owner == owner)
			//    {
			//        return;
			//    }
			//    string text;
			//    if (owner != null)
			//    {
			//        text = owner.NameStringShort;
			//    }
			//    else
			//    {
			//        text = "Unowned".Translate();
			//    }
			//    GenWorldUI.DrawThingLabel(this, text, new Color(1f, 1f, 1f, 0.75f));
			//}
			//Added by nizhuan-jjr: I simply use the parent method because whore beds should have 3 versions.
			base.DrawGUIOverlay();
			/*Added by nizhuan-jjr: Below is copy from Building_Bed.DrawGUIOverlay(), since Whorebeds are designed to have 3 versions - Single bed,Doulbe bed, Royal bed.
            if (!Medical && ((Find.CameraDriver.CurrentZoom == CameraZoomRange.Closest) && PlayerCanSeeOwners))
            {
                Color defaultThingLabelColor = GenMapUI.DefaultThingLabelColor;
                if (!owners.Any())
                {
                    GenMapUI.DrawThingLabel(this, "Unowned".Translate(), defaultThingLabelColor);
                }
                else if (owners.Count == 1)
                {
                    if (!owners[0].InBed() || (owners[0].CurrentBed() != this))
                    {
                        GenMapUI.DrawThingLabel(this, owners[0].NameStringShort, defaultThingLabelColor);
                    }
                }
                else
                {
                    for (int i = 0; i < owners.Count; i++)
                    {
                        if ((!owners[i].InBed() || (owners[i].CurrentBed() != this)) || (owners[i].Position != GetSleepingSlotPos(i)))
                        {
                            GenMapUI.DrawThingLabel(GetMultiOwnersLabelScreenPosFor(i), owners[i].NameStringShort, defaultThingLabelColor);
                        }
                    }
                }
            }
            */
		}
	}
}


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
namespace rjw
{
	public class CompBondageGear : CompUsable
	{
		public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn p)
		{
			if ((p.Map != null) && (p.Map == Find.VisibleMap) && (p.Map.mapPawns.PrisonersOfColonyCount > 0))
			{
				if (!p.CanReserve(parent))
					yield return new FloatMenuOption(FloatMenuOptionLabel + " on prisoner (" + "Reserved".Translate() + ")", null, MenuOptionPriority.DisabledOption);
				else if (p.CanReach(parent, PathEndMode.Touch, Danger.Deadly))
					foreach (var prisoner in p.Map.mapPawns.PrisonersOfColony)
						yield return this.make_option(FloatMenuOptionLabel + " on " + prisoner.NameStringShort, p, prisoner, WorkTypeDefOf.Warden);
			}
		}
	}
}


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;

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

		public string random_hex_byte()
		{
			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 p, Pawn other)
		{
			return FloatMenuOptionLabel + " on " + ((other == null) ? "self" : other.NameStringShort);
		}

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

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

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

					// Options for use on corpses
					foreach (var q in p.Map.listerThings.ThingsInGroup(ThingRequestGroup.Corpse))
					{
						var corpse = q as Corpse;
						if (corpse.InnerPawn.is_wearing_locked_apparel())
							yield return this.make_option(make_label(p, corpse.InnerPawn), p, 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);
					//parent.DeSpawn();
					IntVec3 keyPostition = parent.Position;
					parent.Destroy();
					keyPostition.GetFirstThing(p.MapHeld, xxx.holokey).Destroy();  // so I need this line to despawn the new generated key.
				}
				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;

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

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

		public override void Interacted(Pawn rapist, Pawn victim, List<RulePackDef> extraSentencePacks)
		{
			if (rapist == null || victim == null) return;
			//--Log.Message("[RJW] InteractionWorker_AnalRapeAttempt::Interacted( " + rapist.NameStringShort + ", " + victim.NameStringShort + " ) called");
			AttemptAnalSex(rapist, victim);
		}
	}
}


code:
using System;

using UnityEngine;

using Verse;
using Verse.AI;

namespace rjw
{
	public class ThinkNode_ChancePerHour_Beastiality : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			// Use the fappin mtb hours as the base number b/c it already accounts for things like age
			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
			if (base_mtb < 0.0f)
				return -1.0f;

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.40f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.80f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				//if (pawn.story != null) {
				//    foreach (var trait in pawn.story.traits.allTraits) {
				//        if (trait.def == TraitDefOf.Bloodlust) personality_factor *= 0.25f;
				//        else if (trait.def == TraitDefOf.Brawler) personality_factor *= 0.50f;
				//        else if (trait.def == TraitDefOf.Psychopath) personality_factor *= 0.50f;
				//        else if (trait.def == TraitDefOf.Kind) personality_factor *= 30.00f;
				//    }
				//}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException e)
			{
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Beastiality:TryIssueJobPackage - error message" + e.Message);
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Beastiality:TryIssueJobPackage - error stacktrace" + e.StackTrace);
				return ThinkResult.NoJob; ;
			}
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_Fappin : ThinkNode_ChancePerHour
	{
		public static float get_fappin_mtb_hours(Pawn p)
		{
			if (p is null || p.Dead)
				return -1.0f;

			if (DebugSettings.alwaysDoLovin)
				return 0.1f;

			if (p.needs.food.Starving)
				return -1.0f;

			if (p.health.hediffSet.BleedRateTotal > 0.0f)
				return -1.0f;
			return (xxx.is_nympho(p) ? 0.5f : 1.0f) * rjw_CORE_EXPOSED.LovePartnerRelationUtility.LovinMtbSinglePawnFactor(p);
		}

		protected override float MtbHours(Pawn p)
		{
			bool can_get_job =
				(p.CurJob != null) &&
				p.jobs.curDriver.layingDown != LayingDownState.NotLaying;

			if (p.jobs.curDriver is JobDriver_LayDown)
			{
				bool is_horny;
				{
					is_horny = xxx.need_some_sex(p) > 1;
				}

				// TODO Bed check?

				if (can_get_job && is_horny)
				{
					float SexNeedFactor = (4 - xxx.need_some_sex(p)) / 2f;
					return get_fappin_mtb_hours(p) * SexNeedFactor;
				}
			}
			return -1.0f;
		}
	}
}


code:
using System;

using UnityEngine;

using Verse;
using Verse.AI;

namespace rjw
{
	public class ThinkNode_ChancePerHour_Necro : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			// Use the fappin mtb hours as the base number b/c it already accounts for things like age
			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
			if (base_mtb < 0.0f)
				return -1.0f;

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.15f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.60f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				//if (pawn.story != null) {
				//	foreach (var trait in pawn.story.traits.allTraits) {
				//		if (trait.def == TraitDefOf.Bloodlust) personality_factor *= 0.25f;
				//		else if (trait.def == TraitDefOf.Brawler) personality_factor *= 0.50f;
				//		else if (trait.def == TraitDefOf.Psychopath) personality_factor *= 0.50f;
				//		else if (trait.def == TraitDefOf.Kind) personality_factor *= 30.00f;
				//	}
				//}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException e)
			{
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Necro:TryIssueJobPackage - error message" + e.Message);
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Necro:TryIssueJobPackage - error stacktrace" + e.StackTrace);
				return ThinkResult.NoJob; ;
			}
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_RandomRape : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			if (pawn == null)
				return -1f;
			// Use the fappin mtb hours as the base number b/c it already accounts for things like age
			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
			if (base_mtb < 0.0f)
				return -1.0f;

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.15f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.60f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				if (pawn.story != null)
				{
					foreach (var trait in pawn.story.traits.allTraits)
					{
						if (trait.def == TraitDefOf.Bloodlust) personality_factor *= 0.25f;
						else if (trait.def == TraitDefOf.Brawler) personality_factor *= 0.50f;
						else if (trait.def == TraitDefOf.Psychopath) personality_factor *= 0.50f;
						else if (trait.def == TraitDefOf.Kind) personality_factor *= 30.00f;
					}
				}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException e)
			{
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_RandomRape:TryIssueJobPackage - error message" + e.Message);
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_RandomRape:TryIssueJobPackage - error stacktrace" + e.StackTrace);
				return ThinkResult.NoJob; ;
			}
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_RapeCP : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			if (pawn is null)
				return -1f;
			// Use the fappin mtb hours as the base number b/c it already accounts for things like age
			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
			if (base_mtb < 0.0f)
				return -1.0f;

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.15f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.60f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				if (pawn.story != null)
				{
					foreach (var trait in pawn.story.traits.allTraits)
					{
						if (trait.def == TraitDefOf.Bloodlust) personality_factor *= 0.25f;
						else if (trait.def == TraitDefOf.Brawler) personality_factor *= 0.50f;
						else if (trait.def == TraitDefOf.Psychopath) personality_factor *= 0.50f;
						else if (trait.def == TraitDefOf.Kind) personality_factor *= 30.00f;
					}
				}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			float animal_factor = xxx.is_animal(pawn) ? 3f : 1f;

			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor * animal_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException e)
			{
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_RapeCP:TryIssueJobPackage - error message" + e.Message);
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_RapeCP:TryIssueJobPackage - error stacktrace" + e.StackTrace);
				return ThinkResult.NoJob; ;
			}
		}
	}
}


code:
namespace rjw
{
	//This class is not used now.
	/*
    public class ThinkNode_ChancePerHour_Whore : ThinkNode_ChancePerHour
    {
        protected override float MtbHours(Pawn pawn)
        {
            // Use the fappin mtb hours as the base number b/c it already accounts for things like age
            var base_mtb = xxx.config.whore_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
            if (base_mtb < 0.0f)
                return -1.0f;

            float desire_factor;
            {
                var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
                if (need_sex != null)
                {
                    if (need_sex.CurLevel <= need_sex.thresh_frustrated())
                        desire_factor = 0.15f;
                    else if (need_sex.CurLevel <= need_sex.thresh_horny())
                        desire_factor = 0.60f;
                    else
                        desire_factor = 1.00f;
                }
                else
                    desire_factor = 1.00f;
            }

            float personality_factor;
            {
                personality_factor = 1.0f;
                if (pawn.story != null)
                {
                    foreach (var trait in pawn.story.traits.allTraits)
                    {
                        if (trait.def == xxx.nymphomaniac) personality_factor *= 0.25f;
                        else if (trait.def == TraitDefOf.Greedy) personality_factor *= 0.50f;
                        else if (xxx.RomanceDiversifiedIsActive&&(trait.def==xxx.philanderer || trait.def == xxx.polyamorous)) personality_factor *= 0.75f;
                        else if (xxx.RomanceDiversifiedIsActive && (trait.def == xxx.faithful)&& LovePartnerRelationUtility.HasAnyLovePartner(pawn)) personality_factor *= 30f;
                    }
                }
            }

            float fun_factor;
            {
                if ((pawn.needs.joy != null) && (xxx.is_nympho(pawn)))
                    fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
                else
                    fun_factor = 1.00f;
            }

            var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

            return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
        }
    }
    */
}


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

namespace rjw
{
	public class ThinkNode_ConditionalBeastiality : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.config.beastiality_enabled && xxx.is_zoophiliac(p);
		}
	}
}


code:
using Verse;
using Verse.AI;

//TODO: Fix this class
namespace rjw
{
	public class ThinkNode_ConditionalCanRapeCP : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn pawn)
		{
			if (!xxx.config.comfort_prisoners_enabled)
			{
				return false;
			}
			if (pawn == null || pawn.Faction == null)
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 0 : " + pawn.NameStringShort);
				return false;
			}

			if (Mod_Settings.WildMode)
			{
				return true;
			}
			// Due to the existence of whore system, no longer allow pawns from other factions to
			// rape comfort prisoners
			if (!pawn.Faction.IsPlayer)
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 1 : " + pawn.NameStringShort);
				return false;
			}
			if (pawn.Map == null || pawn.Map != Find.VisibleMap)
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 2 : " + pawn.NameStringShort);
				return false;
			}
			if (pawn.IsPrisonerOfColony) //TODO: Edit this
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 3 : " + pawn.NameStringShort);
				return false;
			}
			if (xxx.is_animal(pawn))
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 4 : " + pawn.NameStringShort);
				return false; // temporary disabled animals raping CP.
			}
			else if (xxx.is_human(pawn))
			{
				//--Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP::satisfied called 5 : " + pawn.NameStringShort);
				return xxx.isSingleOrPartnerNotHere(pawn);
			}
			else return false;
		}
	}
}


code:
namespace rjw
{
	/*Currently No Use
	public class ThinkNode_ConditionalFrustrated : ThinkNode_Conditional {
		protected override bool Satisfied (Pawn p)
		{
			return xxx.need_some_sex(p)>2;
		}
	}
    */
}


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

namespace rjw
{
	public class ThinkNode_ConditionalHorny : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.need_some_sex(p) > 1;
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ConditionalNecro : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.config.necro_enabled && xxx.is_necrophiliac(p);
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ConditionalNympho : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.is_nympho(p);
		}
	}
}


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

namespace rjw
{
	//maybe no longer needed.
	public class ThinkNode_ConditionalRapist : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			if (!xxx.config.random_rape_enabled)
				return false;
			if (Mod_Settings.WildMode) return true;
			if (!xxx.is_rapist(p))
				return false;
			if (!xxx.isSingleOrPartnerNotHere(p))
			{
				return false;
			}
			else return true;
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ConditionalTrait : ThinkNode_Conditional
	{
		private TraitDef trait;
		public override ThinkNode DeepCopy(bool resolve = true)
		{
			ThinkNode_ConditionalTrait thinkNode_ConditionalTrait = (ThinkNode_ConditionalTrait)base.DeepCopy(resolve);
			thinkNode_ConditionalTrait.trait = this.trait;
			return thinkNode_ConditionalTrait;
		}
		protected override bool Satisfied(Pawn pawn)
		{
			if (trait == null)
			if (pawn.story != null)
			{
				//--Log.Message(pawn.NameStringShort+" has trait" + this.trait.defName + ":" + pawn.story.traits.HasTrait(this.trait));
				return pawn.story.traits.HasTrait(this.trait);
			}
			return false;
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ConditionalWhore : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.config.whore_beds_enabled && p.IsFreeColonist && xxx.is_whore(p);
		}
	}
}


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


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);
				if (gagged)
					return ThoughtState.ActiveAtStage(1);
				if (bound)
					return ThoughtState.ActiveAtStage(0);
			}
			return ThoughtState.Inactive;
		}
	}
}