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

namespace rjw
{
    public class Building_WhoreBed : Building_Bed
    {
        private static readonly Color WhoreFieldColor = new Color(170/255f, 79/255f, 255/255f);

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

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

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

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

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

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

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

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

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

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

        // Is this one even being used??
        public override IEnumerable<Gizmo> GetGizmos()
        {
            // Get original gizmos from Building class
            var method = typeof(Building).GetMethod("GetGizmos");
            var ftn = method.MethodHandle.GetFunctionPointer();
            var func = (Func<IEnumerable<Gizmo>>)Activator.CreateInstance(typeof(Func<IEnumerable<Gizmo>>), this, ftn);

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

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

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

        public override void DrawGUIOverlay()
        {
            //if (Find.CameraMap.CurrentZoom == CameraZoomRange.Closest)
            //{
            //    if (owner != null && owner.InBed() && owner.CurrentBed().owner == owner)
            //    {
            //        return;
            //    }
            //    string text;
            //    if (owner != null)
            //    {
            //        text = owner.NameStringShort;
            //    }
            //    else
            //    {
            //        text = "Unowned".Translate();
            //    }
            //    GenWorldUI.DrawThingLabel(this, text, new Color(1f, 1f, 1f, 0.75f));
            //}
        }

        public static void Swap(Building_Bed bed)
        {
            Building_Bed newBed;
            if (bed is Building_WhoreBed)
            {
                newBed = (Building_Bed) MakeBed(bed, bed.def.defName.Split(new[] { "RJW_" }, StringSplitOptions.RemoveEmptyEntries)[0]);
            }
            else
            {
                newBed = (Building_WhoreBed) MakeBed(bed, "RJW_"+bed.def.defName);
            }
            newBed.SetFactionDirect(bed.Faction);
            var spawnedBed = (Building_Bed)GenSpawn.Spawn(newBed, bed.Position, bed.Map, bed.Rotation);
            spawnedBed.HitPoints = bed.HitPoints;
            spawnedBed.ForPrisoners = bed.ForPrisoners;

            var compQuality = spawnedBed.TryGetComp<CompQuality>();

            if(compQuality != null) compQuality.SetQuality(bed.GetComp<CompQuality>().Quality, ArtGenerationContext.Outsider);
            //var compArt = bed.TryGetComp<CompArt>();
            //if (compArt != null)
            //{
            //    var art = spawnedBed.GetComp<CompArt>();
            //    Traverse.Create(art).Field("authorNameInt").SetValue(Traverse.Create(compArt).Field("authorNameInt").GetValue());
            //    Traverse.Create(art).Field("titleInt").SetValue(Traverse.Create(compArt).Field("titleInt").GetValue());
            //    Traverse.Create(art).Field("taleRef").SetValue(Traverse.Create(compArt).Field("taleRef").GetValue());
            //
            //    // TODO: Make this work, art is now destroyed
            //}
            Find.Selector.Select(spawnedBed, false);
        }

        public static Thing MakeBed(Building_Bed bed, string defName)
        {
			Log.Message("1");
			ThingDef newDef = DefDatabase<ThingDef>.GetNamed(defName);
			Log.Message("2");
			return ThingMaker.MakeThing(newDef, bed.Stuff);
        }
    }
}


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

		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 (RJWSettings.DebugWhoring) Log.Message($"JobDriver_InvitingVisitors::DoesTargetPawnAcceptAdvance() - {xxx.get_pawnname(TargetPawn)}");
			//if (RJWSettings.WildMode) return true;

			if (PawnUtility.EnemiesAreNearby(TargetPawn))
			{
				if (RJWSettings.DebugWhoring) Log.Message($" fail - enemy near");
				return false;
			}
			if (!allowedJobs.Contains(TargetPawn.jobs.curJob.def))
			{
				if (RJWSettings.DebugWhoring) Log.Message($" fail - not allowed job");
				return false;
			}

			if (RJWSettings.DebugWhoring)
			{
				Log.Message("Will try hookup " + WhoringHelper.WillPawnTryHookup(TargetPawn));
				Log.Message("Is whore appealing  " + WhoringHelper.IsHookupAppealing(TargetPawn, Whore));
				Log.Message("Can afford whore " + WhoringHelper.CanAfford(TargetPawn, Whore));
				Log.Message("Need sex " + (xxx.need_some_sex(TargetPawn) >= 1));
			}
			if (WhoringHelper.WillPawnTryHookup(TargetPawn) && WhoringHelper.IsHookupAppealing(TargetPawn, Whore) && WhoringHelper.CanAfford(TargetPawn, Whore) && xxx.need_some_sex(TargetPawn) >= 1f)
			{
				Whore.skills.Learn(SkillDefOf.Social, 1.2f);
				return true;
			}
			return false;
		}

		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(() => !xxx.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(JobMaker.MakeJob(xxx.whore_is_serving_visitors, TargetPawn, TargetBed, TargetBed.SleepPosOfAssignedPawn(Whore)));
				//TargetPawn.jobs.jobQueue.EnqueueFirst(JobMaker.MakeJob(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;
		}
	}
}

repo_name: Mewtopian/rjw

license: mit


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

namespace rjw
{
	public class JobDriver_WhoreIsServingVisitors : JobDriver_SexBaseInitiator
	{
		public readonly TargetIndex BedInd = TargetIndex.B;
		public readonly TargetIndex SlotInd = TargetIndex.C;

		public static readonly ThoughtDef thought_free = ThoughtDef.Named("Whorish_Thoughts");
		public static readonly ThoughtDef thought_captive = ThoughtDef.Named("Whorish_Thoughts_Captive");

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

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

		[SyncMethod]
		protected override IEnumerable<Toil> MakeNewToils()
		{
			//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - making toils");
			setup_ticks();

			this.FailOnDespawnedOrNull(iTarget);
			this.FailOnDespawnedNullOrForbidden(BedInd);
			//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() fail conditions check " + !xxx.CanUse(pawn, Bed) + " " + !pawn.CanReserve(Target));
			this.FailOn(() => !xxx.CanUse(pawn, Bed) || !pawn.CanReserve(Target));
			this.FailOn(() => pawn.Drafted);
			this.FailOn(() => Target.IsFighting());
			yield return Toils_Reserve.Reserve(iTarget, 1, 0);
			//yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);

			int price = WhoringHelper.PriceOfWhore(pawn);
			bool partnerHasPenis = Genital_Helper.has_penis(Target) || Genital_Helper.has_penis_infertile(Target);

			//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - generate toils");
			Toil gotoBed = new Toil();
			gotoBed.defaultCompleteMode = ToilCompleteMode.PatherArrival;
			gotoBed.FailOnWhorebedNoLongerUsable(BedInd, Bed);
			gotoBed.AddFailCondition(() => Target.Downed);
			gotoBed.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - gotoWhoreBed initAction is called");
				pawn.pather.StartPath(WhoreSleepSpot, PathEndMode.OnCell);
				Target.jobs.StopAll();
			};
			gotoBed.tickAction = delegate
			{
				//every second, try pathing
				if (GenTicks.TicksGame % 60 == 0)
				{
					// i insist motherfucker
					float radius = 0.1f;
					Job job = JobMaker.MakeJob(JobDefOf.FollowClose, pawn);
					job.followRadius = radius;
					Target.jobs.StartJob(job, JobCondition.InterruptForced);
				}

				gotoBed.FailOn(() => pawn.CurJob.def != this.job.def);
			};
			yield return gotoBed;

			ticks_left = (int)(2000.0f * Rand.Range(0.30f, 1.30f));

			Toil waitInBed = new Toil();
			waitInBed.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - waitInBed");
				Target.pather.StartPath(WhoreSleepSpot, PathEndMode.OnCell);
				ticksLeftThisToil = 5000;
			};
			waitInBed.tickAction = delegate
			{
				pawn.GainComfortFromCellIfPossible();
				if (IsInOrByBed(Bed, Target))
				{
					//Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - waitInBed, tickAction pass");
					ReadyForNextToil();
				}
				else if (GenTicks.TicksGame % 60 == 0) {
					Target.pather.StartPath(WhoreSleepSpot, PathEndMode.OnCell);
				}
				//waitInBed.FailOn(() => Target.CurJob.def != JobDefOf.FollowClose);

			};
			waitInBed.AddFinishAction(delegate {
				if (!IsInOrByBed(Bed, Target)) {
					this.EndJobWith(JobCondition.Incompletable);
				}
			});
			waitInBed.defaultCompleteMode = ToilCompleteMode.Delay;
			yield return waitInBed;

			Toil StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - StartPartnerJob");
				var gettin_loved = JobMaker.MakeJob(xxx.gettin_loved, pawn, Bed);
				Target.jobs.StartJob(gettin_loved, JobCondition.InterruptForced);
			};
			yield return StartPartnerJob;

			Toil loveToil = new Toil();
			loveToil.AddFailCondition(() => Target.Dead || Target.CurJobDef != xxx.gettin_loved);
			loveToil.defaultCompleteMode = ToilCompleteMode.Never;
			loveToil.socialMode = RandomSocialMode.Off;
			loveToil.initAction = delegate
			{
				//Log.Message("[RJW]JobDriver_WhoreIsServingVisitors::MakeNewToils() - loveToil");
				// TODO: replace this quick n dirty way
				CondomUtility.GetCondomFromRoom(pawn);

				// Try to use whore's condom first, then client's
				usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Target);

				Start();

				if (xxx.HasNonPolyPartnerOnCurrentMap(Target))
				{
					Pawn lover = LovePartnerRelationUtility.ExistingLovePartner(Target);
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					// We have to do a few other checks because the pawn might have multiple lovers and ExistingLovePartner() might return the wrong one
					if (lover != null && pawn != lover && !lover.Dead && (lover.Map == Target.Map || Rand.Value < 0.25))
					{
						lover.needs.mood.thoughts.memories.TryGainMemory(ThoughtDefOf.CheatedOnMe, Target);
					}
				}
				if (!partnerHasPenis)
					pawn.rotationTracker.Face(Target.DrawPos);
			};
			loveToil.AddPreTickAction(delegate
			{
				//pawn.Reserve(Target, 1, 0);
				--ticks_left;
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
				pawn.GainComfortFromCellIfPossible();
				Target.GainComfortFromCellIfPossible();
				xxx.reduce_rest(Target, 1);
				xxx.reduce_rest(pawn, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
			});
			loveToil.AddFinishAction(delegate
			{
				if (xxx.is_human(pawn))
					pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
				//End();
			});
			yield return loveToil;

			Toil afterSex = new Toil
			{
				initAction = delegate
				{
					// Adding interactions, social logs, etc
					SexUtility.ProcessSex(pawn, Target, false, false, true);

					//--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Target should pay the price now in afterSex.initAction");
					int remainPrice = WhoringHelper.PayPriceToWhore(Target, price, pawn);
					/*if (remainPrice <= 0)
					{
						--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Paying price is success");
					}
					else
					{
						--Log.Message("JobDriver_WhoreIsServingVisitors::MakeNewToils() - Paying price failed");
					}*/
					xxx.UpdateRecords(pawn, price-remainPrice);
					var thought = (pawn.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 = JobMaker.MakeJob(JobDefOf.Clean);
						clean.AddQueuedTarget(TargetIndex.A, cum);

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


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

namespace rjw
{
	public class JobGiver_WhoreInvitingVisitors : ThinkNode_JobGiver
	{
		// 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;
		}

		[SyncMethod]
		private static bool Roll_to_skip(Pawn client, Pawn whore)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float fuckability = SexAppraiser.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.IsTargetPawnOkay(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
			[SyncMethod]
			internal bool RelationCheckPass(Pawn client)
			{
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				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;
			}
		}

		[SyncMethod]
		public static Pawn FindAttractivePawn(Pawn whore, out int price)
		{
			price = 0;
			if (whore == null || xxx.is_asexual(whore))
			{
				if (RJWSettings.DebugWhoring) Log.Message($" {xxx.get_pawnname(whore)} is asexual, abort");
				return null;
			}
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());

			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.HostileTo(whore)
				&& !x.IsPrisoner
				&& x.Position.DistanceTo(pos) < 100
				&& 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;

			if (RJWSettings.DebugWhoring) Log.Message($" FindAttractivePawn number of all potential clients {potentialClients.Count()}");

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

			foreach (Pawn target in potentialClients)
			{
				if(xxx.can_path_to_target(whore, target.Position))
					valid_targets.Add(target);
			}

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

			if (guestsSpawned.Any())
			{
				if (RJWSettings.DebugWhoring) Log.Message($" FindAttractivePawn number of all acceptable Guests {guestsSpawned.Count()}");
				return guestsSpawned.RandomElement();
			}

			return null;
			//use casual sex for colonist hooking
			if (RJWSettings.DebugWhoring) Log.Message($" FindAttractivePawn found no guests, trying colonists");

			if (!WhoringHelper.WillPawnTryHookup(whore))// will hookup colonists?
			{
				return null;
			}
			IEnumerable<Pawn> freeColonists = valid_targets.Where(x => x.Faction == whore.Faction
				&& Roll_to_skip(x, whore));

			if (RJWSettings.DebugWhoring) Log.Message($" FindAttractivePawn number of free colonists {freeColonists.Count()}");

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

			if (freeColonists.Any())
			{
				if (RJWSettings.DebugWhoring) Log.Message($" FindAttractivePawn number of all acceptable Colonists {freeColonists.Count()}");
				return freeColonists.RandomElement();
			}

			return null;
		}

		protected override Job TryGiveJob(Pawn pawn)
		{

			// Most things are now checked in the ThinkNode_ConditionalWhore.

			if (pawn.Drafted) return null;
			if (MP.IsInMultiplayer) return null; //fix error someday, maybe

			if (!SexUtility.ReadyForLovin(pawn))
			{
				//Whores need rest too, but this'll reduxe the wait a bit every it triggers.
				pawn.mindState.canLovinTick -= 40;
				return null;
			}

			if (RJWSettings.DebugWhoring) Log.Message($"[RJW] JobGiver_WhoreInvitingVisitors.TryGiveJob:({xxx.get_pawnname(pawn)})");
			Building_Bed whorebed = xxx.FindBed(pawn);
			if (whorebed == null || !xxx.CanUse(pawn, whorebed))
			{
				if (RJWSettings.DebugWhoring) Log.Message($" {xxx.get_pawnname(pawn)} has no bed or can use it");
				return null;
			}

			int price;
			Pawn client = FindAttractivePawn(pawn, out price);
			if (client == null)
			{
				if (RJWSettings.DebugWhoring) Log.Message($" no clients found");
				return null;
			}

			if (RJWSettings.DebugWhoring) Log.Message($" {xxx.get_pawnname(client)} is client");

			if (!client.CanReach(whorebed, PathEndMode.OnCell, Danger.Some))
			{
				if (RJWSettings.DebugWhoring) Log.Message($" {xxx.get_pawnname(client)} cant reach bed");
				return null;
			}
			//whorebed.priceOfWhore = price;
			return JobMaker.MakeJob(xxx.whore_inviting_visitors, client, whorebed);
		}
	}
}


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

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

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

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

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

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


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

namespace rjw
{
	/// <summary>
	/// Whore/prisoner look for customers
	/// </summary>
	public class ThinkNode_ConditionalWhore : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			// No animal whorin' for now.
			if (xxx.is_animal(p))
				return false;

			if (!InteractionUtility.CanInitiateInteraction(p))
				return false;

			return xxx.is_whore(p);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Extends the standard thought to add a counter for the whore stages
	/// </summary>
	class ThoughtDef_Whore : ThoughtDef
	{
		public List<int> stageCounts = new List<int>();
		public int storyOffset = 0;
	}

	class ThoughtWorker_Whore : Thought_Memory
	{
		public static readonly HashSet<string> backstories = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("WhoreBackstories").strings);
		protected readonly RecordDef whore_count = DefDatabase<RecordDef>.GetNamed("CountOfWhore");
		protected List<int> stages { get {return ((ThoughtDef_Whore)def).stageCounts; } }
		protected int storyOffset { get { return ((ThoughtDef_Whore)def).storyOffset; } }
		//protected virtual readonly List<int> stages = new List<int>() { 10, 40};
		//protected virtual readonly int story_offset = 10;

		public override int CurStageIndex
		{
			get
			{
				//Log.Message("Static fields are not null " + !(backstories is null) + !(whore_count is null));
				var c = pawn.records.GetAsInt(whore_count);
				//Log.Message("Whore count of " + pawn + " is " + c);
				var b = backstories.Contains(pawn.story?.adulthood?.titleShort) ? storyOffset : 0;
				//Log.Message("Backstory offset " + b);
				var score = c + b;
				if (score > stages[stages.Count-1])
				{
					return stages.Count - 1;
				}
				//Log.Message("Starting search");
				var stage = stages.FindLastIndex(v => score > v)+1;
				//Log.Message("Search done, stage is " + stage);
				return stage;
			}
		}
	}
}


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

using Verse;
using Verse.AI;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using System.Diagnostics;
using System;
using UnityEngine;
using Verse.AI.Group;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// Helper for whoring related stuff
	/// </summary>
	public class WhoringHelper
	{
		private static readonly SimpleCurve whoring_age_curve = new SimpleCurve
		{
			// life expectancy to price modifier
			new CurvePoint(0.15f,  0f), // 12 by human age, No whoring allowed for underage.
			new CurvePoint(0.22f,  1.5f), // 18
			new CurvePoint(0.3f, 1.4f), // 24
			new CurvePoint(0.4f, 1f), // 32
			new CurvePoint(0.6f, 0.75f), // 48
			new CurvePoint(1.0f, 0.5f), // 80
			new CurvePoint(5.0f, 0.5f), // Lifespan extended by bionics, misconfigurated alien races, etc.
		};

		public static int WhoreMinPrice(Pawn whore)
		{
			float min_price = 20f;
			min_price *= whoring_age_curve.Evaluate(SexUtility.ScaleToHumanAge(whore));

			if (xxx.has_traits(whore))
			{
				if (xxx.IndividualityIsActive && whore.story.traits.HasTrait(TraitDef.Named("SYR_Haggler")))
					min_price *= 1.5f;
				if (whore.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 2) // Industrious
					min_price *= 1.5f;
				if (whore.story.traits.DegreeOfTrait(TraitDefOf.NaturalMood) == -2) // Depressive
					min_price *= 0.5f;
				if (whore.story.traits.HasTrait(TraitDefOf.CreepyBreathing))
					min_price *= 0.75f;
				if (whore.story.traits.HasTrait(TraitDefOf.Beauty))
				{
					switch (whore.story.traits.DegreeOfTrait(TraitDefOf.Beauty))
					{
						case 2:
							min_price *= 3f;
							break;
						case 1:
							min_price *= 1.5f;
							break;
						case -1:
							min_price *= 0.5f;
							break;
						case -2:
							min_price *= 0.2f;
							break;
					}
				}
				if (xxx.is_masochist(whore)) // Won't haggle, may settle for low price
					min_price *= 0.7f;
				if (xxx.is_nympho(whore)) // Same as above
					min_price *= 0.4f;
			}

			min_price *= WhoreInjuryAdjustment(whore);
			if (whore.ownership.OwnedRoom != null)
				min_price *= WhoreRoomAdjustment(whore);
			return (int)min_price;
		}

		public static int WhoreMaxPrice(Pawn whore)
		{
			float max_price = 40f;
			max_price *= whoring_age_curve.Evaluate(SexUtility.ScaleToHumanAge(whore));

			if (whore.gender == Gender.Female)
				max_price *= 1.2f;
			if (xxx.has_traits(whore))
			{
				if (whore.story.traits.HasTrait(TraitDefOf.Greedy))
					max_price *= 2f;
				if (xxx.IndividualityIsActive && whore.story.traits.HasTrait(TraitDef.Named("SYR_Haggler")))
					max_price *= 1.5f;
				if (whore.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 2) // Industrious
					max_price *= 1.5f;
				if (whore.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 1) // Hard Worker
					max_price *= 1.2f;
				if (whore.story.traits.HasTrait(TraitDefOf.CreepyBreathing))
					max_price *= 0.75f;
				if (whore.story.traits.HasTrait(TraitDefOf.Beauty))
				{
					switch (whore.story.traits.DegreeOfTrait(TraitDefOf.Beauty))
					{
						case 2:
							max_price *= 3.5f;
							break;
						case 1:
							max_price *= 2f;
							break;
						case -1:
							max_price *= 0.8f;
							break;
						case -2:
							max_price *= 0.6f;
							break;
					}
				}
			}

			max_price *= WhoreInjuryAdjustment(whore);
			if (whore.ownership.OwnedRoom != null)
				max_price *= WhoreRoomAdjustment(whore);
			return (int)max_price;
		}

		public static float WhoreInjuryAdjustment(Pawn whore)
		{
			float modifier = 1.0f;
			int injuries = whore.health.hediffSet.hediffs.Count(x => x.Visible && x.def.everCurableByItem && x.IsPermanent());

			if (injuries == 0) return modifier;

			while (injuries > 0)
			{
				modifier *= 0.85f;
				injuries--;
			}
			return modifier;
		}

		public static float WhoreRoomAdjustment(Pawn whore)
		{
			float room_multiplier = 1f;
			Room ownedRoom = whore.ownership.OwnedRoom;

			if (ownedRoom == null) return 0f;

			//Room sharing is not liked by patrons
			room_multiplier = room_multiplier / (2 * (ownedRoom.Owners.Count() - 1) + 1);
			int scoreStageIndex = RoomStatDefOf.Impressiveness.GetScoreStageIndex(ownedRoom.GetStat(RoomStatDefOf.Impressiveness));
			//Room impressiveness factor
			//0 < scoreStageIndex < 10 (Last time checked)
			//3 is mediocore
			if (scoreStageIndex == 0) { room_multiplier *= 0.3f; }
			if (scoreStageIndex > 3) { room_multiplier *= 1 + (scoreStageIndex - 3) / 3; } //top room triples the price
			return room_multiplier;
		}

		[SyncMethod]
		public static int PriceOfWhore(Pawn whore)
		{
			float NeedSexFactor = xxx.need_some_sex(whore) > 1 ? 1 - xxx.need_some_sex(whore) / 8 : 1f;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float price = Rand.Range(WhoreMinPrice(whore), WhoreMaxPrice(whore));

			price *= NeedSexFactor;
			//--Log.Message("[RJW] xxx::PriceOfWhore - price is " + price);

			return (int)Math.Round(price);
		}

		public static bool CanAfford(Pawn targetPawn, Pawn whore, int priceOfWhore = -1)
		{
			if (targetPawn.Faction == whore.Faction) return true;

			int price = priceOfWhore < 0 ? PriceOfWhore(whore) : priceOfWhore;
			if (price == 0)
				return true;

			Lord lord = targetPawn.GetLord();
			Faction faction = targetPawn.Faction;
			int totalAmountOfSilvers = targetPawn.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver);

			if (faction != null)
			{
				List<Pawn> caravanMembers = targetPawn.Map.mapPawns.PawnsInFaction(targetPawn.Faction).Where(x => x.GetLord() == lord && x.inventory?.innerContainer?.TotalStackCountOfDef(ThingDefOf.Silver) > 0).ToList();
				if (!caravanMembers.Any())
				{
					//--Log.Message("[RJW]CanAfford::(" + xxx.get_pawnname(targetPawn) + "," + xxx.get_pawnname(whore) + ") - totalAmountOfSilvers is " + totalAmountOfSilvers);
					return totalAmountOfSilvers >= price;
				}

				totalAmountOfSilvers += caravanMembers.Sum(member => member.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver));
			}

			//Log.Message("[RJW]CanAfford:: caravan can afford the price: " + (totalAmountOfSilvers >= price));
			return totalAmountOfSilvers >= price;
		}

		//priceOfWhore is assumed >=0, and targetPawn is assumed to be able to pay the price(either by caravan, or by himself)
		//This means that targetPawn has total stackcount of silvers >= priceOfWhore.
		public static int PayPriceToWhore(Pawn targetPawn, int priceOfWhore, Pawn whore)
		{
			int AmountLeft = priceOfWhore;
			if (targetPawn.Faction == whore.Faction || priceOfWhore == 0)
			{
				//--Log.Message("[RJW] xxx::PayPriceToWhore - No need to pay price");
				return AmountLeft;
			}
			Lord lord = targetPawn.GetLord();
			//Caravan guestCaravan = Find.WorldObjects.Caravans.Where(x => x.Spawned && x.ContainsPawn(targetPawn) && x.Faction == targetPawn.Faction && !x.IsPlayerControlled).FirstOrDefault();
			List<Pawn> caravanAnimals = targetPawn.Map.mapPawns.PawnsInFaction(targetPawn.Faction).Where(x => x.GetLord() == lord && x.inventory?.innerContainer != null && x.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver) > 0).ToList();

			IEnumerable<Thing> TraderSilvers;
			if (!caravanAnimals.Any())
			{
				TraderSilvers = targetPawn.inventory.innerContainer.Where(x => x.def == ThingDefOf.Silver);
				foreach (Thing silver in TraderSilvers)
				{
					if (AmountLeft <= 0)
						return AmountLeft;
					int dropAmount = silver.stackCount >= AmountLeft ? AmountLeft : silver.stackCount;
					if (targetPawn.inventory.innerContainer.TryDrop(silver, whore.Position, whore.Map, ThingPlaceMode.Near, dropAmount, out Thing resultingSilvers))
					{
						if (resultingSilvers is null)
						{
							//--Log.Message("[RJW] xxx::PayPriceToWhore - silvers is null0");
							return AmountLeft;
						}
						AmountLeft -= resultingSilvers.stackCount;
						if (AmountLeft <= 0)
						{
							return AmountLeft;
						}
					}
					else
					{
						//--Log.Message("[RJW] xxx::PayPriceToWhore - TryDrop failed0");
						return AmountLeft;
					}
				}
				return AmountLeft;
			}

			foreach (Pawn animal in caravanAnimals)
			{
				TraderSilvers = animal.inventory.innerContainer.Where(x => x.def == ThingDefOf.Silver);
				foreach (Thing silver in TraderSilvers)
				{
					if (AmountLeft <= 0)
						return AmountLeft;
					int dropAmount = silver.stackCount >= AmountLeft ? AmountLeft : silver.stackCount;
					if (animal.inventory.innerContainer.TryDrop(silver, whore.Position, whore.Map, ThingPlaceMode.Near, dropAmount, out Thing resultingSilvers))
					{
						if (resultingSilvers is null)
						{
							//--Log.Message("[RJW] xxx::PayPriceToWhore - silvers is null1");
							return AmountLeft;
						}
						AmountLeft -= resultingSilvers.stackCount;
						if (AmountLeft <= 0)
						{
							return AmountLeft;
						}
					}
				}
			}
			return AmountLeft;
		}

		[SyncMethod]
		public static bool IsHookupAppealing(Pawn target, Pawn whore)
		{
			if (PawnUtility.WillSoonHaveBasicNeed(target))
			{
				//Log.Message("IsHookupAppealing - fail: " + xxx.get_pawnname(target) + " has need to do");
				return false;
			}
			float num = target.relations.SecondaryRomanceChanceFactor(whore) / 1.5f;
			if (xxx.need_some_sex(target) > 2f)
			{
				num *= 3.0f;
			}
			else if (xxx.need_some_sex(target) > 1f)
			{
				num *= 2.0f;
			}
			if (xxx.is_zoophile(target) && !xxx.is_animal(whore))
			{
				num *= 0.5f;
			}
			if (xxx.AlienFrameworkIsActive)
			{
				if (xxx.is_xenophile(target))
				{
					if (target.def.defName == whore.def.defName)
						num *= 0.5f; // Same species, xenophile less interested.
					else
						num *= 1.5f; // Different species, xenophile more interested.
				}
				else if (xxx.is_xenophobe(target))
				{
					if (target.def.defName != whore.def.defName)
						num *= 0.25f; // Different species, xenophobe less interested.
				}
			}

			num *= 0.8f + ((float)whore.skills.GetSkill(SkillDefOf.Social).Level / 40); // 0.8 to 1.3
			num *= Mathf.InverseLerp(-100f, 0f, target.relations.OpinionOf(whore)); // 1 to 0 reduce score by negative opinion/relations to whore
			//Log.Message("IsHookupAppealing - score: " + num);
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return Rand.Range(0.05f, 1f) < num;
		}

		// Summary:
		//   Check if the pawn is willing to hook up. Checked for both target and the whore(when hooking colonists).
		[SyncMethod]
		public static bool WillPawnTryHookup(Pawn target)
		{
			if (target.story.traits.HasTrait(TraitDefOf.Asexual))
			{
				return false;
			}
			Pawn lover = LovePartnerRelationUtility.ExistingMostLikedLovePartner(target, false);
			if (lover == null)
			{
				return true;
			}
			float num = target.relations.OpinionOf(lover);
			float num2 = Mathf.InverseLerp(30f, -80f, num);

			if (xxx.is_prude(target))
			{
				num2 = 0f;
			}
			else if (xxx.is_lecher(target))
			{
				//Lechers are always up for it.
				num2 = Mathf.InverseLerp(100f, 50f, num);
			}
			else if (target.Map == lover.Map)
			{
				//Less likely to cheat if the lover is on the same map.
				num2 = Mathf.InverseLerp(70f, 15f, num);
			}
			//else default values

			if (xxx.need_some_sex(target) > 2)
			{
				num2 *= 1.8f;
			}
			else if (xxx.need_some_sex(target) > 1)
			{
				num2 *= 1.4f;
			}
			num2 /= 1.5f;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return Rand.Range(0f, 1f) < num2;
		}
	}
}


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;

		private int needsex_tick = needsex_tick_timer;
		public const 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 override bool ShowOnNeedList
		//{
		//	get
		//	{
		//		if (Genital_Helper.has_genitals(pawn))
		//			return true;

		//		Log.Message("[RJW]curLevelInt " + curLevelInt);
		//		return false;
		//	}
		//}

		//public override string GetTipString()
		//{
		//	return string.Concat(new string[]
		//	{
		//		this.LabelCap,
		//		": ",
		//		this.CurLevelPercentage.ToStringPercent(),
		//		"\n",
		//		this.def.description,
		//		"\n",
		//	});
		//}

		public static float druggedfactor(Pawn pawn)
		{
			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;
		}

		static float diseasefactor(Pawn pawn)
		{
			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("Boobitis")))
			{
				return 3f;
			}

			return 1f;
		}

		static float agefactor(Pawn pawn)
		{
			if (xxx.is_human(pawn))
			{
				int age = pawn.ageTracker.AgeBiologicalYears;

				Need_Sex horniness = pawn.needs.TryGetNeed<Need_Sex>();
				if (horniness.CurLevel > 0.5f)
					return 1f;

				if (age < RJWSettings.sex_minimum_age)
					return 0f;
			}
			return 1f;
		}

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

			if (needsex_tick <= 0)
			{
				std_updater.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) *
						diseasefactor(pawn) *
						agefactor(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 Verse;

namespace rjw
{
	/// <summary>
	/// Looks up and returns a BodyPartTagDef defined in the XML
	/// </summary>
	public static class BodyPartTagDefOf {
		public static BodyPartTagDef RJW_Fertility
		{
			get
			{
				if (a == null) a = (BodyPartTagDef)GenDefDatabase.GetDef(typeof(BodyPartTagDef), "RJW_Fertility");
				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
	{
		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_ovipositorF(pawn) || Genital_Helper.has_ovipositorM(pawn))
				return 0;

			//Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called for: " + xxx.get_pawnname(pawn));
			RaceProperties race = diffSet.pawn.RaceProps;

			if (!pawn.RaceHasFertility())
			{
				//Log.Message(" Fertility_filter, no fertility for : " + pawn.kindDef.defName);
				return 0f;
			}

			//androids only fertile with archotech parts
			if (AndroidsCompatibility.IsAndroid(pawn) && !(AndroidsCompatibility.AndroidPenisFertility(pawn) || AndroidsCompatibility.AndroidVaginaFertility(pawn)))
			{
				//Log.Message(" Android has no archotech genitals set fertility to 0 for: " + pawn.kindDef.defName);
				return 0f;
			}

			//archotech always fertile mode
			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("FertilityEnhancer")))
			{
				//Log.Message(" has archotech FertilityEnhancer set fertility to 100%");
				return 1f;
			}

			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.Name + " are: " + startAge + ", " + startMaxAge + ", " + endAge + ", " + endMaxAge);

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

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

		}

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


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

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


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

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


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

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

		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallPart : rjw_CORE_EXPOSED.Recipe_InstallOrReplaceBodyPart
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			GenderHelper.Sex before = GenderHelper.GetSex(pawn);

			base.ApplyOnPawn(pawn, part, billDoer, ingredients, bill);

			GenderHelper.Sex after = GenderHelper.GetSex(pawn);

			GenderHelper.ChangeSex(pawn, before, after);
		}
	}

	public class Recipe_InstallGenitals : Recipe_InstallPart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.genitals_blocked(p) || xxx.is_slime(p));//|| xxx.is_demon(p)
		}
	}

	public class Recipe_InstallBreasts : Recipe_InstallPart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.breasts_blocked(p) || xxx.is_slime(p));//|| xxx.is_demon(p)
		}
	}

	public class Recipe_InstallAnus : Recipe_InstallPart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.anus_blocked(p) || xxx.is_slime(p));//|| xxx.is_demon(p)
		}
	}
}


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

namespace rjw
{
	public class Recipe_GrowBreasts : Recipe_Surgery
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			if (billDoer != null)
			{
				if (CheckSurgeryFail(billDoer, pawn, ingredients, part, bill))
				{
					return;
				}
				TaleRecorder.RecordTale(TaleDefOf.DidSurgery, new object[]
				{
					billDoer,
					pawn
				});
			}

			var oldBoobs = pawn.health.hediffSet.hediffs.FirstOrDefault(hediff => hediff.def == bill.recipe.removesHediff);
			var newBoobs = bill.recipe.addsHediff;
			var newSize = BreastSize_Helper.GetSize(newBoobs);

			GenderHelper.ChangeSex(pawn, () =>
			{
				BreastSize_Helper.HurtBreasts(pawn, part, 3 * (newSize - 1));
				if (pawn.health.hediffSet.PartIsMissing(part))
				{
					return;
				}
				if (oldBoobs != null)
				{
					pawn.health.RemoveHediff(oldBoobs);
				}
				pawn.health.AddHediff(newBoobs, part);
			});
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipeDef)
		{
			yield break;
			var chest = Genital_Helper.get_breasts(pawn);

			if (pawn.health.hediffSet.PartIsMissing(chest)
				|| Genital_Helper.breasts_blocked(pawn))
			{
				yield break;
			}

			var old = recipeDef.removesHediff;
			if (old == null ? BreastSize_Helper.HasNipplesOnly(pawn, chest) : pawn.health.hediffSet.HasHediff(old, chest))
			{
				yield return chest;
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Removes heddifs (restraints/cocoon)
	/// </summary>
	public class Recipe_RemoveRestraints : Recipe_RemoveHediff
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			List<Hediff> allHediffs = pawn.health.hediffSet.hediffs;
			int i = 0;
			while (true)
			{
				if (i >= allHediffs.Count)
				{
					yield break;
				}
				if (allHediffs[i].def == recipe.removesHediff && allHediffs[i].Visible)
				{
					break;
				}
				i++;
			}

			yield return allHediffs[i].Part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_ShrinkBreasts : Recipe_Surgery
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			if (billDoer != null)
			{
				if (CheckSurgeryFail(billDoer, pawn, ingredients, part, bill))
				{
					return;
				}
				TaleRecorder.RecordTale(TaleDefOf.DidSurgery, new object[]
				{
					billDoer,
					pawn
				});
			}

			if (!BreastSize_Helper.TryGetBreastSize(pawn, out var oldSize))
			{
				throw new ApplicationException("Recipe_ShrinkBreasts could not find any breasts to shrink.");
			}

			var oldBoobs = pawn.health.hediffSet.GetFirstHediffOfDef(BreastSize_Helper.GetHediffDef(oldSize));
			var newSize = oldSize - 1;
			var newBoobs = BreastSize_Helper.GetHediffDef(newSize);

			// I can't figure out how to spawn a stack of 2 meat.
			for (var i = 0; i < 2; i++)
			{
				GenSpawn.Spawn(pawn.RaceProps.meatDef, billDoer.Position, billDoer.Map);
			}

			GenderHelper.ChangeSex(pawn, () =>
			{
				BreastSize_Helper.HurtBreasts(pawn, part, 5);
				if (pawn.health.hediffSet.PartIsMissing(part))
				{
					return;
				}
				pawn.health.RemoveHediff(oldBoobs);
				pawn.health.AddHediff(newBoobs, part);
			});
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipeDef)
		{
			yield break;
			var chest = Genital_Helper.get_breasts(pawn);

			if (Genital_Helper.breasts_blocked(pawn))
			{
				yield break;
			}

			if (BreastSize_Helper.TryGetBreastSize(pawn, out var size)
				//&& size > BreastSize_Helper.GetSize(Genital_Helper.flat_breasts))
				)
			{
				yield return chest;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveAnus : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_anus(p))
			{
				bool blocked = Genital_Helper.anus_blocked(p) || xxx.is_slime(p);//|| xxx.is_demon(p)

				foreach (BodyPartRecord part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (!blocked))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveBreasts : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_breasts(p))
			{
				bool blocked = Genital_Helper.breasts_blocked(p) || xxx.is_slime(p);//|| xxx.is_demon(p)

				foreach (BodyPartRecord part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (!blocked))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveGenitals : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_genitals(p))
			{
				bool blocked = Genital_Helper.genitals_blocked(p) || xxx.is_slime(p);//|| xxx.is_demon(p)

				foreach (BodyPartRecord part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (!blocked))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemovePart : rjw_CORE_EXPOSED.Recipe_RemoveBodyPart
	{
		// Quick and dirty method to guess whether the player is harvesting the genitals or amputating them
		// due to infection. The core code can't do this properly because it considers the private part
		// hediffs as "unclean".
		public override bool blocked(Pawn p)
		{
			return xxx.is_slime(p);//|| xxx.is_demon(p)
		}

		public bool is_harvest(Pawn p, BodyPartRecord part)
		{
			foreach (Hediff hed in p.health.hediffSet.hediffs)
			{
				if ((hed.Part?.def == part.def) && hed.def.isBad && (hed.Severity >= 0.70f))
					return false;
			}
			return true;
		}

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			foreach (BodyPartRecord part in base.GetPartsToApplyOn(p, r))
			{
				if (r.appliedOnFixedBodyParts.Contains(part.def) && !blocked(p))
					yield return part;
			}
		}

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

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

			if (har)
			{
				if (!p.Dead)
				{
					//Log.Message("alive harvest " + part);
					ThoughtUtility.GiveThoughtsForPawnOrganHarvested(p);
				}
				else
				{
					//Log.Message("dead harvest " + part);
					ThoughtUtility.GiveThoughtsForPawnExecuted(p, PawnExecutionKind.OrganHarvesting);
				}
			}
		}

		public override string GetLabelWhenUsedOn(Pawn p, BodyPartRecord part)
		{
			return recipe.label.CapitalizeFirst();
		}
	}
	/*
	public class Recipe_RemovePenis : Recipe_RemovePart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.genitals_blocked(p)
				|| !(Genital_Helper.has_penis(p)
					&& Genital_Helper.has_penis_infertile(p)
					&& Genital_Helper.has_ovipositorF(p)));
		}
	}

	public class Recipe_RemoveVagina : Recipe_RemovePart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.genitals_blocked(p)
				|| !Genital_Helper.has_vagina(p));
		}
	}

	public class Recipe_RemoveGenitals : Recipe_RemovePart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.genitals_blocked(p)
				|| !Genital_Helper.has_genitals(p));
		}
	}

	public class Recipe_RemoveBreasts : Recipe_RemovePart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.breasts_blocked(p)
				|| !Genital_Helper.has_breasts(p));
		}
	}

	public class Recipe_RemoveAnus : Recipe_RemovePart
	{
		public override bool blocked(Pawn p)
		{
			return (Genital_Helper.anus_blocked(p)
				|| !Genital_Helper.has_anus(p));
		}
	}
	*/
}


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

namespace rjw
{
	public class Recipe_RemovePenis : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_penis(p) || Genital_Helper.has_penis_infertile(p) || Genital_Helper.has_ovipositorF(p))
			{
				bool blocked = Genital_Helper.genitals_blocked(p) || xxx.is_slime(p);

				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (!blocked))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveVagina : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_vagina(p) )
			{
				bool blocked = Genital_Helper.genitals_blocked(p) || xxx.is_slime(p);

				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (!blocked))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_MakeFuta : rjw_CORE_EXPOSED.Recipe_AddBodyPart
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			GenderHelper.Sex before = GenderHelper.GetSex(pawn);

			base.ApplyOnPawn(pawn, part, billDoer, ingredients, bill);

			GenderHelper.Sex after = GenderHelper.GetSex(pawn);

			GenderHelper.ChangeSex(pawn, before, after);
		}
	}

	//Female to futa
	public class Recipe_MakeFutaF : Recipe_MakeFuta
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			bool blocked = Genital_Helper.genitals_blocked(p) || xxx.is_slime(p); //|| xxx.is_demon(p);
			bool has_vag = Genital_Helper.has_vagina(p);
			bool has_cock = Genital_Helper.has_penis(p) || Genital_Helper.has_penis_infertile(p) || Genital_Helper.has_ovipositorF(p);

			foreach (BodyPartRecord part in base.GetPartsToApplyOn(p, r))
				if (r.appliedOnFixedBodyParts.Contains(part.def) && !blocked && (has_vag && !has_cock))
					yield return part;
		}
	}

	//Male to futa
	public class Recipe_MakeFutaM : Recipe_MakeFuta
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			bool blocked = Genital_Helper.genitals_blocked(p) || xxx.is_slime(p); //|| xxx.is_demon(p);
			bool has_vag = Genital_Helper.has_vagina(p);
			bool has_cock = Genital_Helper.has_penis(p) || Genital_Helper.has_penis_infertile(p) || Genital_Helper.has_ovipositorF(p);

			foreach (BodyPartRecord part in base.GetPartsToApplyOn(p, r))
				if (r.appliedOnFixedBodyParts.Contains(part.def) && !blocked && (!has_vag && has_cock))
					yield return part;
		}
	}

	//TODO: maybe merge with above to make single recipe, but meh for now just copy-paste recipes or some filter to disable multiparts if normal part doesnt exist yet

	//add multi parts
	public class Recipe_AddMultiPart : Recipe_MakeFuta
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			//dont add artifical - peg, hydraulics, bionics, archo, ovi
			if (r.addsHediff.addedPartProps?.solid ?? false)
				yield break;

			//dont add if artifical parts present
			if (p.health.hediffSet.hediffs.Any((Hediff hed) =>
				(hed.Part != null) &&
				r.appliedOnFixedBodyParts.Contains(hed.Part.def) &&
				(hed.def.addedPartProps?.solid ?? false)))
				yield break;

			//dont add if no ovi
			//if (Genital_Helper.has_ovipositorF(p) || Genital_Helper.has_ovipositorM(p))
			//	yield break;

			//dont add if same part type not present yet
			if (!Genital_Helper.has_vagina(p) && r.defName.ToLower().Contains("vagina"))
				yield break;
			if (!Genital_Helper.has_penis(p) && r.defName.ToLower().Contains("penis"))
				yield break;

			//cant install parts when part blocked, on slimes, on demons
			bool blocked = (xxx.is_slime(p) //|| xxx.is_demon(p)
				|| (Genital_Helper.genitals_blocked(p) && r.appliedOnFixedBodyParts.Contains(xxx.genitalsDef))
				|| (Genital_Helper.anus_blocked(p) && r.appliedOnFixedBodyParts.Contains(xxx.anusDef))
				|| (Genital_Helper.breasts_blocked(p) && r.appliedOnFixedBodyParts.Contains(xxx.breastsDef)));

			foreach (BodyPartRecord part in base.GetPartsToApplyOn(p, r))
				if (r.appliedOnFixedBodyParts.Contains(part.def) && !blocked)
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class RJWDebugSettings : ModSettings
	{
		public static void DoWindowContents(Rect inRect)
		{
			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.ColumnWidth = inRect.width / 2.05f;
			listingStandard.Begin(inRect);
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("whoringtab_enabled".Translate(), ref RJWSettings.whoringtab_enabled);
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("submit_button_enabled".Translate(), ref RJWSettings.submit_button_enabled, "submit_button_enabled_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("RJW_designation_box".Translate(), ref RJWSettings.show_RJW_designation_box, "RJW_designation_box_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("designated_freewill".Translate(), ref RJWSettings.designated_freewill, "designated_freewill_desc".Translate());
				listingStandard.Gap(5f);
				if (listingStandard.ButtonText("Rjw Parts " + RJWSettings.ShowRjwParts))
				{
					Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
					{
					  new FloatMenuOption("Show", (() => RJWSettings.ShowRjwParts = RJWSettings.ShowParts.Show)),
					  //new FloatMenuOption("Known".Translate(), (() => RJWSettings.ShowRjwParts = RJWSettings.ShowParts.Known)),
					  new FloatMenuOption("Hide", (() => RJWSettings.ShowRjwParts = RJWSettings.ShowParts.Hide))
					}));
				}
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("StackRjwParts_name".Translate(), ref RJWSettings.StackRjwParts, "StackRjwParts_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.Label("maxDistancetowalk_name".Translate() + ": " + (RJWSettings.maxDistancetowalk), -1f, "maxDistancetowalk_desc".Translate());
				RJWSettings.maxDistancetowalk = listingStandard.Slider((int)RJWSettings.maxDistancetowalk, 0, 5000);
				listingStandard.Gap(30f);

			GUI.contentColor = Color.yellow;
			listingStandard.Label("YOU PATHETIC CHEATER ");
			GUI.contentColor = Color.white;
				listingStandard.CheckboxLabeled("override_RJW_designation_checks_name".Translate(), ref RJWSettings.override_RJW_designation_checks, "override_RJW_designation_checks_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Rapist".Translate(), ref RJWSettings.AddTrait_Rapist, "AddTrait_Rapist_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Masocist".Translate(), ref RJWSettings.AddTrait_Masocist, "AddTrait_Masocist_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Nymphomaniac".Translate(), ref RJWSettings.AddTrait_Nymphomaniac, "AddTrait_Nymphomaniac_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Necrophiliac".Translate(), ref RJWSettings.AddTrait_Necrophiliac, "AddTrait_Necrophiliac_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Nerves".Translate(), ref RJWSettings.AddTrait_Nerves, "AddTrait_Nerves_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("AddTrait_Zoophiliac".Translate(), ref RJWSettings.AddTrait_Zoophiliac, "AddTrait_Zoophiliac_desc".Translate());
				listingStandard.Gap(5f);

			listingStandard.NewColumn();
				listingStandard.Gap(4f);
				GUI.contentColor = Color.yellow;
				listingStandard.CheckboxLabeled("ForbidKidnap".Translate(), ref RJWSettings.ForbidKidnap, "ForbidKidnap_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("WildMode_name".Translate(), ref RJWSettings.WildMode, "WildMode_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("GenderlessAsFuta_name".Translate(), ref RJWSettings.GenderlessAsFuta, "GenderlessAsFuta_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("DevMode_name".Translate(), ref RJWSettings.DevMode, "DevMode_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("DebugLogJoinInBed".Translate(), ref RJWSettings.DebugLogJoinInBed, "DebugLogJoinInBed_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("DebugWhoring".Translate(), ref RJWSettings.DebugWhoring, "DebugWhoring_desc".Translate());
				listingStandard.Gap(5f);
				GUI.contentColor = Color.white;

			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref RJWSettings.whoringtab_enabled, "whoringtab_enabled");
			Scribe_Values.Look(ref RJWSettings.submit_button_enabled, "submit_button_enabled");
			Scribe_Values.Look(ref RJWSettings.show_RJW_designation_box, "show_RJW_designation_box");
			Scribe_Values.Look(ref RJWSettings.ShowRjwParts, "ShowRjwParts");
			Scribe_Values.Look(ref RJWSettings.StackRjwParts, "StackRjwParts");
			Scribe_Values.Look(ref RJWSettings.maxDistancetowalk, "maxDistancetowalk");

			Scribe_Values.Look(ref RJWSettings.AddTrait_Rapist, "AddTrait_Rapist");
			Scribe_Values.Look(ref RJWSettings.AddTrait_Masocist, "AddTrait_Masocist");
			Scribe_Values.Look(ref RJWSettings.AddTrait_Nymphomaniac, "AddTrait_Nymphomaniac");
			Scribe_Values.Look(ref RJWSettings.AddTrait_Necrophiliac, "AddTrait_Necrophiliac");
			Scribe_Values.Look(ref RJWSettings.AddTrait_Nerves, "AddTrait_Nerves");
			Scribe_Values.Look(ref RJWSettings.AddTrait_Zoophiliac, "AddTrait_Zoophiliac");

			Scribe_Values.Look(ref RJWSettings.ForbidKidnap, "ForbidKidnap", false, true);
			Scribe_Values.Look(ref RJWSettings.GenderlessAsFuta, "GenderlessAsFuta");
			Scribe_Values.Look(ref RJWSettings.WildMode, "Wildmode");
			Scribe_Values.Look(ref RJWSettings.override_RJW_designation_checks, "override_RJW_designation_checks");
			Scribe_Values.Look(ref RJWSettings.DevMode, "DevMode");
			Scribe_Values.Look(ref RJWSettings.DebugLogJoinInBed, "DebugLogJoinInBed");
			Scribe_Values.Look(ref RJWSettings.DebugWhoring, "DebugWhoring");
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw
{
	public class RJWHookupSettings : ModSettings
	{
		public static bool HookupsEnabled = true;
		public static bool NoHookupsDuringWorkHours = true;
		public static bool ColonistsCanHookup = true;
		public static bool ColonistsCanHookupWithVisitor = false;
		public static bool CanHookupWithPrisoner = false;
		public static bool VisitorsCanHookupWithColonists = false;
		public static bool VisitorsCanHookupWithVisitors = true;
		public static bool PrisonersCanHookupWithNonPrisoner = false;
		public static bool PrisonersCanHookupWithPrisoner = true;
		public static float HookupChanceForNonNymphos = 0.3f;
		public static float MinimumFuckabilityToHookup = 0.1f;
		public static float MinimumAttractivenessToHookup = 0.5f;
		public static float MinimumRelationshipToHookup = 20f;

		public static bool NymphosCanPickAnyone = true;
		public static bool NymphosCanCheat = true;
		public static bool NymphosCanHomewreck = true;
		public static bool NymphosCanHomewreckReverse = true;

		public static void DoWindowContents(Rect inRect)
		{
			MinimumFuckabilityToHookup = Mathf.Clamp(MinimumFuckabilityToHookup, 0.1f, 1f);
			MinimumAttractivenessToHookup = Mathf.Clamp(MinimumAttractivenessToHookup, 0.0f, 1f);
			MinimumRelationshipToHookup = Mathf.Clamp(MinimumRelationshipToHookup, -100, 100);

			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.ColumnWidth = inRect.width / 2.05f;
			listingStandard.Begin(inRect);
			listingStandard.Gap(4f);

			// Casual sex settings
			listingStandard.CheckboxLabeled("SettingHookupsEnabled".Translate(), ref HookupsEnabled, "SettingHookupsEnabled_desc".Translate());
			listingStandard.CheckboxLabeled("SettingNoHookupsDuringWorkHours".Translate(), ref NoHookupsDuringWorkHours, "SettingNoHookupsDuringWorkHours_desc".Translate());

			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("SettingColonistsCanHookup".Translate(), ref ColonistsCanHookup, "SettingColonistsCanHookup_desc".Translate());
			listingStandard.CheckboxLabeled("SettingColonistsCanHookupWithVisitor".Translate(), ref ColonistsCanHookupWithVisitor, "SettingColonistsCanHookupWithVisitor_desc".Translate());
			listingStandard.CheckboxLabeled("SettingVisitorsCanHookupWithColonists".Translate(), ref VisitorsCanHookupWithColonists, "SettingVisitorsCanHookupWithColonists_desc".Translate());
			listingStandard.CheckboxLabeled("SettingVisitorsCanHookupWithVisitors".Translate(), ref VisitorsCanHookupWithVisitors, "SettingVisitorsCanHookupWithVisitors_desc".Translate());

			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("SettingPrisonersCanHookupWithNonPrisoner".Translate(), ref PrisonersCanHookupWithNonPrisoner, "SettingPrisonersCanHookupWithNonPrisoner_desc".Translate());
			listingStandard.CheckboxLabeled("SettingPrisonersCanHookupWithPrisoner".Translate(), ref PrisonersCanHookupWithPrisoner, "SettingPrisonersCanHookupWithPrisoner_desc".Translate());
			listingStandard.CheckboxLabeled("SettingCanHookupWithPrisoner".Translate(), ref CanHookupWithPrisoner, "SettingCanHookupWithPrisoner_desc".Translate());

			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("SettingNymphosCanPickAnyone".Translate(), ref NymphosCanPickAnyone, "SettingNymphosCanPickAnyone_desc".Translate());
			listingStandard.CheckboxLabeled("SettingNymphosCanCheat".Translate(), ref NymphosCanCheat, "SettingNymphosCanCheat_desc".Translate());
			listingStandard.CheckboxLabeled("SettingNymphosCanHomewreck".Translate(), ref NymphosCanHomewreck, "SettingNymphosCanHomewreck_desc".Translate());
			listingStandard.CheckboxLabeled("SettingNymphosCanHomewreckReverse".Translate(), ref NymphosCanHomewreckReverse, "SettingNymphosCanHomewreckReverse_desc".Translate());

			listingStandard.Gap(10f);
			listingStandard.Label("SettingHookupChanceForNonNymphos".Translate() + ": " + (int)(HookupChanceForNonNymphos * 100) + "%", -1f, "SettingHookupChanceForNonNymphos_desc".Translate());
			HookupChanceForNonNymphos = listingStandard.Slider(HookupChanceForNonNymphos, 0.0f, 1.0f);
			listingStandard.Label("SettingMinimumFuckabilityToHookup".Translate() + ": " + (int)(MinimumFuckabilityToHookup * 100) + "%", -1f, "SettingMinimumFuckabilityToHookup_desc".Translate());
			MinimumFuckabilityToHookup = listingStandard.Slider(MinimumFuckabilityToHookup, 0.1f, 1.0f); // Minimum must be above 0.0 to avoid breaking SexAppraiser.would_fuck()'s hard-failure cases that return 0f
			listingStandard.Label("SettingMinimumAttractivenessToHookup".Translate() + ": " + (int)(MinimumAttractivenessToHookup * 100) + "%", -1f, "SettingMinimumAttractivenessToHookup_desc".Translate());
			MinimumAttractivenessToHookup = listingStandard.Slider(MinimumAttractivenessToHookup, 0.0f, 1.0f);
			listingStandard.Label("SettingMinimumRelationshipToHookup".Translate() + ": " + (MinimumRelationshipToHookup), -1f, "SettingMinimumRelationshipToHookup_desc".Translate());
			MinimumRelationshipToHookup = listingStandard.Slider((int)MinimumRelationshipToHookup, -100f, 100f);

			listingStandard.NewColumn();
			listingStandard.Gap(4f);

			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref HookupsEnabled, "SettingHookupsEnabled");
			Scribe_Values.Look(ref NoHookupsDuringWorkHours, "NoHookupsDuringWorkHours");
			Scribe_Values.Look(ref ColonistsCanHookup, "SettingColonistsCanHookup");
			Scribe_Values.Look(ref ColonistsCanHookupWithVisitor, "SettingColonistsCanHookupWithVisitor");
			Scribe_Values.Look(ref VisitorsCanHookupWithColonists, "SettingVisitorsCanHookupWithColonists");
			Scribe_Values.Look(ref VisitorsCanHookupWithVisitors, "SettingVisitorsCanHookupWithVisitors");

			// Prisoner settings
			Scribe_Values.Look(ref CanHookupWithPrisoner, "SettingCanHookupWithPrisoner");
			Scribe_Values.Look(ref PrisonersCanHookupWithNonPrisoner, "SettingPrisonersCanHookupWithNonPrisoner");
			Scribe_Values.Look(ref PrisonersCanHookupWithPrisoner, "SettingPrisonersCanHookupWithPrisoner");

			// Nympho settings
			Scribe_Values.Look(ref NymphosCanPickAnyone, "SettingNymphosCanPickAnyone");
			Scribe_Values.Look(ref NymphosCanCheat, "SettingNymphosCanCheat");
			Scribe_Values.Look(ref NymphosCanHomewreck, "SettingNymphosCanHomewreck");
			Scribe_Values.Look(ref NymphosCanHomewreckReverse, "SettingNymphosCanHomewreckReverse");

			Scribe_Values.Look(ref HookupChanceForNonNymphos, "SettingHookupChanceForNonNymphos");
			Scribe_Values.Look(ref MinimumFuckabilityToHookup, "SettingMinimumFuckabilityToHookup");
			Scribe_Values.Look(ref MinimumAttractivenessToHookup, "SettingMinimumAttractivenessToHookup");
			Scribe_Values.Look(ref MinimumRelationshipToHookup, "SettingMinimumRelationshipToHookup");
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw
{
	public class RJWPregnancySettings : ModSettings
	{
		public static bool humanlike_pregnancy_enabled = true;
		public static bool animal_pregnancy_enabled = true;
		public static bool bestial_pregnancy_enabled = true;
		public static bool insect_pregnancy_enabled = true;
		public static bool egg_pregnancy_implant_anyone = true;
		public static bool egg_pregnancy_fertilize_anyone = false;
		public static bool mechanoid_pregnancy_enabled = true;

		public static bool trait_filtering_enabled = true;
		public static bool use_parent_method = true;
		public static bool complex_interspecies = true;

		public static int animal_impregnation_chance = 25;
		public static int humanlike_impregnation_chance = 25;
		public static float interspecies_impregnation_modifier = 0.2f;
		public static float humanlike_DNA_from_mother = 0.5f;
		public static float bestial_DNA_from_mother = 1.0f;
		public static float bestiality_DNA_inheritance = 0.5f;
		public static float fertility_endage_male = 1.2f;
		public static float fertility_endage_female_humanlike = 0.58f;
		public static float fertility_endage_female_animal = 0.96f;

		private static Vector2 scrollPosition;
		private static float height_modifier = 100f;

		public static void DoWindowContents(Rect inRect)
		{

			//30f for top page description and bottom close button
			Rect outRect = new Rect(0f, 30f, inRect.width, inRect.height - 30f);
			//-16 for slider, height_modifier - additional height for options
			Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, inRect.height + height_modifier);

			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.maxOneColumn = true;
			listingStandard.ColumnWidth = viewRect.width / 2.05f;
			listingStandard.BeginScrollView(outRect, ref scrollPosition, ref viewRect);
			listingStandard.Begin(viewRect);
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("RJWH_pregnancy".Translate(), ref humanlike_pregnancy_enabled, "RJWH_pregnancy_desc".Translate());
			if (humanlike_pregnancy_enabled)
			{
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("  " + "genetic_trait_filter".Translate(), ref trait_filtering_enabled, "genetic_trait_filter_desc".Translate());
			}
			else
			{
				trait_filtering_enabled = false;
			}
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("RJWA_pregnancy".Translate(), ref animal_pregnancy_enabled, "RJWA_pregnancy_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("RJWB_pregnancy".Translate(), ref bestial_pregnancy_enabled, "RJWB_pregnancy_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("RJWI_pregnancy".Translate(), ref insect_pregnancy_enabled, "RJWI_pregnancy_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("egg_pregnancy_implant_anyone".Translate(), ref egg_pregnancy_implant_anyone, "egg_pregnancy_implant_anyone_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("egg_pregnancy_fertilize_anyone".Translate(), ref egg_pregnancy_fertilize_anyone, "egg_pregnancy_fertilize_anyone_desc".Translate());
			listingStandard.Gap(12f);

			listingStandard.CheckboxLabeled("UseParentMethod".Translate(), ref use_parent_method, "UseParentMethod_desc".Translate());
			listingStandard.Gap(5f);
			if (use_parent_method)
			{
				if (humanlike_DNA_from_mother == 0.0f)
				{
					listingStandard.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + "AlwaysFather".Translate(), -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}
				else if (humanlike_DNA_from_mother == 1.0f)
				{
					listingStandard.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + "AlwaysMother".Translate(), -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}
				else
				{
					int value = (int)(humanlike_DNA_from_mother * 100);
					listingStandard.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + value + "%", -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}

				if (bestial_DNA_from_mother == 0.0f)
				{
					listingStandard.Label("  " + "OffspringIsHuman".Translate() + ": " + "AlwaysFather".Translate(), -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}
				else if (bestial_DNA_from_mother == 1.0f)
				{
					listingStandard.Label("  " + "OffspringIsHuman".Translate() + ": " + "AlwaysMother".Translate(), -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}
				else
				{
					int value = (int)(bestial_DNA_from_mother * 100);
					listingStandard.Label("  " + "OffspringIsHuman".Translate() + ": " + value + "%", -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}

				if (bestiality_DNA_inheritance == 0.0f)
				{
					listingStandard.Label("  " + "OffspringIsHuman2".Translate() + ": " + "AlwaysBeast".Translate(), -1f, "OffspringIsHuman2_desc".Translate());
					bestiality_DNA_inheritance = listingStandard.Slider(bestiality_DNA_inheritance, 0.0f, 1.0f);
				}
				else if (bestiality_DNA_inheritance == 1.0f)
				{
					listingStandard.Label("  " + "OffspringIsHuman2".Translate() + ": " + "AlwaysHumanlike".Translate(), -1f, "OffspringIsHuman2_desc".Translate());
					bestiality_DNA_inheritance = listingStandard.Slider(bestiality_DNA_inheritance, 0.0f, 1.0f);
				}
				else
				{
					listingStandard.Label("  " + "OffspringIsHuman2".Translate() + ": <--->", -1f, "OffspringIsHuman2_desc".Translate());
					bestiality_DNA_inheritance = listingStandard.Slider(bestiality_DNA_inheritance, 0.0f, 1.0f);
				}
			}
			else
				humanlike_DNA_from_mother = 100;
			listingStandard.CheckboxLabeled("MechanoidImplanting".Translate(), ref mechanoid_pregnancy_enabled, "MechanoidImplanting_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("ComplexImpregnation".Translate(), ref complex_interspecies, "ComplexImpregnation_desc".Translate());
			listingStandard.Gap(10f);

			GUI.contentColor = Color.cyan;
			listingStandard.Label("Base pregnancy chances:");
			listingStandard.Gap(5f);
			if (humanlike_pregnancy_enabled)
				listingStandard.Label("  Humanlike/Humanlike (same race): " + humanlike_impregnation_chance + "%");
			else
				listingStandard.Label("  Humanlike/Humanlike (same race): -DISABLED-");
			if (humanlike_pregnancy_enabled && !(humanlike_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard.Label("  Humanlike/Humanlike (different race): " + Math.Round(humanlike_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard.Label("  Humanlike/Humanlike (different race): -DEPENDS ON SPECIES-");
			else
				listingStandard.Label("  Humanlike/Humanlike (different race): -DISABLED-");
			if (animal_pregnancy_enabled)
				listingStandard.Label("  Animal/Animal (same race): " + animal_impregnation_chance + "%");
			else
				listingStandard.Label("  Animal/Animal (same race): -DISABLED-");
			if (animal_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard.Label("  Animal/Animal (different race): " + Math.Round(animal_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard.Label("  Animal/Animal (different race): -DEPENDS ON SPECIES-");
			else
				listingStandard.Label("  Animal/Animal (different race): -DISABLED-");
			if (RJWSettings.bestiality_enabled && bestial_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard.Label("  Humanlike/Animal: " + Math.Round(animal_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard.Label("  Humanlike/Animal: -DEPENDS ON SPECIES-");
			else
				listingStandard.Label("  Humanlike/Animal: -DISABLED-");
			if (RJWSettings.bestiality_enabled && bestial_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard.Label("  Animal/Humanlike: " + Math.Round(humanlike_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard.Label("  Animal/Humanlike: -DEPENDS ON SPECIES-");
			else
				listingStandard.Label("  Animal/Humanlike: -DISABLED-");
			GUI.contentColor = Color.white;

			listingStandard.NewColumn();
			listingStandard.Gap(4f);
			listingStandard.Label("PregnantCoeffecientForHuman".Translate() + ": " + humanlike_impregnation_chance + "%", -1f, "PregnantCoeffecientForHuman_desc".Translate());
			humanlike_impregnation_chance = (int)listingStandard.Slider(humanlike_impregnation_chance, 0.0f, 100f);
			listingStandard.Label("PregnantCoeffecientForAnimals".Translate() + ": " + animal_impregnation_chance + "%", -1f, "PregnantCoeffecientForAnimals_desc".Translate());
			animal_impregnation_chance = (int)listingStandard.Slider(animal_impregnation_chance, 0.0f, 100f);
			if (!complex_interspecies)
			{
				switch (interspecies_impregnation_modifier)
				{
					case 0.0f:
						GUI.contentColor = Color.grey;
						listingStandard.Label("InterspeciesImpregnantionModifier".Translate() + ": " + "InterspeciesDisabled".Translate(), -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						GUI.contentColor = Color.white;
						break;
					case 1.0f:
						GUI.contentColor = Color.cyan;
						listingStandard.Label("InterspeciesImpregnantionModifier".Translate() + ": " + "InterspeciesMaximum".Translate(), -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						GUI.contentColor = Color.white;
						break;
					default:
						listingStandard.Label("InterspeciesImpregnantionModifier".Translate() + ": " + Math.Round(interspecies_impregnation_modifier * 100, 1) + "%", -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						break;
				}
				interspecies_impregnation_modifier = listingStandard.Slider(interspecies_impregnation_modifier, 0.0f, 1.0f);
			}
			listingStandard.Label("RJW_fertility_endAge_male".Translate() + ": " + (int)(fertility_endage_male * 80) + "In_human_years".Translate(), -1f, "RJW_fertility_endAge_male_desc".Translate());
			fertility_endage_male = listingStandard.Slider(fertility_endage_male, 0.1f, 3.0f);
			listingStandard.Label("RJW_fertility_endAge_female_humanlike".Translate() + ": " + (int)(fertility_endage_female_humanlike * 80) + "In_human_years".Translate(), -1f, "RJW_fertility_endAge_female_humanlike_desc".Translate());
			fertility_endage_female_humanlike = listingStandard.Slider(fertility_endage_female_humanlike, 0.1f, 3.0f);
			listingStandard.Label("RJW_fertility_endAge_female_animal".Translate() + ": " + (int)(fertility_endage_female_animal * 100) + "XofLifeExpectancy".Translate(), -1f, "RJW_fertility_endAge_female_animal_desc".Translate());
			fertility_endage_female_animal = listingStandard.Slider(fertility_endage_female_animal, 0.1f, 3.0f);

			listingStandard.EndScrollView(ref viewRect);
			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref humanlike_pregnancy_enabled, "humanlike_pregnancy_enabled");
			Scribe_Values.Look(ref animal_pregnancy_enabled, "animal_enabled");
			Scribe_Values.Look(ref bestial_pregnancy_enabled, "bestial_pregnancy_enabled");
			Scribe_Values.Look(ref insect_pregnancy_enabled, "insect_pregnancy_enabled");
			Scribe_Values.Look(ref egg_pregnancy_implant_anyone, "egg_pregnancy_implant_anyone");
			Scribe_Values.Look(ref egg_pregnancy_fertilize_anyone, "egg_pregnancy_fertilize_anyone");
			Scribe_Values.Look(ref mechanoid_pregnancy_enabled, "mechanoid_enabled");
			Scribe_Values.Look(ref trait_filtering_enabled, "trait_filtering_enabled");
			Scribe_Values.Look(ref use_parent_method, "use_parent_method");
			Scribe_Values.Look(ref humanlike_DNA_from_mother, "humanlike_DNA_from_mother");
			Scribe_Values.Look(ref bestial_DNA_from_mother, "bestial_DNA_from_mother");
			Scribe_Values.Look(ref bestiality_DNA_inheritance, "bestiality_DNA_inheritance");
			Scribe_Values.Look(ref humanlike_impregnation_chance, "humanlike_impregnation_chance");
			Scribe_Values.Look(ref animal_impregnation_chance, "animal_impregnation_chance");
			Scribe_Values.Look(ref interspecies_impregnation_modifier, "interspecies_impregnation_chance");
			Scribe_Values.Look(ref complex_interspecies, "complex_interspecies");
			Scribe_Values.Look(ref fertility_endage_male, "RJW_fertility_endAge_male");
			Scribe_Values.Look(ref fertility_endage_female_humanlike, "fertility_endage_female_humanlike");
			Scribe_Values.Look(ref fertility_endage_female_animal, "fertility_endage_female_animal");
		}
	}
}