code:
using RimWorld;
using Verse;
using System;
using System.Linq;
using System.Collections.Generic;
using Multiplayer.API;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Extensions;
using static rjw.Hediff_BasePregnancy;

///RimWorldChildren pregnancy:
//using RimWorldChildren;

namespace rjw
{
	///
	/// This handles pregnancy chosing between different types of pregnancy awailable to it
	/// 1a:RimWorldChildren pregnancy for humanlikes
	/// 1b:RJW pregnancy for humanlikes
	/// 2:RJW pregnancy for bestiality
	/// 3:RJW pregnancy for insects
	/// 4:RJW pregnancy for mechanoids
	///

	public static class PregnancyHelper
	{
		// TODO: Move this and all HediifDef.Named calls to a proper DefOf class
		private static HediffDef RJW_IUD = HediffDef.Named("RJW_IUD");

		//called by aftersex (including rape, breed, etc)
		//called by mcevent

		//pawn - "father"; partner = mother

		//TODO: this needs rewrite to account receiver group sex (props?)
		public static void impregnate(SexProps props)
		{

			if (RJWSettings.DevMode) ModLog.Message("Rimjobworld::impregnate(" + props.sexType + "):: " + xxx.get_pawnname(props.pawn) + " + " + xxx.get_pawnname(props.partner) + ":");

			//"mech" pregnancy
			if (props.sexType == xxx.rjwSextype.MechImplant)
			{
				if (RJWPregnancySettings.mechanoid_pregnancy_enabled && xxx.is_mechanoid(props.pawn))
				{
					// removing old pregnancies
					var p = GetPregnancies(props.partner);
					if (!p.NullOrEmpty())
					{
						var i = p.Count;
						while (i > 0)
						{
							i -= 1;
							var h = GetPregnancies(props.partner);
							if (h[i] is Hediff_MechanoidPregnancy)
							{
								if (RJWSettings.DevMode) ModLog.Message(" already pregnant by mechanoid");
							}
							else if (h[i] is Hediff_BasePregnancy)
							{
								if (RJWSettings.DevMode) ModLog.Message(" removing rjw normal pregnancy");
								(h[i] as Hediff_BasePregnancy).Kill();
							}
							else
							{
								if (RJWSettings.DevMode) ModLog.Message(" removing vanilla or other mod pregnancy");
								props.partner.health.RemoveHediff(h[i]);
							}
						}
					}

					// new pregnancy
					if (RJWSettings.DevMode) ModLog.Message(" mechanoid pregnancy started");
					Hediff_MechanoidPregnancy hediff = Hediff_BasePregnancy.Create<Hediff_MechanoidPregnancy>(props.partner, props.pawn);
					return;

					/*
					// Not an actual pregnancy. This implants mechanoid tech into the target.
					//may lead to pregnancy
					//old "chip pregnancies", maybe integrate them somehow?
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					HediffDef_MechImplants egg = (from x in DefDatabase<HediffDef_MechImplants>.AllDefs	select x).RandomElement();
					if (egg != null)
					{
						if (RJWSettings.DevMode) Log.Message(" planting MechImplants:" + egg.ToString());
						PlantSomething(egg, partner, !Genital_Helper.has_vagina(partner), 1);
						return;
					}
					else
					{
						if (RJWSettings.DevMode) Log.Message(" no mech implant found");
					}*/
				}
				return;
			}

			//"ovi" pregnancy/egglaying
			var AnalOk = props.sexType == xxx.rjwSextype.Anal && RJWPregnancySettings.insect_anal_pregnancy_enabled;
			var OralOk = props.sexType == xxx.rjwSextype.Oral && RJWPregnancySettings.insect_oral_pregnancy_enabled;
			// Sextype can result in pregnancy.
			if (!(props.sexType == xxx.rjwSextype.Vaginal || props.sexType == xxx.rjwSextype.DoublePenetration ||
				AnalOk || OralOk))
				return;

			Pawn giver = props.pawn; // orgasmer
			Pawn receiver = props.partner;
			List<Hediff> pawnparts = giver.GetGenitalsList();
			List<Hediff> partnerparts = receiver.GetGenitalsList();
			var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(props.dictionaryKey);

			//ModLog.Message(" RaceImplantEggs()" + pawn.RaceImplantEggs());
			//"insect" pregnancy
			if (CouldBeEgging(props, giver, receiver, pawnparts, partnerparts))
			{
				//TODO: add widget toggle for bind all/neutral/hostile pawns
				//Initiator/rapist puts victim in cocoon, maybe move it to aftersex?
				if (!props.isReceiver)
					if (CanCocoon(giver))
						if (giver.HostileTo(receiver) || receiver.IsPrisonerOfColony || receiver.health.hediffSet.HasHediff(xxx.submitting))
							if (!receiver.health.hediffSet.HasHediff(HediffDef.Named("RJW_Cocoon")))
							{
								receiver.health.AddHediff(HediffDef.Named("RJW_Cocoon"));
							}

				if (RJWSettings.DevMode) ModLog.Message(" 'egg' pregnancy checks");
				//see who penetrates who in interaction
				if (!props.isReceiver &&
					interaction.DominantHasTag(GenitalTag.CanPenetrate))
				{
					if (RJWSettings.DevMode) ModLog.Message(" impregnate(egg) - by initiator");
				}
				else if (props.isReceiver && props.isRevese &&
					interaction.SubmissiveHasTag(GenitalTag.CanPenetrate))
				{
					if (RJWSettings.DevMode) ModLog.Message(" impregnate(egg) - by receiver (reverse)");
				}
				else
				{
					if (RJWSettings.DevMode) ModLog.Message(" no valid interaction tags/family");
					return;
				}
				//implant/fertilise eggs
				DoEgg(props);
				return;
			}

			if (!(props.sexType == xxx.rjwSextype.Vaginal || props.sexType == xxx.rjwSextype.DoublePenetration))
				return;

			//"normal" and "beastial" pregnancy
			if (RJWSettings.DevMode) ModLog.Message(" 'normal' pregnancy checks");

			//interaction stuff if for handling futa/see who penetrates who in interaction
			if (!props.isReceiver &&
				interaction.DominantHasTag(GenitalTag.CanPenetrate) &&
				interaction.SubmissiveHasFamily(GenitalFamily.Vagina))
			{
				if (RJWSettings.DevMode) ModLog.Message(" impregnate - by initiator");
			}
			else if (props.isReceiver && props.isRevese &&
				interaction.DominantHasFamily(GenitalFamily.Vagina) &&
				interaction.SubmissiveHasTag(GenitalTag.CanPenetrate))
			{
				if (RJWSettings.DevMode) ModLog.Message(" impregnate - by receiver (reverse)");
			}
			else
			{
				if (RJWSettings.DevMode) ModLog.Message(" no valid interaction tags/family");
				return;
			}

			if (!Modules.Interactions.Helpers.PartHelper.FindParts(giver, GenitalTag.CanFertilize).Any())
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(giver) + " has no parts to Fertilize with");
				return;
			}
			if (!Modules.Interactions.Helpers.PartHelper.FindParts(receiver, GenitalTag.CanBeFertilized).Any())
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(receiver) + " has no parts to be Fertilized");
				return;
			}

			if (CanImpregnate(giver, receiver, props.sexType))
			{
				DoImpregnate(giver, receiver);
			}
		}

		private static bool CouldBeEgging(SexProps props, Pawn giver, Pawn reciever, List<Hediff> pawnparts, List<Hediff> partnerparts)
		{
			List<Hediff_InsectEgg> eggs = new();
			reciever.health.hediffSet.GetHediffs(ref eggs);
			//no ovipositor or fertilization possible
			if ((Genital_Helper.has_ovipositorF(giver, pawnparts) ||
				Genital_Helper.has_ovipositorM(giver, pawnparts) ||
				(Genital_Helper.has_penis_fertile(giver, pawnparts) && (giver.RaceImplantEggs() || eggs.Any()))
				) == false)
			{
				return false;
			}

			if ((props.sexType == xxx.rjwSextype.Vaginal || props.sexType == xxx.rjwSextype.DoublePenetration) &&
				Genital_Helper.has_vagina(reciever, partnerparts))
			{
				return true;
			}

			if ((props.sexType == xxx.rjwSextype.Anal || props.sexType == xxx.rjwSextype.DoublePenetration) &&
				Genital_Helper.has_anus(reciever) &&
				RJWPregnancySettings.insect_anal_pregnancy_enabled)
			{
				return true;
			}

			if (props.sexType == xxx.rjwSextype.Oral &&
				RJWPregnancySettings.insect_oral_pregnancy_enabled)
			{
				return true;
			}

			return false;
		}

		private static bool CanCocoon(Pawn pawn)
		{
			return xxx.is_insect(pawn);
		}

		///
		/// Gets the pregnancy that is likely to complete first, when there are multiple
		/// pregnancies, such as from egg implants.
		///
		/// <param name="pawn">The pawn to inspect.</param>
		/// <returns>The pregnancy likely to complete first.</returns>
		public static Hediff GetPregnancy(Pawn pawn)
		{
			var allPregnancies = GetPregnancies(pawn);
			if (allPregnancies.Count == 0) return null;

			allPregnancies.SortBy((preg) =>
			{
				(var ticksCompleted, var ticksToBirth) = GetProgressTicks(preg);
				return ticksToBirth - ticksCompleted;
			});
			return allPregnancies[0];
		}

		///
		/// Gets the gestation progress of a pregnancy.
		///
		/// <param name="hediff">The pregnancy hediff.</param>
		/// <returns>The current progress, a value between 0 and 1.</returns>
		public static float GetProgress(Hediff hediff) => hediff switch
		{
			Hediff_BasePregnancy rjwPreg => rjwPreg.GestationProgress,
			Hediff_Pregnant vanillaPreg => vanillaPreg.GestationProgress,
			_ => 0f
		};

		///
		/// <para>Gets gestation ticks completed and the total needed for birth.  Both
		/// of these values are approximate, as things like malnutrition can affect
		/// the gestation rate.</para>
		///
		/// <param name="hediff">The pregnancy hediff.</param>
		/// <returns>A tuple of ticks.</returns>
		public static (int ticksCompleted, int ticksToBirth) GetProgressTicks(Hediff hediff)
		{
			if (hediff is Hediff_BasePregnancy rjwPreg)
			{
				// RJW pregnancies maintain these numbers internally, so they don't
				// need to be inferred.
				var ticksToBirth = rjwPreg.p_end_tick - rjwPreg.p_start_tick;
				return (rjwPreg.ageTicks, Convert.ToInt32(ticksToBirth));
			}
			else
			{
				var progress = GetProgress(hediff);
				var raceProps = GetGestatingRace(hediff);
				var ticksToBirth = (int)(raceProps.gestationPeriodDays * GenDate.TicksPerDay);
				var ticksCompleted = (int)(progress * ticksToBirth);
				return (ticksCompleted, ticksToBirth);
			}
		}

		///
		/// <para>Gets the race-props that are providing the gestation rate of a pregnancy.</para>
		/// <para>This may or may not be accurate, depending on how the pregnancy is ultimately
		/// implemented.</para>
		///
		/// <param name="hediff">The pregnancy hediff.</param>
		/// <returns>The properties of the assumed gestating race.</returns>
		public static RaceProperties GetGestatingRace(Hediff hediff) => hediff switch
		{
			// Insect eggs use the implanter, if it is set.  Otherwise, the egg is probably
			// still inside the genetic mother, so we can use the carrier instead.
			Hediff_InsectEgg eggPreg when eggPreg.implanter is { } implanter =>
				implanter.RaceProps,
			// Some RJW pregnancies will store the gestating babies; the first pawn in
			// the list is treated as the gestation source.
			Hediff_BasePregnancy rjwPreg when rjwPreg.babies is { } babies && babies.Count > 0 =>
				babies[0].RaceProps,
			// For anything else, assume the carrying pawn is the source.
			_ => hediff.pawn.RaceProps
		};

		///
		/// Gets all pregnancies of the pawn as a list.  This may include both RJW
		/// and vanilla pregnancies.
		///
		/// <param name="pawn">The pawn to inspect.</param>
		/// <returns>A list of pregnancy hediffs.</returns>
		public static List<Hediff> GetPregnancies(Pawn pawn) =>
			pawn.health.hediffSet.hediffs
				.Where(x => x is Hediff_BasePregnancy or Hediff_Pregnant)
				.ToList();

		///Can get preg with above conditions, do impregnation.

		[SyncMethod]
		public static void DoEgg(SexProps props)
		{
			if (RJWPregnancySettings.insect_pregnancy_enabled)
			{
				if (RJWSettings.DevMode) ModLog.Message(" insect pregnancy");

				//female "insect" plant eggs
				//futa "insect" 50% plant eggs, 50% fertilize eggs
				if ((Genital_Helper.has_ovipositorF(props.pawn) && !Genital_Helper.has_penis_fertile(props.pawn)) ||
					(Rand.Value > 0.5f && Genital_Helper.has_ovipositorF(props.pawn)))
					//penis eggs someday?
					//(Rand.Value > 0.5f && (Genital_Helper.has_ovipositorF(pawn) || Genital_Helper.has_penis_fertile(pawn) && pawn.RaceImplantEggs())))
				{
					float maxeggssize = (props.partner.BodySize / 5) * (xxx.has_quirk(props.partner, "Incubator") ? 2f : 1f)
						* (Genital_Helper.has_ovipositorF(props.partner)
						? 2f * RJWPregnancySettings.egg_pregnancy_ovipositor_capacity_factor
						: 1f);
					float eggedsize = 0;

					// get implanter eggs
					List<Hediff_InsectEgg> eggs = new();
					props.partner.health.hediffSet.GetHediffs(ref eggs);

					// check if fertilised eggs/ non generic eggs
					foreach (Hediff_InsectEgg egg in eggs)
					{
						if (egg.father != null)
							eggedsize += egg.father.RaceProps.baseBodySize / 5 * (egg.def as HediffDef_InsectEgg).eggsize * RJWPregnancySettings.egg_pregnancy_eggs_size;
						else
							eggedsize += egg.implanter.RaceProps.baseBodySize / 5 * (egg.def as HediffDef_InsectEgg).eggsize * RJWPregnancySettings.egg_pregnancy_eggs_size;
					}
					if (RJWSettings.DevMode) ModLog.Message(" determine " + xxx.get_pawnname(props.partner) + " size of eggs inside: " + eggedsize + ", max: " + maxeggssize);

					BodyPartRecord targetBodyPart = Genital_Helper.get_genitalsBPR(props.pawn); //Get our genitals
					Predicate<Hediff> filterByGenitalia = hediff => hediff.Part == targetBodyPart; //make filter
					props.pawn.health.hediffSet.GetHediffs(ref eggs, filterByGenitalia); //pick only eggs from our genitals (not from anus or stomach)
					BodyPartRecord partnerGenitals = null;

					if (props.sexType == xxx.rjwSextype.Anal)
						partnerGenitals = Genital_Helper.get_anusBPR(props.partner);
					else if (props.sexType == xxx.rjwSextype.Oral)
						partnerGenitals = Genital_Helper.get_stomachBPR(props.partner);
					else if (props.sexType == xxx.rjwSextype.DoublePenetration && Rand.Value > 0.5f && RJWPregnancySettings.insect_anal_pregnancy_enabled)
						partnerGenitals = Genital_Helper.get_anusBPR(props.partner);
					else
						partnerGenitals = Genital_Helper.get_genitalsBPR(props.partner);

					while (eggs.Any() && eggedsize < maxeggssize)
					{
						if (props.sexType == xxx.rjwSextype.Vaginal)
						{
							// removing old pregnancies
							var p = GetPregnancies(props.partner);
							if (!p.NullOrEmpty())
							{
								var i = p.Count;
								while (i > 0)
								{
									i -= 1;
									var h = GetPregnancies(props.partner);
									if (h[i] is Hediff_MechanoidPregnancy)
									{
										if (RJWSettings.DevMode) ModLog.Message(" egging - pregnant by mechanoid, skip");
									}
									else if (h[i] is Hediff_BasePregnancy)
									{
										if (RJWSettings.DevMode) ModLog.Message(" egging - removing rjw normal pregnancy");
										(h[i] as Hediff_BasePregnancy).Kill();
									}
									else
									{
										if (RJWSettings.DevMode) ModLog.Message(" egging - removing vanilla or other mod pregnancy");
										props.partner.health.RemoveHediff(h[i]);
									}
								}
							}
						}

						var egg = eggs.First();
						eggs.Remove(egg);

						props.pawn.health.RemoveHediff(egg);
						props.partner.health.AddHediff(egg, partnerGenitals);

						egg.Implanter(props.pawn);

						eggedsize += egg.eggssize * (egg.def as HediffDef_InsectEgg).eggsize * RJWPregnancySettings.egg_pregnancy_eggs_size;
					}
				}
				//male or futa fertilize eggs
				else if (!props.pawn.health.hediffSet.HasHediff(xxx.sterilized))
				{
					if (Genital_Helper.has_penis_fertile(props.pawn))
						if ((Genital_Helper.has_ovipositorF(props.pawn) || Genital_Helper.has_ovipositorM(props.pawn)) || (props.pawn.health.capacities.GetLevel(xxx.reproduction) > 0))
						{
							List<Hediff_InsectEgg> eggs = new();
							props.partner.health.hediffSet.GetHediffs(ref eggs);
							if (!RJWPregnancySettings.egg_pregnancy_fertOrificeCheck_enabled)
							{
								foreach (var egg in eggs)
									egg.Fertilize(props.pawn);
							}
							else
							{
								//Gargulecode
								BodyPartRecord targetBodyPart;
								if (props.sexType == xxx.rjwSextype.Anal || props.sexType == xxx.rjwSextype.DoublePenetration)
									targetBodyPart = Genital_Helper.get_anusBPR(props.partner); //Anal? get anus
								else if (props.sexType == xxx.rjwSextype.Oral)
									targetBodyPart = Genital_Helper.get_stomachBPR(props.partner); //Oral? get stomach
								else
									targetBodyPart = Genital_Helper.get_genitalsBPR(props.partner); //nothing above? get genitals
								//End Gargulecode

								foreach (var egg in eggs.Where(x=> x.Part == targetBodyPart))
									egg.Fertilize(props.pawn);

								if (props.sexType == xxx.rjwSextype.DoublePenetration)
								{
									targetBodyPart = Genital_Helper.get_genitalsBPR(props.partner);

									foreach (var egg in eggs.Where(x => x.Part == targetBodyPart))
										egg.Fertilize(props.pawn);
								}
							}
						}
				}
				return;
			}
		}

		[SyncMethod]
		public static void DoImpregnate(Pawn pawn, Pawn partner)
		{
			if (RJWSettings.DevMode) ModLog.Message(" Doimpregnate " + xxx.get_pawnname(pawn) + " is a father, " + xxx.get_pawnname(partner) + " is a mother");

			if (AndroidsCompatibility.IsAndroid(pawn) && !AndroidsCompatibility.AndroidPenisFertility(pawn))
			{
				if (RJWSettings.DevMode) ModLog.Message(" Father is android with no arcotech penis, abort");
				return;
			}
			if (AndroidsCompatibility.IsAndroid(partner) && !AndroidsCompatibility.AndroidVaginaFertility(partner))
			{
				if (RJWSettings.DevMode) ModLog.Message(" Mother is android with no arcotech vagina, abort");
				return;
			}

			// fertility check
			float basePregnancyChance = RJWPregnancySettings.humanlike_impregnation_chance / 100f;
			if (xxx.is_animal(partner))
				basePregnancyChance = RJWPregnancySettings.animal_impregnation_chance / 100f;

			// Interspecies modifier
			if (pawn.def.defName != partner.def.defName)
			{
				if (RJWPregnancySettings.complex_interspecies)
					basePregnancyChance *= SexUtility.BodySimilarity(pawn, partner);
				else
					basePregnancyChance *= RJWPregnancySettings.interspecies_impregnation_modifier;
			}
			else
			{
				//Egg fertility check
				CompEggLayer compEggLayer = partner.TryGetComp<CompEggLayer>();
				if (compEggLayer != null)
					basePregnancyChance = 1.0f;
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float fertility = Math.Min(pawn.health.capacities.GetLevel(xxx.reproduction), partner.health.capacities.GetLevel(xxx.reproduction));

			if (partner.health.hediffSet.HasHediff(RJW_IUD))
			{
				fertility /= 99f;
			}

			float pregnancyChance = basePregnancyChance * fertility;

			if (!Rand.Chance(pregnancyChance))
			{
				if (RJWSettings.DevMode) ModLog.Message(" Impregnation failed. Chance: " + pregnancyChance.ToStringPercent());
				return;
			}
			if (RJWSettings.DevMode) ModLog.Message(" Impregnation succeeded. Chance: " + pregnancyChance.ToStringPercent());

			AddPregnancyHediff(partner, pawn);
		}

		///For checking normal pregnancy, should not for egg implantion or such.
		public static bool CanImpregnate(Pawn fucker, Pawn fucked, xxx.rjwSextype sexType = xxx.rjwSextype.Vaginal)
		{
			if (fucker == null || fucked == null) return false;

			if (RJWSettings.DevMode) ModLog.Message("Rimjobworld::CanImpregnate checks (" + sexType + "):: " + xxx.get_pawnname(fucker) + " + " + xxx.get_pawnname(fucked) + ":");

			if (sexType == xxx.rjwSextype.MechImplant && !RJWPregnancySettings.mechanoid_pregnancy_enabled)
			{
				if (RJWSettings.DevMode) ModLog.Message(" mechanoid 'pregnancy' disabled");
				return false;
			}
			if (!(sexType == xxx.rjwSextype.Vaginal || sexType == xxx.rjwSextype.DoublePenetration))
			{
				if (RJWSettings.DevMode) ModLog.Message(" sextype cannot result in pregnancy");
				return false;
			}

			if (AndroidsCompatibility.IsAndroid(fucker) && AndroidsCompatibility.IsAndroid(fucked))
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(fucked) + " androids cant breed/reproduce androids");
				return false;
			}

			if ((fucker.IsUnsexyRobot() || fucked.IsUnsexyRobot()) && !(sexType == xxx.rjwSextype.MechImplant))
			{
				if (RJWSettings.DevMode) ModLog.Message(" unsexy robot cant be pregnant");
				return false;
			}

			if (!fucker.RaceHasPregnancy())
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(fucked) + " filtered race that cant be pregnant");
				return false;
			}

			if (!fucked.RaceHasPregnancy())
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(fucker) + " filtered race that cant impregnate");
				return false;
			}

			if (fucked.IsPregnant())
			{
				if (RJWSettings.DevMode) ModLog.Message(" already pregnant.");
				return false;
			}

			List<Hediff_InsectEgg> eggs = new();
			fucked.health.hediffSet.GetHediffs(ref eggs);
			if ((from x in eggs where x.def == DefDatabase<HediffDef_InsectEgg>.GetNamed(x.def.defName) select x).Any())
			{
				if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(fucked) + " cant get pregnant while eggs inside");
				return false;
			}

			var pawnparts = fucker.GetGenitalsList();
			var partnerparts = fucked.GetGenitalsList();
			if (!(Genital_Helper.has_penis_fertile(fucker, pawnparts) && Genital_Helper.has_vagina(fucked, partnerparts)) && !(Genital_Helper.has_penis_fertile(fucked, partnerparts) && Genital_Helper.has_vagina(fucker, pawnparts)))
			{
				if (RJWSettings.DevMode) ModLog.Message(" missing genitals for impregnation");
				return false;
			}

			if (fucker.health.capacities.GetLevel(xxx.reproduction) <= 0 || fucked.health.capacities.GetLevel(xxx.reproduction) <= 0)
			{
				if (RJWSettings.DevMode) ModLog.Message(" one (or both) pawn(s) infertile");
				return false;
			}

			if (xxx.is_human(fucked) && xxx.is_human(fucker) && (RJWPregnancySettings.humanlike_impregnation_chance == 0 || !RJWPregnancySettings.humanlike_pregnancy_enabled))
			{
				if (RJWSettings.DevMode) ModLog.Message(" human pregnancy chance set to 0% or pregnancy disabled.");
				return false;
			}
			else if (((xxx.is_animal(fucker) && xxx.is_human(fucked)) || (xxx.is_human(fucker) && xxx.is_animal(fucked))) && !RJWPregnancySettings.bestial_pregnancy_enabled)
			{
				if (RJWSettings.DevMode) ModLog.Message(" bestiality pregnancy chance set to 0% or pregnancy disabled.");
				return false;
			}
			else if (xxx.is_animal(fucked) && xxx.is_animal(fucker) && (RJWPregnancySettings.animal_impregnation_chance == 0 || !RJWPregnancySettings.animal_pregnancy_enabled))
			{
				if (RJWSettings.DevMode) ModLog.Message(" animal-animal pregnancy chance set to 0% or pregnancy disabled.");
				return false;
			}
			else if (fucker.def.defName != fucked.def.defName && (RJWPregnancySettings.interspecies_impregnation_modifier <= 0.0f && !RJWPregnancySettings.complex_interspecies))
			{
				if (RJWSettings.DevMode) ModLog.Message(" interspecies pregnancy disabled.");
				return false;
			}

			if (!(fucked.RaceProps.gestationPeriodDays > 0))
			{
				CompEggLayer compEggLayer = fucked.TryGetComp<CompEggLayer>();
				if (compEggLayer == null)
				{
					if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(fucked) + " mother.RaceProps.gestationPeriodDays is " + fucked.RaceProps.gestationPeriodDays + " cant impregnate");
					return false;
				}
			}

			return true;
		}

		//Plant babies for human/bestiality pregnancy
		public static void AddPregnancyHediff(Pawn mother, Pawn father)
		{
			//human-human
			if (RJWPregnancySettings.humanlike_pregnancy_enabled && xxx.is_human(mother) && xxx.is_human(father))
			{
				CompEggLayer compEggLayer = mother.TryGetComp<CompEggLayer>();
				if (compEggLayer != null)
				{
					// fertilize eggs of humanlikes ?!
					if (!compEggLayer.FullyFertilized)
					{
						compEggLayer.Fertilize(father);
						//if (!mother.kindDef.defName.Contains("Chicken"))
						//	if (compEggLayer.Props.eggFertilizedDef.defName.Contains("RJW"))
					}
				}
				else
				{
					if (RJWPregnancySettings.UseVanillaPregnancy)
					{
						// vanilla 1.4 human pregnancy hediff
						ModLog.Message("preg hediffdefof PregnantHuman " + HediffDefOf.PregnantHuman);
						StartVanillaPregnancy(mother, father);
					}
					else
					{
						// use RJW hediff
						Hediff_BasePregnancy.Create<Hediff_HumanlikePregnancy>(mother, father);
					}
				}
			}
			//human-animal
			//maybe make separate option for human males vs female animals???
			else if (RJWPregnancySettings.bestial_pregnancy_enabled && (xxx.is_human(mother) ^ xxx.is_human(father)))
			{
				CompEggLayer compEggLayer = mother.TryGetComp<CompEggLayer>();
				if (compEggLayer != null)
				{
					if (!compEggLayer.FullyFertilized)
						compEggLayer.Fertilize(father);
				}
				else
				{
					DnaGivingParent dnaGivingParent = Hediff_BasePregnancy.SelectDnaGivingParent(mother, father);
					if (RJWPregnancySettings.UseVanillaPregnancy && xxx.is_human(mother) && dnaGivingParent == DnaGivingParent.Mother)
					{
						// vanilla 1.4 human pregnancy hediff
						ModLog.Message("preg hediffdefof PregnantHuman " + HediffDefOf.PregnantHuman);
						StartVanillaPregnancy(mother, father);
					}
					else
					{
						Hediff_BasePregnancy.Create<Hediff_BestialPregnancy>(mother, father, dnaGivingParent);
					}
				}
			}
			//animal-animal
			else if (xxx.is_animal(mother) && xxx.is_animal(father))
			{
				CompEggLayer compEggLayer = mother.TryGetComp<CompEggLayer>();
				if (compEggLayer != null)
				{
					// fertilize eggs of same species
					if (!compEggLayer.FullyFertilized)
						if (mother.kindDef == father.kindDef)
							compEggLayer.Fertilize(father);
				}
				else if (RJWPregnancySettings.animal_pregnancy_enabled)
				{
					Hediff_BasePregnancy.Create<Hediff_BestialPregnancy>(mother, father);
				}
			}
		}

		public static void StartVanillaPregnancy(Pawn mother, Pawn father, Pawn geneticMother = null, HediffDef def = null)
		{
			def ??= HediffDefOf.PregnantHuman;
			Hediff_Pregnant pregnancy = (Hediff_Pregnant) HediffMaker.MakeHediff(def, mother);
			pregnancy.SetParents(geneticMother, father, PregnancyUtility.GetInheritedGeneSet(father, mother));
			mother.health.AddHediff(pregnancy);
		}

		//Plant Insect eggs/mech chips/other preg mod hediff?
		public static bool PlantSomething(HediffDef def, Pawn target, bool isToAnal = false, int amount = 1)
		{
			if (def == null)
				return false;
			if (!isToAnal && !Genital_Helper.has_vagina(target))
				return false;
			if (isToAnal && !Genital_Helper.has_anus(target))
				return false;

			BodyPartRecord Part = (isToAnal) ? Genital_Helper.get_anusBPR(target) : Genital_Helper.get_genitalsBPR(target);
			if (Part != null || Part.parts.Count != 0)
			{
				//killoff normal preg
				if (!isToAnal)
				{
					if (RJWSettings.DevMode) ModLog.Message(" removing other pregnancies");
					var p = GetPregnancies(target);
					if (!p.NullOrEmpty())
					{
						foreach (var x in p)
						{
							if (x is Hediff_BasePregnancy)
							{
								var preg = x as Hediff_BasePregnancy;
								preg.Kill();
							}
							else
							{
								target.health.RemoveHediff(x);
							}
						}
					}
				}

				for (int i = 0; i < amount; i++)
				{

					if (RJWSettings.DevMode) ModLog.Message(" planting something weird");
					target.health.AddHediff(def, Part);
				}

				return true;
			}
			return false;
		}

		///
		/// Remove CnP Pregnancy, that is added without passing rjw checks
		///
		public static void cleanup_CnP(Pawn pawn)
		{
			//They do subpar probability checks and disrespect our settings, but I fail to just prevent their doloving override.
			//probably needs harmonypatch
			//So I remove the hediff if it is created and recreate it if needed in our handler later

			if (RJWSettings.DevMode) ModLog.Message(" cleanup_CnP after love check");

			var h = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("HumanPregnancy"));
			if (h != null && h.ageTicks < 100)
			{
				pawn.health.RemoveHediff(h);
				if (RJWSettings.DevMode) ModLog.Message(" removed hediff from " + xxx.get_pawnname(pawn));
			}
		}

		///
		/// Remove Vanilla Pregnancy
		///
		public static void cleanup_vanilla(Pawn pawn)
		{
			if (RJWSettings.DevMode) ModLog.Message(" cleanup_vanilla after love check");

			var h = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.Pregnant);
			if (h != null && h.ageTicks < 100)
			{
				pawn.health.RemoveHediff(h);
				if (RJWSettings.DevMode) ModLog.Message(" removed hediff from " + xxx.get_pawnname(pawn));
			}
		}

		///
		/// Below is stuff for RimWorldChildren
		/// its not used, we rely only on our own pregnancies
		///

		///
		/// This function tries to call Children and pregnancy utilities to see if that mod could handle the pregnancy
		///
		/// <returns>true if cnp pregnancy will work, false if rjw one should be used instead</returns>
		public static bool CnP_WillAccept(Pawn mother)
		{
			//if (!xxx.RimWorldChildrenIsActive)
				return false;

			//return RimWorldChildren.ChildrenUtility.RaceUsesChildren(mother);
		}

		///
		/// This funtcion tries to call Children and Pregnancy to create humanlike pregnancy implemented by the said mod.
		///
		public static void CnP_DoPreg(Pawn mother, Pawn father)
		{
			if (!xxx.RimWorldChildrenIsActive)
				return;

			//RimWorldChildren.Hediff_HumanPregnancy.Create(mother, father);
		}

	}
}


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

namespace rjw
{
	public class Recipe_Abortion : Recipe_RemoveHediff
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null)
			{
				bool isMatch = Hediff_BasePregnancy.KnownPregnancies() // For every known pregnancy
					.Where(x => pawn.health.hediffSet.HasHediff(HediffDef.Named(x), true) && recipe.removesHediff == HediffDef.Named(x)) // Find matching bodyparts
					.Select(x => (Hediff_BasePregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named(x))) // get pregnancy hediff
					.Where(pregnancy => pregnancy.is_discovered &&
						(!pregnancy.is_checked || !(pregnancy is Hediff_MechanoidPregnancy))) // Find visible pregnancies or unchecked mech
					.Any(); // return true if found something
				if (isMatch)
				{
					yield return part;
				}
			}
		}
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			foreach (Hediff x in pawn.health.hediffSet.hediffs.Where(x => x is Hediff_MechanoidPregnancy))
			{
				(x as Hediff_MechanoidPregnancy).GiveBirth();
				return;
			}
			base.ApplyOnPawn(pawn, part, billDoer, ingredients, bill);
		}
	}
	public class Recipe_PregnancyAbortMech : Recipe_Abortion
	{

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null)
			{
				bool isMatch = Hediff_BasePregnancy.KnownPregnancies() // For every known pregnancy
					.Where(x => pawn.health.hediffSet.HasHediff(HediffDef.Named(x), true) && recipe.removesHediff == HediffDef.Named(x)) // Find matching bodyparts
					.Select(x => (Hediff_BasePregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named(x))) // get pregnancy hediff
					.Where(pregnancy => pregnancy.is_checked && pregnancy is Hediff_MechanoidPregnancy) // Find checked/visible pregnancies
					.Any(); // return true if found something
				if (isMatch)
				{
					yield return part;
				}
			}
		}
	}
}

repo_name: aelina/rjw

license: mit


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

namespace rjw
{
	public class Recipe_ClaimChild :  RecipeWorker
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			if (pawn != null)//I have no idea how it works but Recipe_ShutDown : RecipeWorker does not return values when not applicable
			{
				//Log.Message("RJW Claim child check on " + pawn);
				if (xxx.is_human(pawn) && !pawn.IsColonist)
				{
					if ( (pawn.ageTracker.CurLifeStageIndex < 2))//Guess it is hardcoded for now to `baby` and `toddler` of standard 4 stages of human life
					{
						BodyPartRecord brain = pawn.health.hediffSet.GetBrain();
						if (brain != null)
						{
							//Log.Message("RJW Claim child is applicable");
							yield return brain;
						}
					}
				}
			}
		}

		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			if (pawn == null)
			{
				ModLog.Error("Error applying medical recipe, pawn is null");
				return;
			}
			pawn.SetFaction(Faction.OfPlayer);
			//we could do
			//pawn.SetFaction(billDoer.Faction);
			//but that is useless because GetPartsToApplyOn does not support factions anyway and all recipes are hardcoded to player.
		}
	}
}


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

namespace rjw
{
	public class Recipe_DeterminePregnancy : RecipeWorker
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			/* Males can be impregnated by mechanoids, probably
			if (!xxx.is_female(pawn))
			{
				yield break;
			}
			*/
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null && (pawn.ageTracker.CurLifeStage.reproductive)
				|| pawn.IsPregnant(true))
			{
				yield return part;
			}
		}

		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			var p = PregnancyHelper.GetPregnancies(pawn);
			if (p.NullOrEmpty())
			{
				Messages.Message(xxx.get_pawnname(billDoer) + " has determined " + xxx.get_pawnname(pawn) + " is not pregnant.", MessageTypeDefOf.NeutralEvent);
				return;
			}

			foreach (var x in p)
			{
				if (x is Hediff_BasePregnancy)
				{
					var preg = x as Hediff_BasePregnancy;
					preg.CheckPregnancy();
				}
			}
		}
	}
	public class Recipe_DeterminePaternity : RecipeWorker
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null && pawn.IsPregnant(true))
			{
				yield return part;
			}
		}

		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			var p = PregnancyHelper.GetPregnancies(pawn);
			if (p.NullOrEmpty())
			{
				Messages.Message(xxx.get_pawnname(billDoer) + " has determined " + xxx.get_pawnname(pawn) + " is not pregnant.", MessageTypeDefOf.NeutralEvent);
				return;
			}

			foreach (var x in p)
			{
				if (x is Hediff_BasePregnancy)
				{
					var preg = x as Hediff_BasePregnancy;
					Messages.Message(xxx.get_pawnname(billDoer) + " has determined " + xxx.get_pawnname(pawn) + " is pregnant and " + preg.father + " is the father.", MessageTypeDefOf.NeutralEvent);

					preg.CheckPregnancy();
					preg.is_parent_known = true;
				}
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// IUD - prevent pregnancy
	/// </summary>
	public class Recipe_InstallIUD : Recipe_InstallImplantToExistParts
	{
		public override bool AvailableOnNow(Thing thing, BodyPartRecord part = null)
		{
			if (RJWPregnancySettings.UseVanillaPregnancy)
			{
				return false;
			}
			return base.AvailableOnNow(thing, part);
		}

		// Let's comment it out. What's the worst that could happen?
		// public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		// {
		// 	if (!xxx.is_female(pawn))
		// 	{
		// 		return Enumerable.Empty<BodyPartRecord>();
		// 	}
		// 	return base.GetPartsToApplyOn(pawn, recipe);
		// }
	}
}


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

namespace rjw
{
	public class Recipe_PregnancyHackMech : RecipeWorker
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null && pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech"), part, true))
			{
				Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
				//Log.Message("RJW_pregnancy_mech hack check: " + pregnancy.is_checked);
				if (pregnancy.is_checked && !pregnancy.is_hacked)
					yield return part;
			}
		}

		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{

			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech")))
			{
				Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
				pregnancy.Hack();
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Sterilization
	/// </summary>
	public class Recipe_InstallImplantToExistParts : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			bool blocked = Genital_Helper.genitals_blocked(pawn) || xxx.is_slime(pawn);

			if (!blocked)
				foreach (BodyPartRecord record in pawn.RaceProps.body.AllParts.Where(x => recipe.appliedOnFixedBodyParts.Contains(x.def)))
				{
					if (!pawn.health.hediffSet.hediffs.Any((Hediff x) => x.def == recipe.addsHediff))
					{
						yield return record;
					}
				}
		}
	}
}


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

namespace rjw.Modules.Quirks
{
	public interface IQuirkService
	{
		bool HasQuirk(Pawn pawn, string quirk);
	}
}


code:
using rjw.Modules.Quirks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Quirks.Implementation
{
	public class QuirkService : IQuirkService
	{
		public static IQuirkService Instance { get; private set; }

		static QuirkService()
		{
			Instance = new QuirkService();
		}

		private QuirkService() { }

		public bool HasQuirk(Pawn pawn, string quirk)
		{
			//No paw ! hum ... I meant pawn !
			if (pawn == null)
			{
				return false;
			}

			//No quirk !
			if (string.IsNullOrWhiteSpace(quirk))
			{
				return false;
			}

			string loweredQuirk = quirk.ToLower();

			string pawnQuirks = CompRJW.Comp(pawn).quirks
				.ToString()
				.ToLower();

			return pawnQuirks.Contains(loweredQuirk);
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Quirks
{
	public static class Quirks
	{
		public const string Podophile = "Podophile";
		public const string Impregnation = "Impregnation fetish";
	}
}


code:
using Verse;

namespace rjw.Modules.Rand
{
	/// <summary>
	/// <para>When used with a `using` statement, creates a seeded RNG context.</para>
	/// <para>Once the block is exited, the original RNG state is restored.</para>
	/// <example>
	/// <para>How to create a seeded context:</para>
	/// <code>
	/// // Pull in the `Seeded` type at the top of the C# file.
	/// using Seeded = rjw.Modules.Rand.Seeded;
	///
	/// string RandomYepNah(Pawn pawn)
	/// {
	///   // Put it in a `using` with a new seed or a good seed source (like this
	///   // pawn here), and then do your random stuff inside.  There's no need to
	///   // bind it to a variable.
	///   using (Seeded.With(pawn))
	///   {
	///     var someChance = Verse.Rand.Chance(0.5f);
	///     return someChance ? "Yep!" : "Nah...";
	///   }
	/// }
	/// </code>
	/// <para>When the block is exited, the ref-struct is disposed, which pops the
	/// RNG state for you.  It even does this if there's an exception thrown in the
	/// using block, so the RNG state won't be borked.</para>
	/// <para>It should be safe to layer multiple RNG contexts.  They should pop
	/// in the correct order.</para>
	/// </example>
	/// </summary>
	public readonly ref struct Seeded
	{
		public readonly int seed;

		private Seeded(int replacementSeed)
		{
			seed = replacementSeed;
			Verse.Rand.PushState(replacementSeed);
		}

		public void Dispose()
		{
			Verse.Rand.PopState();
		}

		/// <summary>
		/// <para>When used with a `using` statement, creates an isolated RNG context.</para>
		/// <para>Once the block is exited, the original RNG state is restored.</para>
		/// <para>This version will use the given seed.</para>
		/// </summary>
		/// <param name="replacementSeed">The seed to use.</param>
		/// <returns>An RNG context.</returns>
		public static Seeded With(int replacementSeed) =>
			new(replacementSeed);

		/// <summary>
		/// <para>When used with a `using` statement, creates an isolated RNG context.</para>
		/// <para>Once the block is exited, the original RNG state is restored.</para>
		/// <para>This version will use a seed based on the thing's ID and so the
		/// randomness will be reproducible for that thing each time the block is
		/// entered.  As long as you always do the same operations, it will always
		/// produce the same RNG.</para>
		/// </summary>
		/// <param name="thing">The thing to use as a seed source.</param>
		/// <returns>An RNG context.</returns>
		public static Seeded With(Thing thing) =>
			new(thing.HashOffset());

		/// <summary>
		/// <para>When used with a `using` statement, creates an isolated RNG context.</para>
		/// <para>Once the block is exited, the original RNG state is restored.</para>
		/// <para>This is similar to <see cref="With"/>, but it will use a special seed
		/// associated with the given thing that will be stable for the current in-game
		/// hour.</para>
		/// <para>You can get the seed from the context's struct, if needed.  Just
		/// assign the context to a variable.</para>
		/// </summary>
		/// <param name="thing">The thing to use as a seed source.</param>
		/// <param name="salt">Salt that can be added to munge the seed further.</param>
		/// <returns>An RNG context.</returns>
		public static Seeded ForHour(Thing thing, int salt = 42) =>
			new(Verse.Rand.RandSeedForHour(thing, salt));

		/// <summary>
		/// <para>When used with a `using` statement, creates an isolated RNG context.</para>
		/// <para>Once the block is exited, the original RNG state is restored.</para>
		/// <para>This is similar to <see cref="With"/>, but it will use a random seed
		/// based on the last RNG state.  This should be stable for multiplayer.</para>
		/// <para>You can get the seed from the context's struct, if needed.  Just
		/// assign the context to a variable.</para>
		/// </summary>
		/// <returns>An RNG context.</returns>
		public static Seeded Randomly() =>
			new(Verse.Rand.Int);
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared.Comparers
{
	public class StringComparer_IgnoreCase : IEqualityComparer<string>
	{
		public bool Equals(string x, string y)
		{
			return String.Equals(x, y, StringComparison.InvariantCultureIgnoreCase);
		}

		public int GetHashCode(string obj)
		{
			if (obj == null)
			{
				return 0;
			}

			return obj.GetHashCode();
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared.Enums
{
	public enum PawnState
	{
		Healthy,
		Downed,
		Unconscious,
		Dead
	}
}


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

namespace rjw.Modules.Shared.Extensions
{
	public static class BodyPartRecordExtension
	{
		public static bool IsMissingForPawn(this BodyPartRecord self, Pawn pawn)
		{
			if (pawn == null)
			{
				throw new ArgumentNullException(nameof(pawn));
			}
			if (self == null)
			{
				throw new ArgumentNullException(nameof(self));
			}

			return pawn.health.hediffSet.hediffs
				.Where(hediff => hediff.Part == self)
				.Where(hediff => hediff is Hediff_MissingPart)
				.Any();
		}
	}
}


code:
using System;
using Verse;

namespace rjw.Modules.Shared.Extensions
{
	public static class PawnExtensions
	{
		public static string GetName(this Pawn pawn)
		{
			if (pawn == null)
			{
				return "null";
			}

			if (String.IsNullOrWhiteSpace(pawn.Name?.ToStringFull) == false)
			{
				return pawn.Name.ToStringFull;
			}

			return pawn.def.defName;
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw.Modules.Shared.Helpers
{
	public static  class AgeHelper
	{
		public const int ImmortalRaceAgeClamp = 25;
		public const int NonHumanRaceAgeClamp = 25;

		public static int ScaleToHumanAge(Pawn pawn, int humanLifespan = 80)
		{
			float pawnAge = pawn.ageTracker.AgeBiologicalYearsFloat;

			if (pawn.def.defName == "Human") return (int)pawnAge; // Human, no need to scale anything.

			float lifeExpectancy = pawn.RaceProps.lifeExpectancy;

			if (RJWSettings.UseAdvancedAgeScaling == true)
			{
				//pseudo-immortal & immortal races
				if (lifeExpectancy >= 500)
				{
					return CalculateForImmortals(pawn, humanLifespan);
				}

				if (lifeExpectancy != humanLifespan)
				{
					//other races
					return CalculateForNonHuman(pawn, humanLifespan);
				}
			}

			float ageScaling = humanLifespan / lifeExpectancy;
			float scaledAge = pawnAge * ageScaling;

			return (int)Mathf.Max(scaledAge, 1);
		}

		private static int CalculateForImmortals(Pawn pawn, int humanLifespan)
		{
			float age = pawn.ageTracker.AgeBiologicalYearsFloat;
			float lifeExpectancy = pawn.RaceProps.lifeExpectancy;
			float growth = pawn.ageTracker.Growth;

			//Growth and hacks
			{
				growth = ImmortalGrowthHacks(pawn, age, growth);

				if (growth < 1)
				{
					return (int)Mathf.Lerp(0, ImmortalRaceAgeClamp, growth);
				}
			}

			//curve
			{
				float life = age / lifeExpectancy;

				//Hypothesis : very long living races looks "young" until the end of their lifespan
				if (life < 0.9f)
				{
					return ImmortalRaceAgeClamp;
				}

				return (int)Mathf.LerpUnclamped(ImmortalRaceAgeClamp, humanLifespan, Mathf.InverseLerp(0.9f, 1, life));
			}
		}

		private static float ImmortalGrowthHacks(Pawn pawn, float age, float originalGrowth)
		{
			if (pawn.ageTracker.CurLifeStage.reproductive == false)
			{
				//Hopefully, reproductive life stage will mean that we're dealing with an adult
				return Math.Min(1, age / ImmortalRaceAgeClamp);
			}

			return 1;
		}

		private static int CalculateForNonHuman(Pawn pawn, int humanLifespan)
		{
			float age = pawn.ageTracker.AgeBiologicalYearsFloat;
			float lifeExpectancy = pawn.RaceProps.lifeExpectancy;
			float growth = pawn.ageTracker.Growth;

			//Growth and hacks
			{
				growth = NonHumanGrowthHacks(pawn, age, growth);

				if (growth < 1)
				{
					return (int)Mathf.Lerp(0, NonHumanRaceAgeClamp, growth);
				}
			}

			//curve
			{
				float life = age / lifeExpectancy;

				return (int)Mathf.LerpUnclamped(NonHumanRaceAgeClamp, humanLifespan, life);
			}
		}

		private static float NonHumanGrowthHacks(Pawn pawn, float age, float originalGrowth)
		{
			if (pawn.ageTracker.CurLifeStage.reproductive == false)
			{
				//Hopefully, reproductive life stage will mean that we're dealing with an adult
				return Math.Min(1, age / NonHumanRaceAgeClamp);
			}

			return 1;
		}

	}
}


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

namespace rjw.Modules.Shared.Helpers
{
	public static class RandomHelper
	{
		private static Random _random;

		static RandomHelper()
		{
			_random = new Random();
		}

		/// <remarks>this is not foolproof</remarks>
		public static TType WeightedRandom<TType>(IList<Weighted<TType>> weights)
		{
			if (weights == null || weights.Any() == false || weights.Where(e => e.Weight < 0).Any())
			{
				return default(TType);
			}

			Weighted<TType> result;

			if (weights.TryRandomElementByWeight(e => e.Weight, out result) == true)
			{
				return result.Element;
			}

			return weights.RandomElement().Element;
		}
	}
}


code:
using rjw.Modules.Shared.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Shared
{
	public interface IPawnStateService
	{
		PawnState Detect(Pawn pawn);
	}
}


code:
using rjw.Modules.Shared.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Shared.Implementation
{
	public class PawnStateService : IPawnStateService
	{
		public static IPawnStateService Instance { get; private set; }

		static PawnStateService()
		{
			Instance = new PawnStateService();
		}

		public PawnState Detect(Pawn pawn)
		{
			if (pawn.Dead)
			{
				return PawnState.Dead;
			}

			if (pawn.Downed)
			{
				if (pawn.health.capacities.CanBeAwake == false)
				{
					return PawnState.Unconscious;
				}

				return PawnState.Downed;
			}

			return PawnState.Healthy;
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared.Logs
{
	public interface ILog
	{
		void Debug(string message);
		void Debug(string message, Exception e);
		void Message(string message);
		void Message(string message, Exception e);
		void Warning(string message);
		void Warning(string message, Exception e);
		void Error(string message);
		void Error(string message, Exception e);
	}
}


code:
namespace rjw.Modules.Shared.Logs
{
	public interface ILogProvider
	{
		bool IsActive { get; }
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared.Logs
{
	public static class LogManager
	{
		private class Logger : ILog
		{
			private readonly string _loggerTypeName;
			private readonly ILogProvider _logProvider;

			public Logger(string typeName)
			{
				_loggerTypeName = typeName;
			}
			public Logger(string typeName, ILogProvider logProvider)
			{
				_loggerTypeName = typeName;

				_logProvider = logProvider;
			}

			public void Debug(string message)
			{
				LogDebug(CreateLogMessage(message));
			}
			public void Debug(string message, Exception exception)
			{
				LogDebug(CreateLogMessage(message, exception));
			}

			public void Message(string message)
			{
				LogMessage(CreateLogMessage(message));
			}
			public void Message(string message, Exception exception)
			{
				LogMessage(CreateLogMessage(message, exception));
			}

			public void Warning(string message)
			{
				LogWarning(CreateLogMessage(message));
			}
			public void Warning(string message, Exception exception)
			{
				LogWarning(CreateLogMessage(message, exception));
			}

			public void Error(string message)
			{
				LogError(CreateLogMessage(message));
			}
			public void Error(string message, Exception exception)
			{
				LogError(CreateLogMessage(message, exception));
			}

			private string CreateLogMessage(string message)
			{
				return $"[{_loggerTypeName}] {message}";
			}
			private string CreateLogMessage(string message, Exception exception)
			{
				return $"{CreateLogMessage(message)}{Environment.NewLine}{exception}";
			}

			private void LogDebug(string message)
			{
				if (_logProvider?.IsActive != false && RJWSettings.DevMode == true)
				{
					ModLog.Message(message);
				}
			}
			private void LogMessage(string message)
			{
				if (_logProvider?.IsActive != false)
				{
					ModLog.Message(message);
				}
			}
			private void LogWarning(string message)
			{
				if (_logProvider?.IsActive != false)
				{
					ModLog.Warning(message);
				}
			}
			private void LogError(string message)
			{
				if (_logProvider?.IsActive != false)
				{
					ModLog.Error(message);
				}
			}
		}

		public static ILog GetLogger<TType, TLogProvider>()
			where TLogProvider : ILogProvider, new()
		{
			return new Logger(typeof(TType).Name, new TLogProvider());
		}
		public static ILog GetLogger<TType>()
		{
			return new Logger(typeof(TType).Name);
		}
		public static ILog GetLogger(string staticTypeName)
		{
			return new Logger(staticTypeName);
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared
{
	public static class Multipliers
	{
		public const float Never = 0f;

		public const float AlmostNever = 0.1f;
		public const float VeryRare = 0.2f;

		public const float Rare = 0.5f;
		public const float Uncommon = 0.8f;

		public const float Average = 1.0f;

		public const float Common = 1.2f;
		public const float Frequent = 1.5f;

		public const float VeryFrequent = 1.8f;
		public const float Doubled = 2.0f;
		public const float DoubledPlus = 2.5f;
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Shared
{
	public class Weighted<TType>
	{
		public TType Element { get; set; }
		public float Weight { get; set; }

		public Weighted(float weight, TType element)
		{
			Weight = weight;
			Element = element;
		}
	}
}


code:
#nullable enable

using System.Linq;
using Verse;
using rjw.Modules.Shared.Logs;
using rjw.Modules.Interactions.DefModExtensions;
using static rjw.Genital_Helper;

using Sex = rjw.GenderHelper.Sex;
using Seeded = rjw.Modules.Rand.Seeded;

namespace rjw.Modules.Testing
{
	static class TestHelper
	{
		static readonly ILog Log = LogManager.GetLogger("TestHelper");

		/// <summary>
		/// Adds some default options to this request that are desireable for testing.
		/// </summary>
		/// <param name="request">The request to modify.</param>
		/// <returns>The modified request.</returns>
		public static PawnGenerationRequest RequestDefaults(this PawnGenerationRequest request) =>
			request with
			{
				CanGeneratePawnRelations = false,
				ForceNoIdeo = true,
				ForceGenerateNewPawn = true
			};

		/// <summary>
		/// <para>Generates a natural pawn using the given request and a fixed seed.</para>
		/// <para>If there was an issue generating the pawn, it will return null.</para>
		/// </summary>
		/// <param name="request">The pawn request.</param>
		/// <returns>The generated pawn or null.</returns>
		public static Pawn? GenerateSeededPawn(PawnGenerationRequest request)
		{
			using (Seeded.With(42))
			{
				var tries = 5;
				while (tries >= 0)
				{
					var pawn = PawnGenerator.GeneratePawn(request);

					// Keep generating until we get a "natural" pawn.  We want to
					// control when futas and traps happen.
					switch (GenderHelper.GetSex(pawn))
					{
						case Sex.male when pawn.gender == Gender.Male:
						case Sex.female when pawn.gender == Gender.Female:
						case Sex.none when pawn.gender == Gender.None:
							return pawn;
					}
					tries -= 1;
				}
				Log.Error($"Could not generate test pawn for: {request.KindDef.defName}");
				return null;
			}
		}

		/// <summary>
		/// Tries to replace a sex-part of the given pawn using a recipe.
		/// </summary>
		/// <param name="pawn">The pawn to change.</param>
		/// <param name="recipe">The recipe to apply.</param>
		/// <returns>Whether the part was applied successfully.</returns>
		public static bool ApplyPartToPawn(Pawn pawn, RecipeDef recipe)
		{
			var worker = recipe.Worker;
			var hediffDef = recipe.addsHediff;
			if (!GenitalPartExtension.TryGet(hediffDef, out var ext)) return false;

			// They must have the correct body part for this sex part.
			var bpr = worker.GetPartsToApplyOn(pawn, recipe).FirstOrDefault();
			if (bpr is null) return false;

			var curParts = get_AllPartsHediffList(pawn)
				.Where((hed) => GenitalPartExtension.TryGet(hed, out var e) && e.family == ext.family)
				.ToArray();

			// We're replacing natural with artifical.
			if (curParts.Length == 0) return false;
			foreach (var part in curParts) pawn.health.RemoveHediff(part);

			var hediff = SexPartAdder.MakePart(hediffDef, pawn, bpr);
			pawn.health.AddHediff(hediff, bpr);
			return true;
		}

		/// <summary>
		/// Replaces the genitals of a pawn with artificial ones.
		/// </summary>
		/// <param name="pawn">The pawn to change.</param>
		/// <returns>Whether the part was swapped successfully.</returns>
		public static bool GiveArtificialGenitals(Pawn pawn)
		{
			var changed = false;
			if (get_genitalsBPR(pawn) is not { } bpr) return changed;

			if (has_penis_fertile(pawn))
			{
				foreach (var part in pawn.GetGenitalsList())
					if (is_fertile_penis(part))
						pawn.health.RemoveHediff(part);
				var hediff = SexPartAdder.MakePart(hydraulic_penis, pawn, bpr);
				pawn.health.AddHediff(hediff, bpr);
				changed = true;
			}

			if (has_vagina(pawn))
			{
				foreach (var part in pawn.GetGenitalsList())
					if (is_vagina(part))
						pawn.health.RemoveHediff(part);
				var hediff = SexPartAdder.MakePart(hydraulic_vagina, pawn, bpr);
				pawn.health.AddHediff(hediff, bpr);
				changed = true;
			}

			return changed;
		}

		/// <summary>
		/// <para>Adds parts to a pawn to make them into a trap.</para>
		/// <para>The pawn must be a natural male to be changed.  If the pawn is
		/// already a trap, it will return `true`.  Otherwise, it will return `false`
		/// if it failed to change the pawn.</para>
		/// <para>Note since Feb-2023: this is currently bugged, since the
		/// `SexPartAdder.add_breasts` does not respect the request to use female
		/// breasts.</para>
		/// </summary>
		/// <param name="pawn">The pawn to change.</param>
		/// <returns>Whether the pawn was modified.</returns>
		public static bool MakeIntoTrap(Pawn pawn)
		{
			if (GenderHelper.GetSex(pawn) is var sex and not Sex.male)
				return sex is Sex.trap;

			var parts = pawn.GetBreastList();
			foreach (var part in parts)
				pawn.health.RemoveHediff(part);
			SexPartAdder.add_breasts(pawn, gender: Gender.Female);
			return GenderHelper.GetSex(pawn) is Sex.trap;
		}

		/// <summary>
		/// <para>Adds a part to a pawn to make them into a futa.</para>
		/// <para>The pawn must be a natural male or female and will return `false`
		/// if it failed to change the pawn into a futa.</para>
		/// </summary>
		/// <param name="pawn">The pawn to change.</param>
		/// <param name="infertile">Whether the part should be infertile.</param>
		/// <returns>Whether the pawn was modified.</returns>
		public static bool MakeIntoFuta(Pawn pawn, bool infertile = false)
		{
			Hediff hediff;
			switch ((GenderHelper.GetSex(pawn), infertile))
			{
				case (Sex.male or Sex.trap, false):
					SexPartAdder.add_genitals(pawn, gender: Gender.Female);
					return GenderHelper.GetSex(pawn) is Sex.futa;
				case (Sex.male or Sex.trap, true) when get_genitalsBPR(pawn) is { } bpr:
					hediff = SexPartAdder.MakePart(hydraulic_vagina, pawn, bpr);
					pawn.health.AddHediff(hediff, bpr);
					return GenderHelper.GetSex(pawn) is Sex.futa;
				case (Sex.female, false):
					SexPartAdder.add_genitals(pawn, gender: Gender.Male);
					return GenderHelper.GetSex(pawn) is Sex.futa;
				case (Sex.female, true) when get_genitalsBPR(pawn) is { } bpr:
					hediff = SexPartAdder.MakePart(hydraulic_penis, pawn, bpr);
					pawn.health.AddHediff(hediff, bpr);
					return GenderHelper.GetSex(pawn) is Sex.futa;
				default:
					return false;
			}
		}
	}
}


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

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

		private static float decay_per_day = 0.3f;

		// TODO: make these threshold values constants or at least static
		// readonly properties, if they're meant for patching.

		public float thresh_frustrated() => 0.05f;

		public float thresh_horny() => 0.25f;

		public float thresh_neutral() => 0.50f;

		public float thresh_satisfied() => 0.75f;

		public float thresh_ahegao() => 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()
			};
		}

		// These are overridden just for other mods to patch and alter their behavior.
		// Without it, they would need to patch `Need` itself, adding a type check to
		// every single need IN THE GAME before executing the new behavior.
		public override float CurInstantLevel
		{
			get { return base.CurInstantLevel; }
		}

		public override float CurLevel
		{
			get { return base.CurLevel; }
			set { base.CurLevel = value; }
		}

		//public override bool ShowOnNeedList
		//{
		//	get
		//	{
		//		if (Genital_Helper.has_genitals(pawn))
		//			return true;

		//		ModLog.Message("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 brokenbodyfactor(Pawn pawn)
		{
			//This adds in the broken body system
			float broken_body_factor = 1f;
			if (pawn.health.hediffSet.HasHediff(xxx.feelingBroken))
			{
				switch (pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken).CurStageIndex)
				{
					case 0:
						return 0.75f;
					case 1:
						return 1.4f;
					case 2:
						return 2f;
				}
			}
			return broken_body_factor;
		}

		public static float druggedfactor(Pawn pawn)
		{
			if (pawn.health.hediffSet.HasHediff(RJWHediffDefOf.HumpShroomEffect))
			{
				//ModLog.Message("Need_Sex::druggedfactor 3 pawn is " + xxx.get_pawnname(pawn));
				return 3f;
			}

			// No humpshroom effect but addicted?
			if (pawn.health.hediffSet.HasHediff(RJWHediffDefOf.HumpShroomAddiction))
			{
				//ModLog.Message("Need_Sex::druggedfactor 0.5 pawn is " + xxx.get_pawnname(pawn));
				return 0.5f;
			}

			//ModLog.Message("Need_Sex::druggedfactor 1 pawn is " + xxx.get_pawnname(pawn));
			return 1f;
		}

		static float diseasefactor(Pawn pawn)
		{
			return 1f;
		}

		static float futafactor(Pawn pawn)
		{
			// Checks for doubly-fertile futa.
			// Presumably, they got twice the hormones coursing through their brain.
			return Genital_Helper.is_futa(pawn) ? 2.0f : 1.0f;
		}

		static float agefactor(Pawn pawn)
		{
			// Age check was moved to start of `NeedInterval` and this factor
			// is no longer useful in base RJW.  It is left here in case any
			// mod patches this factor.
			return 1f;
		}

		/// <summary>
		/// Gets the cumulative factors affecting decay for a given pawn.
		/// </summary>
		public static float GetFallFactorFor(Pawn pawn)
		{
			return brokenbodyfactor(pawn) *
				druggedfactor(pawn) *
				diseasefactor(pawn) *
				agefactor(pawn) *
				futafactor(pawn);
		}

		static float fall_per_tick(Pawn pawn)
		{
			var fall_per_tick = decay_per_day / GenDate.TicksPerDay * GetFallFactorFor(pawn);
			//--ModLog.Message("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)));
			//ModLog.Message(" " + 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) );
			return fall_per_tick;
		}

		public override void NeedInterval()
		{
			if (isInvisible) return; // no caravans

			// Asexual or too young.
			if (xxx.is_asexual(pawn) || !xxx.can_do_loving(pawn))
			{
				CurLevel = 0.5f;
				return;
			}

			//--ModLog.Message("Need_Sex::NeedInterval is called0 - pawn is "+xxx.get_pawnname(pawn));

			if (!def.freezeWhileSleeping || pawn.Awake())
			{
				// `NeedInterval` is called by `Pawn_NeedsTracker` once every 150 ticks, which is around .06 game hours.
				var fallPerCall = 150 * fall_per_tick(pawn);
				var sexDriveModifier = xxx.get_sex_drive(pawn);
				var decayRateModifier = RJWSettings.sexneed_decay_rate;
				var decayThisCall = fallPerCall * sexDriveModifier * decayRateModifier;
				CurLevel -= decayThisCall;

				// ModLog.Message($" {xxx.get_pawnname(pawn)}'s sex need stats:: Decay/call: {decayThisCall}, Dec. rate: {decayRateModifier}, Cur.lvl: {CurLevel}, Sex drive: {sexDriveModifier}");
			}

			//--ModLog.Message("Need_Sex::NeedInterval is called1");
		}
	}
}


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;
			var parts = pawn.GetGenitalsList();

			if (!Genital_Helper.has_penis_fertile(pawn, parts) && !Genital_Helper.has_vagina(pawn, parts))
				return 0;

			if (Genital_Helper.has_ovipositorF(pawn, parts) || Genital_Helper.has_ovipositorM(pawn, parts))
				return 0;

			//ModLog.Message("PawnCapacityWorker_Fertility::CalculateCapacityLevel is called for: " + xxx.get_pawnname(pawn));

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

			float result = 1;

			if (RJWPregnancySettings.UseVanillaPregnancy && pawn.RaceProps.Humanlike)
			{
				float fertilityStat = pawn.GetStatValue(StatDefOf.Fertility);
				if (fertilityStat != 1)
				{
					result *= fertilityStat;
					impactors?.Add(new CapacityImpactorVanillaFertility());
				}
			}
			else
			{
				float ageFactor = CalculateAgeImpact(pawn);
				if (ageFactor != 1)
				{
					result *= ageFactor;
					impactors?.Add(new CapacityImpactorAge());
				}
			}

			result *= PawnCapacityUtility.CalculateTagEfficiency(diffSet, BodyPartTagDefOf.RJW_Fertility, 1f, FloatRange.ZeroToOne, impactors);

			//ModLog.Message("PawnCapacityWorker_Fertility::CalculateCapacityLevel result is: " + result);
			return result;

		}

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

		private float CalculateAgeImpact(Pawn pawn)
		{
			RaceProperties race = pawn.RaceProps;
			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%.
				}
			}
			//If pawn is an animal, first reproductive lifestage is (usually) adult, so set startMaxAge at first reproductive stage, and startAge at stage before that.
			int adult;
			if (xxx.is_animal(pawn) && (adult = race.lifeStageAges.FirstIndexOf((ls) => ls.def.reproductive)) > 0)
			{
				startAge = race.lifeStageAges[adult - 1].minAge;
				startMaxAge = race.lifeStageAges[adult].minAge;
			}
			else
			{
				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) // ensure peak fertility stays at start or a bit before adult stage
								startMaxAge = lifestage.minAge;
						}
				}
			}
			//Log.Message(" Fertility ages for " + pawn.Name + " are: " + startAge + ", " + startMaxAge + ", " + endAge + ", " + endMaxAge);

			return GenMath.FlatHill(startAge, startMaxAge, endAge, zeroFertility, pawn.ageTracker.AgeBiologicalYearsFloat);
		}

		// Tooltips are hardcoded to only use concrete vanilla CapacityImpactors(?!), so we need to subclass one
		// in order to show anything without gratuitous patching (and possibly stepping on the toes of any mod that
		// happens to mess with these tooltips). CapacityImpactorPain doesn't have any fields or specialised methods,
		// so that's what we'll use.
		public class CapacityImpactorAge : PawnCapacityUtility.CapacityImpactorPain
		{
			public override string Readable(Pawn pawn)
			{
				return "AgeImpactor".Translate() + pawn.ageTracker.AgeBiologicalYearsFloat.ToStringApproxAge();
			}
		}

		public class CapacityImpactorVanillaFertility : PawnCapacityUtility.CapacityImpactorPain
		{
			public override string Readable(Pawn pawn)
			{
				return "VanillaFertilityImpactor".Translate(
					((int) (pawn.GetStatValue(StatDefOf.Fertility) * 100)).Named("VALUE")
				);
			}
		}
	}
}


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

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public static class DesignatorCheckbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_Refuse");

		private static bool checkboxPainting;
		private static bool checkboxPaintingState;

		public static void Checkbox(Vector2 topLeft, ref bool checkOn, float size = 24f, bool disabled = false, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
		{
			Checkbox(topLeft.x, topLeft.y, ref checkOn, size, disabled, texChecked, texUnchecked);
		}

		public static void Checkbox(float x, float y, ref bool checkOn, float size = 24f, bool disabled = false, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
		{
			Rect rect = new Rect(x, y, size, size);
			CheckboxDraw(x, y, checkOn, disabled, size, texChecked, texUnchecked,texDisabled);
			if (!disabled)
			{
				MouseoverSounds.DoRegion(rect);
				bool flag = false;
				Widgets.DraggableResult draggableResult = Widgets.ButtonInvisibleDraggable(rect, false);
				if (draggableResult == Widgets.DraggableResult.Pressed)
				{
					checkOn = !checkOn;
					flag = true;
				}
				else if (draggableResult == Widgets.DraggableResult.Dragged)
				{
					checkOn = !checkOn;
					flag = true;
					checkboxPainting = true;
					checkboxPaintingState = checkOn;
				}
				if (Mouse.IsOver(rect) && checkboxPainting && Input.GetMouseButton(0) && checkOn != checkboxPaintingState)
				{
					checkOn = checkboxPaintingState;
					flag = true;
				}
				if (flag)
				{
					if (checkOn)
					{
						SoundDefOf.Checkbox_TurnedOn.PlayOneShotOnCamera(null);
					}
					else
					{
						SoundDefOf.Checkbox_TurnedOff.PlayOneShotOnCamera(null);
					}
				}
			}
		}

		private static void CheckboxDraw(float x, float y, bool active, bool disabled, float size = 24f, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
		{
			Texture2D image;
			if (disabled)
			{
				image = ((!(texDisabled != null)) ? CheckboxDisabledTex : texDisabled);
			}
			else if (active)
			{
				image = ((!(texChecked != null)) ? CheckboxOnTex : texChecked);
			}
			else
			{
				image = ((!(texUnchecked != null)) ? CheckboxOffTex : texUnchecked);
			}
			Rect position = new Rect(x, y, size, size);
			GUI.DrawTexture(position, image);
		}

	}
}


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

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public abstract class PawnColumnCheckbox : PawnColumnWorker
	{
		public static readonly Texture2D CheckboxOnTex;
		public static readonly Texture2D CheckboxOffTex;
		public static readonly Texture2D CheckboxDisabledTex;

		public const int HorizontalPadding = 2;

		public override void DoCell(Rect rect, Pawn pawn, RimWorld.PawnTable table)
		{
			if (!this.HasCheckbox(pawn))
			{
				return;
			}
			if (Find.TickManager.TicksGame % 60 == 0)
			{
				pawn.UpdatePermissions();
				//Log.Message("GetDisabled UpdateCanDesignateService for " + xxx.get_pawnname(pawn));
				//Log.Message("UpdateCanDesignateService " + pawn.UpdateCanDesignateService());
				//Log.Message("CanDesignateService " + pawn.CanDesignateService());
				//Log.Message("GetDisabled " + GetDisabled(pawn));
			}
			int num = (int)((rect.width - 24f) / 2f);
			int num2 = Mathf.Max(3, 0);
			Vector2 vector = new Vector2(rect.x + (float)num, rect.y + (float)num2);
			Rect rect2 = new Rect(vector.x, vector.y, 24f, 24f);
			bool disabled = this.GetDisabled(pawn);
			bool value;
			if (disabled)
			{
				value = false;
			}
			else
			{
				value = this.GetValue(pawn);
			}
			bool flag = value;
			Vector2 topLeft = vector;
			//Widgets.Checkbox(topLeft, ref value, 24f, disabled, CheckboxOnTex, CheckboxOffTex, CheckboxDisabledTex);
			MakeCheckbox(topLeft, ref value, 24f, disabled, CheckboxOnTex, CheckboxOffTex, CheckboxDisabledTex);
			if (Mouse.IsOver(rect2))
			{
				string tip = this.GetTip(pawn);
				if (!tip.NullOrEmpty())
				{
					TooltipHandler.TipRegion(rect2, tip);
				}
			}
			if (value != flag)
			{
				this.SetValue(pawn, value);
			}
		}

		protected void MakeCheckbox(Vector2 topLeft, ref bool value, float v = 24f, bool disabled = false, Texture2D checkboxOnTex = null, Texture2D checkboxOffTex = null, Texture2D checkboxDisabledTex = null)
		{
			Widgets.Checkbox(topLeft, ref value, v, disabled, checkboxOnTex, checkboxOffTex, checkboxDisabledTex);
		}

		protected virtual string GetTip(Pawn pawn)
		{
			return null;
		}

		protected virtual bool HasCheckbox(Pawn pawn)
		{
			return false;
		}

		protected abstract bool GetValue(Pawn pawn);

		protected abstract void SetValue(Pawn pawn, bool value);

		protected virtual bool GetDisabled(Pawn pawn)
		{
			return false;
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_BreedAnimal : PawnColumnWorker_Checkbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_Refuse");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateBreeding();
		}
		protected bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateBreeding();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedBreeding() && xxx.is_animal(pawn);
		}

		protected override void SetValue(Pawn pawn, bool value, PawnTable table)
		{
			if (value == this.GetValue(pawn)) return;
			pawn.ToggleBreeding();
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_BreedHumanlike : PawnColumnWorker_Checkbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_Refuse");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateBreeding();
		}
		protected bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateBreeding();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedBreeding() && xxx.is_human(pawn);
		}

		protected override void SetValue(Pawn pawn, bool value, PawnTable table)
		{
			if (value == this.GetValue(pawn)) return;
			pawn.ToggleBreeding();
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_BreederAnimal : PawnColumnWorker_Checkbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Breeding_Pawn_Refuse");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateBreedingAnimal();
		}
		protected bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateBreedingAnimal();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedBreedingAnimal() && xxx.is_animal(pawn);
		}

		protected override void SetValue(Pawn pawn, bool value, PawnTable table)
		{
			if (value == this.GetValue(pawn)) return;
			pawn.ToggleBreedingAnimal();
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_Comfort : PawnColumnWorker_Checkbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_Refuse");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateComfort();
		}
		protected bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateComfort();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedComfort();
		}

		protected override void SetValue(Pawn pawn, bool value, PawnTable table)
		{
			pawn.ToggleComfort();
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_Hero : PawnColumnCheckbox
	{
		//public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Hero_on");
		//public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Hero_off");
		//public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_Refuse");

		//public static readonly Texture2D iconAccept = ContentFinder<Texture2D>.Get("UI/Commands/Hero_off");
		//static readonly Texture2D iconCancel = ContentFinder<Texture2D>.Get("UI/Commands/Hero_on");
		//protected override Texture2D GetIconFor(Pawn pawn)
		//{
		//	return pawn.CanDesignateHero() ? pawn.IsDesignatedHero() ? iconAccept : iconCancel : null;
		//}

		//protected override string GetIconTip(Pawn pawn)
		//{
		//	return "PawnColumnWorker_IsHero".Translate();
		//	;
		//}

		//public static Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Hero_on");
		//public static Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Hero_off");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateHero() || pawn.IsDesignatedHero();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedHero();
		}

		protected override void SetValue(Pawn pawn, bool value)
		{
			if (pawn.IsDesignatedHero()) return;
			pawn.ToggleHero();

			//reload/update tab
			var rjwtab = DefDatabase<MainButtonDef>.GetNamed("RJW_MainButton");
			Find.MainTabsRoot.ToggleTab(rjwtab, false);//off
			Find.MainTabsRoot.ToggleTab(rjwtab, false);//on
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw.MainTab.Checkbox
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_Whore : PawnColumnWorker_Checkbox
	{
		public static readonly Texture2D CheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_on");
		public static readonly Texture2D CheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_off");
		public static readonly Texture2D CheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_Refuse");
		protected override bool HasCheckbox(Pawn pawn)
		{
			return pawn.CanDesignateService();
		}
		protected bool GetDisabled(Pawn pawn)
		{
			return !pawn.CanDesignateService();
		}

		protected override bool GetValue(Pawn pawn)
		{
			return pawn.IsDesignatedService();
		}

		protected override void SetValue(Pawn pawn, bool value, PawnTable table)
		{
			pawn.ToggleService();
		}
	}
}


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

namespace rjw.MainTab.DefModExtensions
{
	public class RJW_PawnTable : DefModExtension
	{
		public string label;
	}
}


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

namespace rjw.MainTab.Icon
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsBreeder : PawnColumnWorker_Icon
	{
		private static readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_on");
		private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off");
		private readonly Texture2D comfortOff_nobg = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off_nobg");
		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return pawn.CanDesignateBreeding() ? pawn.IsDesignatedBreeding() ? comfortOn : comfortOff : comfortOff_nobg;
			//return xxx.is_slave(pawn) ? comfortOff : null;
		}
		protected override string GetIconTip(Pawn pawn)
		{
			return "PawnColumnWorker_IsBreeder".Translate();
			;
		}
	}
}


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

namespace rjw.MainTab.Icon
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsComfort : PawnColumnWorker_Icon
	{
		private readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_on");
		private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off");
		private readonly Texture2D comfortOff_nobg = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off_nobg");
		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return pawn.CanDesignateComfort() ? pawn.IsDesignatedComfort() ? comfortOn : comfortOff : comfortOff_nobg;
		}
		protected override string GetIconTip(Pawn pawn)
		{
			return "PawnColumnWorker_IsComfort".Translate();
			;
		}
	}
}


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

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

		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return pawn.IsPrisonerOfColony ? comfortOff_nobg : null;
		}
		protected override string GetIconTip(Pawn pawn)
		{
			return "PawnColumnWorker_IsPrisoner".Translate();
		}
	}
}


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

namespace rjw.MainTab.Icon
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_IsSlave : PawnColumnWorker_Icon
	{
		private readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_on");
		private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off");
		private readonly Texture2D comfortOff_nobg = ContentFinder<Texture2D>.Get("UI/Commands/ComfortPrisoner_off_nobg");
		protected override Texture2D GetIconFor(Pawn pawn)
		{
			return xxx.is_slave(pawn) ? ModsConfig.IdeologyActive ? GuestUtility.SlaveIcon : comfortOff_nobg : null;
		}
		protected override string GetIconTip(Pawn pawn)
		{
			return "PawnColumnWorker_IsSlave".Translate();
			;
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

using static rjw.GenderHelper;

namespace rjw.MainTab.Icon
{
	[StaticConstructorOnStartup]
	public class PawnColumnWorker_RJWGender : PawnColumnWorker_Gender
	{
		public static readonly Texture2D hermIcon = ContentFinder<Texture2D>.Get("UI/Icons/Gender/Genders", true);

		protected override Texture2D GetIconFor(Pawn pawn) => GetSex(pawn) switch
		{
			Sex.futa => hermIcon,
			_ => pawn.gender.GetIcon()
		};
		protected override string GetIconTip(Pawn pawn) => GetSex(pawn) switch
		{
			Sex.futa => "PawnColumnWorker_RJWGender_IsHerm".Translate(),
			_ => pawn.GetGenderLabel().CapitalizeFirst()
		};
	}
}


code:
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using rjw.MainTab.DefModExtensions;

namespace rjw.MainTab
{
	[StaticConstructorOnStartup]
	public class RJW_PawnTableList
	{
		public List<PawnTableDef> getdefs()
		{
			var defs = new List<PawnTableDef>();
			defs.AddRange(DefDatabase<PawnTableDef>.AllDefs.Where(x => x.HasModExtension<RJW_PawnTable>()));
			return defs;
		}
	}
	public class MainTabWindow : MainTabWindow_PawnTable
	{
		protected override float ExtraBottomSpace
		{
			get
			{
				return 53f; //default 53
			}
		}

		protected override float ExtraTopSpace
		{
			get
			{
				return 40f; //default 0
			}
		}

		protected override PawnTableDef PawnTableDef => pawnTableDef;

		protected override IEnumerable<Pawn> Pawns => pawns;

		public IEnumerable<Pawn> pawns = Find.CurrentMap.mapPawns.AllPawns.Where(p => p.RaceProps.Humanlike && p.IsColonist && !xxx.is_slave(p));
		public PawnTableDef pawnTableDef = DefDatabase<PawnTableDef>.GetNamed("RJW_PawnTable_Colonists");

		/// <summary>
		/// draw table
		/// </summary>
		/// <param name="rect"></param>
		public override void DoWindowContents(Rect rect)
		{
			base.DoWindowContents(rect);
			if (Widgets.ButtonText(new Rect(rect.x + 5f, rect.y + 5f, Mathf.Min(rect.width, 260f), 32f), "MainTabWindow_Designators".Translate(), true, true, true))
			{
				MakeMenu();
			}
		}

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

		/// <summary>
		/// reload/update tab
		/// </summary>
		public static void Reloadtab()
		{
			var rjwtab = DefDatabase<MainButtonDef>.GetNamed("RJW_MainButton");
			Find.MainTabsRoot.ToggleTab(rjwtab, false);//off
			Find.MainTabsRoot.ToggleTab(rjwtab, false);//on
		}

		public void MakeMenu()
		{
			Find.WindowStack.Add(new FloatMenu(MakeOptions()));
		}

		/// <summary>
		/// switch pawnTable's
		/// patch this
		/// </summary>
		public List<FloatMenuOption> MakeOptions()
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			PawnTableDef tabC = DefDatabase<PawnTableDef>.GetNamed("RJW_PawnTable_Colonists");
			PawnTableDef tabA = DefDatabase<PawnTableDef>.GetNamed("RJW_PawnTable_Animals");
			PawnTableDef tabP = DefDatabase<PawnTableDef>.GetNamed("RJW_PawnTable_Property");

			opts.Add(new FloatMenuOption(tabC.GetModExtension<RJW_PawnTable>().label, () =>
			{
				pawnTableDef = tabC;
				pawns = Find.CurrentMap.mapPawns.AllPawns.Where(p => p.RaceProps.Humanlike && p.IsColonist && !xxx.is_slave(p));
				Notify_ResolutionChanged();
				Reloadtab();
			}, MenuOptionPriority.Default));

			opts.Add(new FloatMenuOption(tabA.GetModExtension<RJW_PawnTable>().label, () =>
			{
				pawnTableDef = tabA;
				pawns = Find.CurrentMap.mapPawns.PawnsInFaction(Faction.OfPlayer).Where(p => xxx.is_animal(p));
				Notify_ResolutionChanged();
				Reloadtab();
			}, MenuOptionPriority.Default));

			opts.Add(new FloatMenuOption(tabP.GetModExtension<RJW_PawnTable>().label, () =>
			{
				pawnTableDef = tabP;
				pawns = Find.CurrentMap.mapPawns.AllPawns.Where(p => p.RaceProps.Humanlike && (p.IsColonist && xxx.is_slave(p) || p.IsPrisonerOfColony));
				Notify_ResolutionChanged();
				Reloadtab();
			}, MenuOptionPriority.Default));

			return opts;
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;
using Verse.Sound;

namespace RimWorld
{
	// Token: 0x020013F0 RID: 5104
	public class PawnColumnWorker_Master : PawnColumnWorker
	{
		// Token: 0x17001618 RID: 5656
		// (get) Token: 0x06007D97 RID: 32151 RVA: 0x00012AE6 File Offset: 0x00010CE6
		protected override GameFont DefaultHeaderFont
		{
			get
			{
				return GameFont.Tiny;
			}
		}

		// Token: 0x06007D98 RID: 32152 RVA: 0x002CCC79 File Offset: 0x002CAE79
		public override int GetMinWidth(PawnTable table)
		{
			return Mathf.Max(base.GetMinWidth(table), 100);
		}

		// Token: 0x06007D99 RID: 32153 RVA: 0x002CCC89 File Offset: 0x002CAE89
		public override int GetOptimalWidth(PawnTable table)
		{
			return Mathf.Clamp(170, this.GetMinWidth(table), this.GetMaxWidth(table));
		}

		// Token: 0x06007D9A RID: 32154 RVA: 0x002CB126 File Offset: 0x002C9326
		public override void DoHeader(Rect rect, PawnTable table)
		{
			base.DoHeader(rect, table);
			MouseoverSounds.DoRegion(rect);
		}

		// Token: 0x06007D9B RID: 32155 RVA: 0x002CCCA3 File Offset: 0x002CAEA3
		public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
		{
			if (!this.CanAssignMaster(pawn))
			{
				return;
			}
			TrainableUtility.MasterSelectButton(rect.ContractedBy(2f), pawn, true);
		}

		// Token: 0x06007D9C RID: 32156 RVA: 0x002CCCC4 File Offset: 0x002CAEC4
		public override int Compare(Pawn a, Pawn b)
		{
			int valueToCompare = this.GetValueToCompare1(a);
			int valueToCompare2 = this.GetValueToCompare1(b);
			if (valueToCompare != valueToCompare2)
			{
				return valueToCompare.CompareTo(valueToCompare2);
			}
			return this.GetValueToCompare2(a).CompareTo(this.GetValueToCompare2(b));
		}

		// Token: 0x06007D9D RID: 32157 RVA: 0x002CCD01 File Offset: 0x002CAF01
		private bool CanAssignMaster(Pawn pawn)
		{
			return pawn.RaceProps.Animal && pawn.Faction == Faction.OfPlayer && pawn.training.HasLearned(TrainableDefOf.Obedience);
		}

		// Token: 0x06007D9E RID: 32158 RVA: 0x002CCD34 File Offset: 0x002CAF34
		private int GetValueToCompare1(Pawn pawn)
		{
			if (!this.CanAssignMaster(pawn))
			{
				return 0;
			}
			if (pawn.playerSettings.Master == null)
			{
				return 1;
			}
			return 2;
		}

		// Token: 0x06007D9F RID: 32159 RVA: 0x002CCD51 File Offset: 0x002CAF51
		private string GetValueToCompare2(Pawn pawn)
		{
			if (pawn.playerSettings != null && pawn.playerSettings.Master != null)
			{
				return pawn.playerSettings.Master.Label;
			}
			return "";
		}
	}
}


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

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


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

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

		//default sorting
		protected override IEnumerable<Pawn> LabelSortFunction(IEnumerable<Pawn> input)
		{
			//return input.OrderBy(p => p.Name);
			foreach (Pawn p in input)
				p.UpdatePermissions();
			return input.OrderByDescending(p => xxx.get_pawnname(p));
			//return input.OrderByDescending(p => (p.IsPrisonerOfColony || p.IsSlaveOfColony) != false).ThenBy(p => (p.Name.ToStringShort.Colorize(Color.yellow)));
			//return input.OrderBy(p => xxx.get_pawnname(p));
		}

		protected override IEnumerable<Pawn> PrimarySortFunction(IEnumerable<Pawn> input)
		{
			foreach (Pawn p in input)
				p.UpdatePermissions();
			return input;
			//return base.PrimarySortFunction(input);
		}

		//public IEnumerable<Pawn> FilterPawns
		//{
		//	get
		//	{
		//		ModLog.Message("FilterPawnsGet");
		//		var x = Find.CurrentMap.mapPawns.PawnsInFaction(Faction.OfPlayer).Where(p => xxx.is_animal(p));
		//		ModLog.Message("x: " + x);
		//		return x;
		//	}
		//}
	}
}


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

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

		//default sorting
		protected override IEnumerable<Pawn> LabelSortFunction(IEnumerable<Pawn> input)
		{
			//return input.OrderBy(p => p.Name);
			foreach (Pawn p in input)
				p.UpdatePermissions();
			return input.OrderByDescending(p => (p.IsPrisonerOfColony || p.IsSlaveOfColony) != false).ThenBy(p => xxx.get_pawnname(p));
			//return input.OrderByDescending(p => (p.IsPrisonerOfColony || p.IsSlaveOfColony) != false).ThenBy(p => (p.Name.ToStringShort.Colorize(Color.yellow)));
			//return input.OrderBy(p => xxx.get_pawnname(p));
		}

		protected override IEnumerable<Pawn> PrimarySortFunction(IEnumerable<Pawn> input)
		{
			foreach (Pawn p in input)
				p.UpdatePermissions();
			return input;
			//return base.PrimarySortFunction(input);
		}

		//public static IEnumerable<Pawn> FilterPawns()
		//{
		//	return Find.CurrentMap.mapPawns.PawnsInFaction(Faction.OfPlayer).Where(p => xxx.is_animal(p));
		//}
	}
}


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