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

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

		protected TargetIndex iprisoner = TargetIndex.A;

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

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

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

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

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

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

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

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

		protected override IEnumerable<Toil> MakeNewToils()
		{
			duration = (int)(2000.0f * Rand.Range(0.50f, 0.90f));
			ticks_between_hearts = Rand.RangeInclusive(70, 130);
			ticks_between_hits = Rand.Range(xxx.config.min_ticks_between_hits, xxx.config.max_ticks_between_hits);
			ticks_between_thrusts = 100;
			bool pawnHasPenis = Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn);

			if (xxx.is_bloodlust(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.75);
			if (xxx.is_brawler(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.90);

			//--Log.Message("JobDriver_ComfortPrisonerRapin::MakeNewToils() - setting fail conditions");
			this.FailOnDespawnedNullOrForbidden(iprisoner);
			//this.FailOn(() => (!Target.health.capacities.CanBeAwake) || (!comfort_prisoners.is_designated(Target)));//this is wrong
			this.FailOn(() => (!Target.IsDesignatedComfort()));
			this.FailOn(() => !pawn.CanReserve(Target, xxx.max_rapists_per_prisoner, 0)); // Fail if someone else reserves the prisoner before the pawn arrives
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Goto.GotoThing(iprisoner, PathEndMode.OnCell);

			SexUtility.RapeAttemptAlert(pawn, Target);

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

repo_name: chippy/rjw

license: mit


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

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


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

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

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

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

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

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

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

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

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

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + "::MakeNewToils() called");
			duration = (int)(2000.0f * Rand.Range(0.50f, 0.90f));
			ticks_between_hearts = Rand.RangeInclusive(70, 130);
			ticks_between_hits = Rand.Range(xxx.config.min_ticks_between_hits, xxx.config.max_ticks_between_hits);
			ticks_between_thrusts = 100;

			if (xxx.is_bloodlust(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.75);
			if (xxx.is_brawler(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.90);

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

			SexUtility.RapeAttemptAlert(pawn, Target);

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

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


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

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

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

		private JobDriver_RapeEnemy _tmpInstance;

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

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

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

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

		//override can_rapemechanics
		protected bool requierCanRape = true;

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

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

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

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

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

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

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

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

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

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

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


code:
using RimWorld;
using Verse;

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

			return xxx.is_animal(rapist) && !xxx.is_insect(rapist) && (RJWSettings.bestiality_enabled || RJWSettings.animal_on_animal_enabled);
		}
	}
}


code:
using RimWorld;
using Verse;

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

			return xxx.is_human(rapist);
		}
	}
}


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

namespace rjw
{
	internal class JobDriver_RapeEnemyByInsect : JobDriver_RapeEnemy
	{
		//override mechanics

		//public JobDriver_RapeEnemyByInsect()
		//{
		//	this.requierCanRape = false;
		//}

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

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

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


code:
using RimWorld;
using Verse;

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

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

			return xxx.is_mechanoid(rapist);
		}

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

	}
}


code:
using RimWorld;
using Verse;

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

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

			return false;
		}
	}
}


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

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

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

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


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

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

		protected TargetIndex iitem = TargetIndex.A;

		protected TargetIndex itar = TargetIndex.B;

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() called");
			duration = (int)(2000.0f * Rand.Range(0.50f, 0.90f));
			ticks_between_hearts = Rand.RangeInclusive(70, 130);
			ticks_between_hits = Rand.Range(xxx.config.min_ticks_between_hits, xxx.config.max_ticks_between_hits);
			ticks_between_thrusts = 100;

			if (xxx.is_bloodlust(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.75);
			if (xxx.is_brawler(pawn))
				ticks_between_hits = (int)(ticks_between_hits * 0.90);

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

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

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

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

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

			yield return new Toil
			{
				initAction = delegate
				{
					//--Log.Message("[RJW] JobDriver_ViolateCorpse::MakeNewToils() - creating aftersex toil");
					//Addded by nizhuan-jjr: Try to apply an aftersex process for the pawn and the corpse
					if (corpse.InnerPawn != null)
					{
						SexUtility.ProcessSex(pawn, corpse.InnerPawn, true);
					}
				},
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


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

namespace rjw
{
	public class JobDriver_WhoreInvitingVisitors : JobDriver
	{
		// List of jobs that can be interrupted by whores.
		public static readonly List<JobDef> allowedJobs = new List<JobDef> { null, JobDefOf.Wait_Wander, JobDefOf.GotoWander, JobDefOf.Clean, JobDefOf.ClearSnow,
			JobDefOf.CutPlant, JobDefOf.HaulToCell, JobDefOf.Deconstruct, JobDefOf.Harvest, JobDefOf.LayDown, JobDefOf.Research, JobDefOf.SmoothFloor, JobDefOf.SmoothWall,
			JobDefOf.SocialRelax, JobDefOf.StandAndBeSociallyActive, JobDefOf.RemoveApparel, JobDefOf.Strip, JobDefOf.Tame, JobDefOf.Wait, JobDefOf.Wear, JobDefOf.FixBrokenDownBuilding,
			JobDefOf.FillFermentingBarrel, JobDefOf.DoBill, JobDefOf.Sow, JobDefOf.Shear, JobDefOf.BuildRoof, JobDefOf.DeliverFood,	JobDefOf.HaulToContainer, JobDefOf.Hunt, JobDefOf.Mine,
			JobDefOf.OperateDeepDrill, JobDefOf.OperateScanner, JobDefOf.RearmTurret, JobDefOf.Refuel, JobDefOf.RefuelAtomic, JobDefOf.RemoveFloor, JobDefOf.RemoveRoof, JobDefOf.Repair,
			JobDefOf.TakeBeerOutOfFermentingBarrel, JobDefOf.Train, JobDefOf.Uninstall, xxx.fappin};

		public bool successfulPass = true;

		private Pawn Whore => GetActor();
		private Pawn TargetPawn => TargetThingA as Pawn;
		private Building_Bed TargetBed => TargetThingB as Building_Bed;

		private readonly TargetIndex TargetPawnIndex = TargetIndex.A;
		private readonly TargetIndex TargetBedIndex = TargetIndex.B;

		private bool DoesTargetPawnAcceptAdvance()
		{
			//if (xxx.config.always_accept_whores)
			//	return true;

			//Log.Message("[RJW]JobDriver_InvitingVisitors::DoesTargetPawnAcceptAdvance() is called");

			if (PawnUtility.EnemiesAreNearby(TargetPawn))
			{
				return false;
			}
			if (!allowedJobs.Contains(TargetPawn.jobs.curJob.def))
			{
				return false;
			}

			//Log.Message("Will try " + xxx.WillPawnTryHookup(TargetPawn));
			//Log.Message("Appeal  " + xxx.IsHookupAppealing(TargetPawn, Whore));
			//Log.Message("Afford " + xxx.CanAfford(TargetPawn, Whore));
			Whore.skills.Learn(SkillDefOf.Social, 1.2f);
			return WhoringHelper.WillPawnTryHookup(TargetPawn) && WhoringHelper.IsHookupAppealing(TargetPawn, Whore) && WhoringHelper.CanAfford(TargetPawn, Whore) && xxx.need_some_sex(TargetPawn) >= 1f;
		}

		public override bool TryMakePreToilReservations(bool errorOnFailed) => true;

		protected override IEnumerable<Toil> MakeNewToils()
		{
			this.FailOnDespawnedOrNull(TargetPawnIndex);
			this.FailOnDespawnedNullOrForbidden(TargetBedIndex);
			this.FailOn(() => Whore is null || !xxx.CanUse(Whore, TargetBed));//|| !Whore.CanReserve(TargetPawn)
			this.FailOn(() => pawn.Drafted);
			yield return Toils_Goto.GotoThing(TargetPawnIndex, PathEndMode.Touch);

			Toil TryItOn = new Toil();
			TryItOn.AddFailCondition(() => !WhoringHelper.IsTargetPawnOkay(TargetPawn));
			TryItOn.defaultCompleteMode = ToilCompleteMode.Delay;
			TryItOn.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_InvitingVisitors::MakeNewToils - TryItOn - initAction is called");
				Whore.jobs.curDriver.ticksLeftThisToil = 50;
				MoteMaker.ThrowMetaIcon(Whore.Position, Whore.Map, ThingDefOf.Mote_Heart);
			};
			yield return TryItOn;

			Toil AwaitResponse = new Toil();
			AwaitResponse.AddFailCondition(() => !successfulPass);
			AwaitResponse.defaultCompleteMode = ToilCompleteMode.Instant;
			AwaitResponse.initAction = delegate
			{
				List<RulePackDef> extraSentencePacks = new List<RulePackDef>();
				successfulPass = DoesTargetPawnAcceptAdvance();
				//Log.Message("[RJW]JobDriver_InvitingVisitors::MakeNewToils - AwaitResponse - initAction is called");
				if (successfulPass)
				{
					MoteMaker.ThrowMetaIcon(TargetPawn.Position, TargetPawn.Map, ThingDefOf.Mote_Heart);
                    TargetPawn.jobs.EndCurrentJob(JobCondition.Incompletable);
					if (xxx.RomanceDiversifiedIsActive)
					{
						extraSentencePacks.Add(RulePackDef.Named("HookupSucceeded"));
					}
					if (Whore.health.HasHediffsNeedingTend())
					{
						successfulPass = false;
						const string key = "RJW_VisitorSickWhore";
						string text = key.Translate(TargetPawn.LabelIndefinite(), Whore.LabelIndefinite()).CapitalizeFirst();
						Messages.Message(text, Whore, MessageTypeDefOf.TaskCompletion);
					}
					else
					{
						const string key = "RJW_VisitorAcceptWhore";
						string text = key.Translate(TargetPawn.LabelIndefinite(), Whore.LabelIndefinite()).CapitalizeFirst();
						Messages.Message(text, TargetPawn, MessageTypeDefOf.TaskCompletion);
					}
				}
				else
				{
					MoteMaker.ThrowMetaIcon(TargetPawn.Position, TargetPawn.Map, ThingDefOf.Mote_IncapIcon);
					TargetPawn.needs.mood.thoughts.memories.TryGainMemory(
						TargetPawn.Faction == Whore.Faction
							? ThoughtDef.Named("RJWTurnedDownWhore")
							: ThoughtDef.Named("RJWFailedSolicitation"), Whore);

					if (xxx.RomanceDiversifiedIsActive)
					{
						Whore.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("RebuffedMyHookupAttempt"), TargetPawn);
						TargetPawn.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("FailedHookupAttemptOnMe"), Whore);
						extraSentencePacks.Add(RulePackDef.Named("HookupFailed"));
					}
					//Disabled rejection notifications
					//Messages.Message("RJW_VisitorRejectWhore".Translate(new object[] { xxx.get_pawnname(TargetPawn), xxx.get_pawnname(Whore) }), TargetPawn, MessageTypeDefOf.SilentInput);
				}
				if (xxx.RomanceDiversifiedIsActive)
				{
					Find.PlayLog.Add(new PlayLogEntry_Interaction(DefDatabase<InteractionDef>.GetNamed("TriedHookupWith"), pawn, TargetPawn, extraSentencePacks));
				}
			};
			yield return AwaitResponse;

			Toil BothGoToBed = new Toil();
			BothGoToBed.AddFailCondition(() => !successfulPass || !xxx.CanUse(Whore, TargetBed));
			BothGoToBed.defaultCompleteMode = ToilCompleteMode.Instant;
			BothGoToBed.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - initAction is called0");
				if (!successfulPass) return;
				if (!xxx.CanUse(Whore, TargetBed) && Whore.CanReserve(TargetPawn, 1, 0))
				{
					//Log.Message("[RJW]JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - cannot use the whore bed");
					return;
				}
				//Log.Message("[RJW]JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - initAction is called1");
				Whore.jobs.jobQueue.EnqueueFirst(new Job(xxx.whore_is_serving_visitors, TargetPawn, TargetBed, TargetBed.SleepPosOfAssignedPawn(Whore)));
				//TargetPawn.jobs.jobQueue.EnqueueFirst(new Job(DefDatabase<JobDef>.GetNamed("WhoreIsServingVisitors"), Whore, TargetBed, (TargetBed.MaxAssignedPawnsCount>1)?TargetBed.GetSleepingSlotPos(1): TargetBed.)), null);
				Whore.jobs.curDriver.EndJobWith(JobCondition.InterruptOptional);
				//TargetPawn.jobs.curDriver.EndJobWith(JobCondition.InterruptOptional);
			};
			yield return BothGoToBed;
		}
	}
}


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

namespace rjw
{
	public class JobDriver_WhoreIsServingVisitors : JobDriver
	{
		private readonly TargetIndex PartnerInd = TargetIndex.A;
		private readonly TargetIndex BedInd = TargetIndex.B;
		private readonly TargetIndex SlotInd = TargetIndex.C;
		private int ticks_left = 200;
		private const int ticks_between_hearts = 100;
		private static readonly ThoughtDef thought_free = ThoughtDef.Named("Whorish_Thoughts");
		private static readonly ThoughtDef thought_captive = ThoughtDef.Named("Whorish_Thoughts_Captive");

		public Pawn Actor => GetActor();
		public Pawn Partner => (Pawn)(job.GetTarget(PartnerInd));

		public Building_Bed Bed => (Building_Bed)(job.GetTarget(BedInd));
		public IntVec3 WhoreSleepSpot => (IntVec3)job.GetTarget(SlotInd);

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

		private static bool IsInOrByBed(Building_Bed b, Pawn p)
		{
			for (int i = 0; i < b.SleepingSlotsCount; i++)
			{
				if (b.GetSleepingSlotPos(i).InHorDistOf(p.Position, 1f))
				{
					return true;
				}
			}
			return false;
		}
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return pawn.Reserve(Partner, job, 1, 0, null, errorOnFailed);
		}

		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - making toils");
			this.FailOnDespawnedOrNull(PartnerInd);
			this.FailOnDespawnedNullOrForbidden(BedInd);
			//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() fail conditions check " + (Actor is null) + " " + !xxx.CanUse(Actor, Bed) + " " + !Actor.CanReserve(Partner));
			this.FailOn(() => Actor is null || !xxx.CanUse(Actor, Bed) || !Actor.CanReserve(Partner));
			this.FailOn(() => pawn.Drafted);
			int price = WhoringHelper.PriceOfWhore(Actor);
			yield return Toils_Reserve.Reserve(PartnerInd, 1, 0);
			//yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);
			bool partnerHasPenis = Genital_Helper.has_penis(Partner) || Genital_Helper.has_penis_infertile(Partner);

			Toil gotoWhoreBed = new Toil
			{
				initAction = delegate
				 {
					 //Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - gotoWhoreBed initAction is called");
					 Actor.pather.StartPath(WhoreSleepSpot, PathEndMode.OnCell);
					 Partner.jobs.StopAll();
					 Partner.pather.StartPath(WhoreSleepSpot, PathEndMode.Touch);
				 },
				tickAction = delegate
				{
					if (Partner.IsHashIntervalTick(150))
					{
						Partner.pather.StartPath(Actor, PathEndMode.Touch);
						//Log.Message(xxx.get_pawnname(Partner) + ": 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);
				},
				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() == null);
			yield return waitInBed;

			bool canAfford = WhoringHelper.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");
						/*
						//Hoge: Whore is just work. no feel cheatedOnMe.
						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 lover = LovePartnerRelationUtility.ExistingLovePartner(Partner);
							if ((Actor != lover && !lover.Dead) && (lover.Map == Partner.Map || Rand.Value < 0.25))
							{
								lover.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;
					xxx.reduce_rest(Partner);
					xxx.reduce_rest(Actor, 2);

					if (ticks_left <= 0)
						ReadyForNextToil();
					else if (pawn.IsHashIntervalTick(ticks_between_hearts))
					{
						MoteMaker.ThrowMetaIcon(Actor.Position, Actor.Map, ThingDefOf.Mote_Heart);
					}
					Actor.GainComfortFromCellIfPossible();
					Partner.GainComfortFromCellIfPossible();
				});
				loveToil.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
					{
						// Adding interactions, social logs, etc
						SexUtility.ProcessSex(Actor, Partner, false, false, true);

						//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Partner should pay the price now in afterSex.initAction");
						int remainPrice = WhoringHelper.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);
						var thought = (Actor.IsPrisoner) ? thought_captive : thought_free;
						pawn.needs.mood.thoughts.memories.TryGainMemory(thought);
						if (SexUtility.ConsiderCleaning(pawn))
						{
							LocalTargetInfo cum = pawn.PositionHeld.GetFirstThing<Filth>(pawn.Map);

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

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


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

namespace rjw
{
	//Rape to Prisoner of QuestPrisonerWillingToJoin
	class JobGiver_AIRapePrisoner : ThinkNode_JobGiver
	{
		private const float min_fuckability = 0.10f; // Don't rape prisoners with <10% fuckability

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

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

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

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

			return valid_targets.Any() ? valid_targets.RandomElement() : null;
		}

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

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

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

			return null;
		}

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


code:
// #define TESTMODE // Uncomment to enable logging.

using System.Diagnostics;
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Pawn tries to find animal to do loving/raping.
	/// </summary>
	public class JobGiver_Bestiality : ThinkNode_JobGiver
	{
		[Conditional("TESTMODE")]
		private static void DebugText(string msg)
		{
			Log.Message(msg);
		}

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

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

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

			if (target == null) return null;

			if (xxx.can_rape(pawn))
			{
				return new Job(xxx.bestiality, target);
			}

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

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


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

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

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

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

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

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

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

				}
				*/

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

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


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

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

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

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

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

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

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

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

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

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

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

			if (target == null) return null;

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


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

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

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

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

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

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

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

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

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

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

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

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

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

				if (score <= bestPosition) continue;

				bestPosition = score;
				cell = random_cell;
			}

			return cell;

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

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

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

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


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

namespace rjw
{
	public class JobGiver_JoinInBed : ThinkNode_JobGiver
	{
		private static bool is_healthy(Pawn target)
		{
			return xxx.is_healthy(target) && (xxx.can_fuck(target) || xxx.can_be_fucked(target));
		}

		private static bool roll_to_skip(Pawn pawn, Pawn target)
		{
			float fuckability = xxx.would_fuck(pawn, target); // 0.0 to 1.0
			if (fuckability < 0.1f) return false;

			float chance_to_skip = 0.9f - 0.7f * fuckability;
			return Rand.Value < chance_to_skip;
		}

		public static Pawn find_pawn_to_fuck(Pawn pawn, Map map)
		{
			Pawn best_fuckee = null;
			float best_distance = 1.0e6f;

			List<Pawn> targets = map.mapPawns.FreeColonists.Where(x
				=> x.InBed()
				&& x != pawn
				&& !x.Position.IsForbidden(pawn)
				&& !x.Suspended
				&& !x.Downed
				&& is_healthy(x)
				&& x.Map == pawn.Map
				&& (xxx.is_laying_down_alone(x) || xxx.in_same_bed(x, pawn))
				).ToList();

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

			if (partners.Any())
			{
				partners.Shuffle(); //Randomize order.
				foreach (Pawn target in partners)
				{
					//Log.Message("[RJW] find_pawn_to_fuck( " + xxx.get_pawnname(target) + " ) partner found");
					if (pawn.Position.DistanceToSquared(target.Position) < 100
						&& pawn.CanReserveAndReach(target, PathEndMode.OnCell, Danger.Some, 1, 0)
						&& target.CanReserve(pawn, 1, 0))
						return target;
					//Log.Message("[RJW] find_pawn_to_fuck( " + xxx.get_pawnname(target) + " ) partner cant be fucked right now");
				}
			}

			// No random lovin' for non-nymphos.
			if (!xxx.is_nympho(pawn))
				return null;

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

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

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

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

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

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

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

					//--Log.Message("   returning job");
					return new Job(DefDatabase<JobDef>.GetNamed("JoinInBed"), pawn, partner, bed);
				}
			}

			return null;
		}
	}
}


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

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

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

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

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

				if (victim != null)
					return victim;
			}

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

			return victim;
		}

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

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

			return null;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Pawn try to find enemy to rape.
	/// </summary>
	public class JobGiver_RapeEnemy : ThinkNode_JobGiver
	{
		public float targetAcquireRadius = 60f;

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

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

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

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

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

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

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

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


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

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

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

			foreach (Thing thing in corpses)
			{
				// This should stop the job from overcasting into wrong would_fuck method.
				Corpse corpse = (Corpse)thing;

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

				float fuc = xxx.would_fuck(rapist, corpse, false, false);
				//--Log.Message("   " + xxx.get_pawnname(corpse.Inner) + " =  " + fuc + ",  best =  " + best_fuckability);
				if (!(fuc > best_fuckability)) continue;

				found = corpse;
				best_fuckability = fuc;
			}
			return found;
		}

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

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

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

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

			return null;
		}
	}
}


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

namespace rjw
{
	public class JobGiver_WhoreInvitingVisitors : ThinkNode_JobGiver
	{
		private static readonly float max_distance_whore = 10000;

		// Checks if pawn has a memory.
		// Maybe not the best place for function, might be useful elsewhere too.
		public static bool MemoryChecker(Pawn pawn, ThoughtDef thought)
		{
			Thought_Memory val = pawn.needs.mood.thoughts.memories.Memories.Find((Thought_Memory x) => (object)x.def == thought);
			return val == null ? false : true;
		}

		private static bool Roll_to_skip(Pawn client, Pawn whore)
		{
			float fuckability = xxx.would_fuck(client, whore); // 0.0 to 1.

			// More likely to skip other whores, because they're supposed to be working.
			if (client.IsDesignatedService())
				fuckability *= 0.6f;
			return fuckability >= 0.1f && xxx.need_some_sex(client) >= 1f && Rand.Chance(0.5f);
		}

		/*
		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 whore;

			internal bool TraitCheckFail(Pawn client)
			{
				if (!xxx.is_human(client))
					return true;
				if (!xxx.has_traits(client))
					return true;
				if (!(xxx.can_fuck(client) || xxx.can_be_fucked(client)) || !xxx.is_healthy(client))
					return true;

				//Log.Message("client:" + client + " whore:" + whore);
				if (CompRJW.CheckPreference(client, whore) == false)
					return true;
				return false; // Everything ok.
			}

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

			//Use this check when client is in the same faction as the whore
			internal bool RelationCheckPass(Pawn client)
			{
				if (xxx.isSingleOrPartnerNotHere(client) || xxx.is_lecher(client) || Rand.Value < 0.9f)
				{
					if (client != LovePartnerRelationUtility.ExistingLovePartner(whore))
					{ //Exception for prisoners to account for PrisonerWhoreSexualEmergencyTree, which allows prisoners to try to hook up with anyone who's around (mostly other prisoners or warden)
						return (client != whore) & (client.Map == whore.Map) && (client.Faction == whore.Faction || whore.IsPrisoner) && (client.IsColonist || whore.IsPrisoner) && WhoringHelper.IsHookupAppealing(whore, client);
					}
				}
				return false;
			}
		}

		public static Pawn FindAttractivePawn(Pawn whore, out int price)
		{
			price = 0;
			if (whore == null || xxx.is_asexual(whore))
			{
				return null;
			}
			FindAttractivePawnHelper client = new FindAttractivePawnHelper
			{
				whore = whore
			};
			price = WhoringHelper.PriceOfWhore(whore);
			int priceOfWhore = price;

			IntVec3 pos = whore.Position;

			IEnumerable<Pawn> potentialClients = whore.Map.mapPawns.AllPawnsSpawned;
			potentialClients = potentialClients.Where(x
				=> x != whore
				&& !x.IsForbidden(whore)
				&& !x.Suspended
				&& !x.HostileTo(whore)
				&& !x.IsPrisoner
				&& x.Position.DistanceToSquared(pos) < max_distance_whore
				&& whore.CanReserveAndReach(x, PathEndMode.ClosestTouch, Danger.Some, 1)
				&& xxx.is_healthy_enough(x));

			potentialClients = potentialClients.Except(potentialClients.Where(client.TraitCheckFail));

			if (!potentialClients.Any()) return null;

			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn number of potential clients " + potentialClients.Count());

			IEnumerable<Pawn> guestsSpawned = potentialClients.Where(x => x.Faction != whore.Faction
				&& WhoringHelper.CanAfford(x, whore, priceOfWhore)
				&& !MemoryChecker(x, ThoughtDef.Named("RJWFailedSolicitation"))
				&& x != LovePartnerRelationUtility.ExistingLovePartner(whore));

			if (guestsSpawned.Any())
			{
				//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn number of acceptable guests " + guestsSpawned.Count());

				return guestsSpawned.RandomElement();
			}

			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn - found no guests, trying colonists");

			if (!WhoringHelper.WillPawnTryHookup(whore))
			{
				return null;
			}
			IEnumerable<Pawn> freeColonists = potentialClients.Where(x => x.Faction == whore.Faction
				&& Roll_to_skip(x, whore));

			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn number of free colonists " + freeColonists.Count());

			freeColonists = freeColonists.Where(x => client.RelationCheckPass(x) && !MemoryChecker(x, ThoughtDef.Named("RJWTurnedDownWhore")));

			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::FindAttractivePawn number of acceptable colonists " + freeColonists.Count());

			return freeColonists.Any() ? freeColonists.RandomElement() : null;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{
			// Most things are now checked in the ThinkNode_ConditionalWhore.

			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called");
			if (!SexUtility.ReadyForLovin(pawn))
			{
				//Whores need rest too, but this'll redude the wait a bit every it triggers.
				pawn.mindState.canLovinTick -= 40;
				return null;
			}

			int price;
			Pawn client = FindAttractivePawn(pawn, out price);
			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) called1 - pawn2 is " + (pawn2 == null ? "NULL" : xxx.get_pawnname(pawn2)));
			if (client == null)
			{
				return null;
			}
			Building_Bed whorebed = xxx.FindBed(pawn);
			if (whorebed == null || !xxx.CanUse(pawn, whorebed) || !client.CanReach(whorebed, PathEndMode.OnCell, Danger.Some))
			{
				return null;
			}
			//Log.Message("[RJW] JobGiver_WhoreInvitingVisitors::TryGiveJob( " + xxx.get_pawnname(pawn) + " ) whore - " + xxx.get_pawnname(client) + " is client.");
			//whorebed.priceOfWhore = price;
			return new Job(xxx.whore_inviting_visitors, client, whorebed);
		}
	}
}


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

namespace rjw
{
	public class MentalState_RandomRape : SexualMentalState
	{

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

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

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


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

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

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

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


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

namespace rjw
{
	public class Need_Sex : Need_Seeker
	{
		private bool isInvisible => pawn.Map == null;

		private bool BootStrapTriggered = false;

		protected static readonly HashSet<string> Sex_Need_filter = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("Sex_Need_filter").strings);

		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 = RJWSettings.sexneed_decay_rate;

		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 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 static float druggedfactor(Pawn pawn)
		{
			//This adds in the drugged/shroom system
			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomAddiction")))
			{
				if (!xxx.is_nympho(pawn))
				{
					pawn.story.traits.GainTrait(new Trait(xxx.nymphomaniac));
					//Log.Message(xxx.get_pawnname(pawn) + " is HumpShroomAddicted, not nymphomaniac, adding nymphomaniac trait");
				}

			}
			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomAddiction")) && !pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomEffect")))
			{
				//Log.Message("[RJW]Need_Sex::druggedfactor 0.5 pawn is " + xxx.get_pawnname(pawn));
				return 0.5f;
			}

			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomEffect")))
			{
				//Log.Message("[RJW]Need_Sex::druggedfactor 3 pawn is " + xxx.get_pawnname(pawn));
				return 3f;
			}

			//Log.Message("[RJW]Need_Sex::druggedfactor 1 pawn is " + xxx.get_pawnname(pawn));
			return 1f;
		}

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

			if (needsex_tick <= 0)
			{
				std.update(pawn);

				if (xxx.is_asexual(pawn)) return;

				//--Log.Message("[RJW]Need_Sex::NeedInterval is called0 - pawn is "+xxx.get_pawnname(pawn));
				needsex_tick = needsex_tick_timer;
				//age = age / maxage;

				if (!def.freezeWhileSleeping || pawn.Awake())
				{
					decay_rate_modifier = RJWSettings.sexneed_decay_rate * xxx.get_sex_drive(pawn);

					//every 200 calls will have a real functioning call
					var fall_per_tick =
						//def.fallPerDay *
						decay_per_day *
						brokenbodyfactor(pawn) *
						druggedfactor(pawn) *
						(((Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)) && Genital_Helper.has_vagina(pawn)) ? 2.0f : 1.0f) /
						60000.0f;
					//--Log.Message("[RJW]Need_Sex::NeedInterval is called - pawn is " + xxx.get_pawnname(pawn) + " is has both genders " + (Genital_Helper.has_penis(pawn) && Genital_Helper.has_vagina(pawn)));
					//Log.Message("[RJW] " + xxx.get_pawnname(pawn) + "'s sex need stats:: fall_per_tick: " + fall_per_tick + ", sex_need_factor_from_lifestage: " + sex_need_factor_from_lifestage(pawn) );

					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] " + xxx.get_pawnname(pawn) + "'s sex need stats:: Decay/call: " + fall_per_call * decay_rate_modifier + ", Cur.lvl: " + CurLevel + ", Dec. rate: " + decay_rate_modifier);
				}

				//--Log.Message("[RJW]Need_Sex::NeedInterval is called1");

				//If they need it, they should seek it
				if (CurLevel < thresh_horny() && (pawn.mindState.canLovinTick - Find.TickManager.TicksGame > 300) )
				{
					pawn.mindState.canLovinTick = Find.TickManager.TicksGame + 300;
				}

				// 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 " + xxx.get_pawnname(pawn));
					xxx.bootstrap(pawn.Map);
					BootStrapTriggered = true;
				}
			}
			else
			{
				needsex_tick--;
			}
			//--Log.Message("[RJW]Need_Sex::NeedInterval is called2 - needsex_tick is "+needsex_tick);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Looks up and returns a BodyPartTagDef defined in the XML
	/// </summary>
	public static class BodyPartTagDefOf {
		public static BodyPartTagDef RJW_FertilitySource
		{
			get
			{
				if (a == null) a = (BodyPartTagDef)GenDefDatabase.GetDef(typeof(BodyPartTagDef), "RJW_FertilitySource");
				return a;
			}
		}
		private static BodyPartTagDef a;
	}

}


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

namespace rjw
{
	/// <summary>
	/// Calculates a pawn's fertility based on its age and fertility sources
	/// </summary>
	public class PawnCapacityWorker_Fertility : PawnCapacityWorker
	{
		private readonly HashSet<String> Fertility_filter = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("Fertility_filter").strings);

		public override float CalculateCapacityLevel(HediffSet diffSet, List<PawnCapacityUtility.CapacityImpactor> impactors = null)
		{
			Pawn pawn = diffSet.pawn;

			if (!Genital_Helper.has_penis(pawn) && !Genital_Helper.has_vagina(pawn))
				return 0;

			if (!Genital_Helper.has_penis(pawn) && Genital_Helper.has_ovipositorF(pawn))
				return 0;

			//--Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called0");
			RaceProperties race = diffSet.pawn.RaceProps;

			if (Fertility_filter.Contains(pawn.kindDef.defName)
			|| (xxx.RoMIsActive && (pawn.health.hediffSet.HasHediff(HediffDef.Named("TM_UndeadHD")) || pawn.health.hediffSet.HasHediff(HediffDef.Named("TM_UndeadAnimalHD")))))
			{
				//Log.Message("[RJW] filtered race that cant be fertile, like droids, undead etc");
				return 0f;
			}

			float startAge = 0f;	//raise fertility
			float startMaxAge = 0f;	//max fertility
			float endAge = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_male * 0.7f); // Age when males start to lose potency.
			float zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_male;	// Age when fertility hits 0%.

			if (xxx.is_female(pawn))
			{
				if (xxx.is_animal(pawn))
				{
					endAge = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_female_animal * 0.6f);
					zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_female_animal;
				}
				else
				{
					endAge = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_female_humanlike * 0.6f); // Age when fertility begins to drop.
					zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_female_humanlike; // Age when fertility hits 0%.
				}
			}

			foreach (LifeStageAge lifestage in race.lifeStageAges)
			{
				if (lifestage.def.reproductive)
					//presumably teen stage
					if (startAge == 0f && startMaxAge == 0f)
					{
						startAge = lifestage.minAge;
						startMaxAge = (Mathf.Max(startAge + (startAge + endAge) * 0.08f, startAge));
					}
					//presumably adult stage
					else
					{
						if (startMaxAge > lifestage.minAge)
							startMaxAge = lifestage.minAge;
					}
			}
			//Log.Message("Fertility ages for pawn " + pawn.Name + " are: " + startAge + ", " + startMaxAge + ", " + endAge + ", " + endMaxAge);

			float result = PawnCapacityUtility.CalculateTagEfficiency(diffSet, BodyPartTagDefOf.RJW_FertilitySource, 1f, FloatRange.ZeroToOne, impactors);
			result *= GenMath.FlatHill(startAge, startMaxAge, endAge, zeroFertility, pawn.ageTracker.AgeBiologicalYearsFloat);

			//--Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called2 - result is " + result);
			return result;

		}

		public override bool CanHaveCapacity(BodyDef body)
		{
			return body.HasPartWithTag(BodyPartTagDefOf.RJW_FertilitySource);
		}
	}
}