code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the pawn can engage in sex.
	/// This should be used as the first conditional for sex-related thinktrees.
	/// </summary>
	public class ThinkNode_ConditionalSexChecks : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalSexChecks " + xxx.get_pawnname(p));
			//if (p.Faction != null && p.Faction.IsPlayer)
			//	Log.Message("[RJW]ThinkNode_ConditionalSexChecks " + xxx.get_pawnname(p) + " is animal: " + xxx.is_animal(p));

			// Downed, Drafted and Awake are checked in core ThinkNode_ConditionalCanDoConstantThinkTreeJobNow.
			if (p.Map == null)
				return false;

			// Override starvation/health when in debug.
			if (DebugSettings.alwaysDoLovin)
				return true;

			// Setting checks.
			if (xxx.is_human(p) && p.ageTracker.AgeBiologicalYears < RJWSettings.sex_minimum_age)
				return false;
			else if (xxx.is_animal(p) && !RJWSettings.bestiality_enabled && !RJWSettings.animal_on_animal_enabled)
				return false;

			// No sex while starving or badly hurt.
			return ((!p.needs?.food?.Starving) ?? true && xxx.is_healthy_enough(p));
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// No idea what this is, dosnt look to be used anywhere
	/// </summary>
	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(xxx.get_pawnname(pawn)+" has trait" + this.trait.defName + ":" + pawn.story.traits.HasTrait(this.trait));
					return pawn.story.traits.HasTrait(this.trait);
				}
			return false;
		}
	}
}

repo_name: Harper/rjw

license: mit


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_FeelingBroken : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			var brokenstages = p.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken);
			if (brokenstages != null && brokenstages.CurStageIndex != 0)
			{
				return ThoughtState.ActiveAtStage(brokenstages.CurStageIndex - 1);
			}
			return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_NeedSex : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			var sex_need = p.needs.TryGetNeed<Need_Sex>();
			var p_age = p.ageTracker.AgeBiologicalYears;
			if (sex_need != null && (p_age > RJWSettings.sex_minimum_age || (!xxx.is_human(p) && p.ageTracker.CurLifeStage.reproductive)))
			{
				var lev = sex_need.CurLevel;
				if (lev <= sex_need.thresh_frustrated())
					return ThoughtState.ActiveAtStage(0);
				else if (lev <= sex_need.thresh_horny())
					return ThoughtState.ActiveAtStage(1);
				else if (lev >= sex_need.thresh_ahegao())
					return ThoughtState.ActiveAtStage(3);
				else if (lev >= sex_need.thresh_satisfied())
					return ThoughtState.ActiveAtStage(2);
				else
					return ThoughtState.Inactive;
			}
			else
				return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	//This thought system of RW is retarded AF. It needs separate thought handler for each hediff.
	public abstract class ThoughtWorker_SexChange : ThoughtWorker
	{
		public virtual HediffDef hediff_served { get; }
		protected override ThoughtState CurrentStateInternal(Pawn pawn)
		{
			//Log.Message(" "+this.GetType() + " is called for " + pawn +" and hediff" + hediff_served);
			Hediff denial = pawn.health.hediffSet.GetFirstHediffOfDef(hediff_served);
			//Log.Message("Hediff of the class is null " + (hediff_served == null));
			if (denial != null && denial.CurStageIndex!=0)
			{
				//Log.Message("Current denial level is  " + denial.CurStageIndex );
				return ThoughtState.ActiveAtStage(denial.CurStageIndex-1);
			}
			return ThoughtState.Inactive;
		}
	}
	public class ThoughtWorker_MtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2t; } }
	}
	public class ThoughtWorker_MtF:ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2f; }  }
	}
	public class ThoughtWorker_MtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2h; } }
	}
	public class ThoughtWorker_FtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2t; } }
	}
	public class ThoughtWorker_FtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2m; } }
	}
	public class ThoughtWorker_FtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2h; } }
	}
	public class ThoughtWorker_HtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2t; } }
	}
	public class ThoughtWorker_HtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2m; } }
	}
	public class ThoughtWorker_HtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2f; } }
	}
	public class ThoughtWorker_TtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2h; } }
	}
	public class ThoughtWorker_TtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2m; } }
	}
	public class ThoughtWorker_TtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2f; } }
	}
}


code:
using Verse;
using Verse.AI.Group;

namespace rjw
{
	public class Trigger_SexSatisfy : TriggerFilter
	{
		private const int CheckInterval = 120;
		private const int TickTimeout = 900;
		private int currentTick = 0;
		public float targetValue = 0.3f;

		public Trigger_SexSatisfy(float t)
		{
			this.targetValue = t;
			currentTick = 0;
		}

		public override bool AllowActivation(Lord lord, TriggerSignal signal)
		{
			currentTick++;
			if (signal.type == TriggerSignalType.Tick && Find.TickManager.TicksGame % CheckInterval == 0)
			{
				float? avgValue = null;
				foreach (var pawn in lord.ownedPawns)
				{
					/*foreach(Pawn p in lord.Map.mapPawns.PawnsInFaction(Faction.OfPlayer))
					{
					}*/
					Need_Sex n = pawn.needs.TryGetNeed<Need_Sex>();
					//if (n != null && pawn.gender == Gender.Male && !pawn.Downed)
					if(xxx.can_rape(pawn) && xxx.is_healthy_enough(pawn) && xxx.IsTargetPawnOkay(pawn) && Find.TickManager.TicksGame > pawn.mindState.canLovinTick)
					{
						avgValue = (avgValue == null) ? n.CurLevel : (avgValue + n.CurLevel) / 2f;
					}
				}
				//--Log.Message("[ABF]Trigger_SexSatisfy::ActivateOn Checked value :" + avgValue + "/" + targetValue);
				return avgValue == null || avgValue >= targetValue;
			}
			return currentTick >= TickTimeout;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityF : WorkGiver_RJW_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(passive)
	/// </summary>
	public class WorkGiver_BestialityForFemale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + ":: base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn cant be fucked");
				return false;
			}

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (xxx.need_some_sex(pawn) < 2f)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//Log.Message("[RJW]WorkGiver_BestialityForFemale::" + xxx.would_fuck_animal(pawn, target));
					if (xxx.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			Building_Bed bed = pawn.ownership.OwnedBed;
			if (bed == null)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn has no bed");
				return false;
			}
			if (!target.CanReach(bed, PathEndMode.OnCell, Danger.Some) || target.Downed)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant reach bed");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			Building_Bed bed = pawn.ownership.OwnedBed;
			return new Job(xxx.bestialityForFemale, t, bed, bed.SleepPosOfAssignedPawn(pawn));
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(active)
	/// </summary>
	public class WorkGiver_BestialityForMale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant be fucked");
				return false;
			}

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (xxx.need_some_sex(pawn) < 2f)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//Log.Message("[RJW]WorkGiver_BestialityForMale::" + xxx.would_fuck_animal(pawn, target));
					if (xxx.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			if (!xxx.can_fuck(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("cant fuck");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.bestiality, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityM : WorkGiver_RJW_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to cleanup/collect sex fluids
	/// </summary>
	//TODO: add sex fluid collection/cleaning
	public class WorkGiver_CleanSexStuff : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.filth);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap
	/// </summary>
	public class WorkGiver_Fap : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			Pawn target = t as Pawn;
			if (target != pawn)
			{
				return false;
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_nympho(pawn))
						if ((xxx.need_some_sex(pawn) < 2f))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (pawn.CanSee(bystander) && pawn.Position.DistanceToSquared(bystander.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.quickfap, t.Position);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap in bed
	/// </summary>
	public class WorkGiver_Fap_bed : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			Building_Bed target = t as Building_Bed;
			if (target == null)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}

			if (!target.AssignedPawns.Contains(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not pawn bed");
				return false;
			}
			/*
			if (!target.AssignedPawns.Contains(pawn))
				if (!pawn.CanReserve(target, target.SleepingSlotsCount, 0) && (!target.AnyUnownedSleepingSlot))
				{
					if (RJWSettings.DevMode) JobFailReason.Is("no space in bed");
					return false;
				}
			*/

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_nympho(pawn))
						if ((xxx.need_some_sex(pawn) < 2f))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (target.CurOccupants.Count() != 0)
					{
						if (target.CurOccupants.Count() == 1 && !target.CurOccupants.Contains(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
						if (target.CurOccupants.Count() > 1)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
					}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (target.CanSee(bystander) && target.Position.DistanceToSquared(bystander.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}

			//experimental change textures of bed to whore bed
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " path " + t.Graphic.data.texPath);
			//t.Graphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.Graphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " texPath " + t.Graphic.data.texPath);
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " drawSize " + t.Graphic.data.drawSize);
			//t.Draw();
			//t.ExposeData();
			//Scribe_Values.Look(ref t.Graphic.data.texPath, t.Graphic.data.texPath, "Things/Building/Furniture/Bed/DoubleBedWhore", true);

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.fappin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape
	/// </summary>
	public class WorkGiver_Rape : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self rape", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_kind(pawn) || xxx.is_masochist(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape");
						return false;
					}
					if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape a friend");
						return false;
					}
					if (!xxx.can_rape(pawn) || !xxx.can_get_raped(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("cannot rape target (vulnerability too low)");
						return false;
					}
					//fail for:
					//satisfied pawns
					//horny non rapists
					if ((xxx.need_some_sex(pawn) <= 1f)
						|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot rape while partner around");
								return false;
							}
					//Log.Message("[RJW]WorkGiver_RapeCP::" + xxx.would_fuck(pawn, target));
					if (xxx.would_fuck(pawn, target) > 0.1f)
					{
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}
		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.random_rape, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_RapeCP : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!target.IsDesignatedComfort())
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not designated as comfort", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape enemy
	/// </summary>
	public class WorkGiver_RapeEnemy : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!pawn.HostileTo(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not hostile", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.RapeEnemy, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to have sex with
	/// </summary>
	public class WorkGiver_Sex : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self sex", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, 1, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					//fail for:
					//satisfied non nymph pawns
					if (xxx.need_some_sex(pawn) <= 1f && !xxx.is_nympho(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.IsTargetPawnOkay(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot have sex while partner around");
								return false;
							}
					//Log.Message("[RJW]WorkGiver_Sex::" + xxx.would_fuck(pawn, target));
					if (xxx.would_fuck(pawn, target) > 0.1f)
					{
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//TODO:: fix bed stealing during join other pawn
			//Building_Bed bed = pawn.ownership.OwnedBed;
			//if (bed == null)
			//	bed = (t as Pawn).ownership.OwnedBed;

			Building_Bed bed = pawn.CurrentBed();

			if (bed == null)
				return null;

			if (pawn.CurrentBed() != (t as Pawn).CurrentBed())
				return null;

			return new Job(xxx.casual_sex, pawn, t as Pawn, bed);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Allow pawn to have sex
	/// dunno if this should be used to allow manual sex start or limit it behind sort of "hero" designator for RP purposes, so player can only control 1 pawn directly?
	/// </summary>
	public class WorkGiver_Sexchecks : WorkGiver_Scanner
	{
		public override int LocalRegionsToScanFirst => 4;
		public override PathEndMode PathEndMode => PathEndMode.OnCell;
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Pawn);

		public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			if (!forced)
			//if (!forced && !RJWSettings.WildMode)
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::not player interaction, exit:" + forced);
				return false;
			}
			Pawn target = t as Pawn;
			if (t is Corpse)
			{
				Corpse corpse = t as Corpse;
				target = corpse.InnerPawn;
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target corpse(" + xxx.get_pawnname(target) + ")");
			}
			else
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target pawn(" + xxx.get_pawnname(target) + ")");
			}

			//Log.Message("1");
			if (t == null || t.Map == null)
			{
				return false;
			}
			//Log.Message("2");
			if (!(xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)))
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(pawn) + ") is cannot fuck or be fucked.");
				return false;
			}
			//Log.Message("3");
			if (t is Pawn)
				if (!(xxx.can_fuck(target) || xxx.can_be_fucked(target)))
				{
					//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(target) + ") is cannot fuck or be fucked.");
					return false;
				}
			//Log.Message("4");

			//investigate AoA, someday
			//move this?
			//if (xxx.is_animal(pawn) && xxx.is_animal(target) && !RJWSettings.animal_on_animal_enabled)
			//{
			//	return false;
			//}
			if (!xxx.is_human(pawn) && !(xxx.RoMIsActive && pawn.health.hediffSet.HasHediff(HediffDef.Named("TM_ShapeshiftHD"))))
			{
				return false;
			}

			//Log.Message("5");
			if (!pawn.CanReach(t, PathEndMode, Danger.Some))
			{
				if (RJWSettings.DevMode) JobFailReason.Is(
					pawn.CanReach(t, PathEndMode, Danger.Deadly)
						? "unable to reach target safely" : "target unreachable");
				return false;
			}
			//Log.Message("6");
			if (t.IsForbidden(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target is outside of allowed area");
				return false;
			}
			//Log.Message("7");
			if (!pawn.IsDesignatedHero())
			{
				if (!RJWSettings.WildMode)
				{
					if (pawn.IsDesignatedComfort() || pawn.IsDesignatedBreeding())
					{
						if (RJWSettings.DevMode) JobFailReason.Is("designated pawns cannot initiate sex");
						return false;
					}
					if (!xxx.is_healthy_enough(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not healthy enough for sex");
						return false;
					}
					if (xxx.is_asexual(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to have sex");
						return false;
					}
				}

			}
			else
			{
				if (!pawn.IsHeroOwner())
				{
					//Log.Message("[RJW]WorkGiver_Sexchecks::player interaction for not owned hero, exit");
					return false;
				}
			}

			if (!MoreChecks(pawn, t, forced))
				return false;

			return true;
		}

		public virtual bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public virtual bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a corpse
	/// </summary>
	public class WorkGiver_ViolateCorpse : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Corpse);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.necrophilia_enabled) return false;

			//Pawn target = (t as Corpse).InnerPawn;
			if (!pawn.CanReserve(t, 1, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_necrophiliac(pawn) && xxx.need_some_sex(pawn) < 2f)
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.is_necrophiliac(pawn))
						if ((t as Corpse).CurRotDrawMode != RotDrawMode.Fresh)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("refuse to rape rotten");
							return false;
						}
						else if (xxx.need_some_sex(pawn) < 3f)
							{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
								return false;
							}
					//Log.Message("[RJW]WorkGiver_ViolateCorpse::" + xxx.would_fuck(pawn, t as Corpse));
					if (xxx.would_fuck(pawn, t as Corpse) > 0.1f)
					{
						return false;
					}
				}
			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return new Job(xxx.violate_corpse, t as Corpse);
		}
	}
}