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

namespace rjw
{
	public class Hediff_InsectEgg : HediffWithComps
	{
		/*
		 * Dev Note: To easily test egg-birth, set the p_end_tick to p_start_tick + 10 in the SaveFile.
		 */

		public float p_start_tick = 0;
		public float p_end_tick = 0;
		public float lastTick = 0;

		private HediffDef_InsectEgg HediffDefOfEgg => (HediffDef_InsectEgg)def;

		public string parentDef => HediffDefOfEgg.parentDef;

		public List<string> parentDefs => HediffDefOfEgg.parentDefs;
		public List<GeneDef> eggGenes;

		// TODO: I'd like to change "father" to "fertilizer" but that would be a breaking change.
		public Pawn father;					//can be parentkind defined in egg
		public Pawn implanter;					//can be any pawn
		public bool canbefertilized = true;
		public bool fertilized => father != null;
		public float eggssize = 0.1f;
		protected List<Pawn> babies;
		float Gestation = 0f;

		///Contractions duration, effectively additional hediff stage, a dirty hack to make birthing process notable
		//protected const int TicksPerHour = 2500;
		protected int contractions = 0;

		public override string LabelBase
		{
			get
			{
				if (Prefs.DevMode && implanter != null)
				{
						return implanter.kindDef.race.label + " egg";
				}

				if (eggssize <= 0.10f)
					return "Small egg";
				if (eggssize <= 0.3f)
					return "Medium egg";
				else if (eggssize <= 0.5f)
					return "Big egg";
				else
					return "Huge egg";
			}
		}

		public override string LabelInBrackets
		{
			get
			{
				if (Prefs.DevMode)
				{
					if (fertilized)
						return "Fertilized";
					else
						return "Unfertilized";
				}
				return null;
			}
		}

		public float GestationProgress
		{
			get => Gestation;
			set => Gestation = value;
		}

		public override bool TryMergeWith(Hediff other)
		{
			return false;
		}

		public override void Tick()
		{
			var thisTick = Find.TickManager.TicksGame;
			GestationProgress = (1 + thisTick - p_start_tick) / (p_end_tick - p_start_tick);

			if ((thisTick - lastTick) >= 1000)
			{
				lastTick = thisTick;
				//birthing takes an hour
				if (GestationProgress >= 1 && contractions == 0 && !(pawn.jobs.curDriver is JobDriver_Sex))
				{
					if (PawnUtility.ShouldSendNotificationAbout(pawn) && (pawn.IsColonist || pawn.IsPrisonerOfColony))
					{
						string key = "RJW_EggContractions";
						string text = TranslatorFormattedStringExtensions.Translate(key, pawn.LabelIndefinite());
						Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);
					}
					contractions++;
					if (!Genital_Helper.has_ovipositorF(pawn))
						pawn.health.AddHediff(xxx.submitting);
				}
				else if (GestationProgress >= 1 && contractions != 0 && (pawn.CarriedBy == null || pawn.CarriedByCaravan()))
				{
					if (PawnUtility.ShouldSendNotificationAbout(pawn) && (pawn.IsColonist || pawn.IsPrisonerOfColony))
					{
						string key1 = "RJW_GaveBirthEggTitle";
						string message_title = TranslatorFormattedStringExtensions.Translate(key1, pawn.LabelIndefinite());
						string key2 = "RJW_GaveBirthEggText";
						string message_text = TranslatorFormattedStringExtensions.Translate(key2, pawn.LabelIndefinite());

						Messages.Message(message_text, pawn, MessageTypeDefOf.SituationResolved);
					}
					GiveBirth();
				}
			}
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_References.Look(ref father, "father", true);
			Scribe_References.Look(ref implanter, "implanter", true);
			Scribe_Values.Look(ref Gestation, "Gestation");
			Scribe_Values.Look(ref p_start_tick, "p_start_tick", 0);
			Scribe_Values.Look(ref p_end_tick, "p_end_tick", 0);
			Scribe_Values.Look(ref lastTick, "lastTick", 0);
			Scribe_Values.Look(ref eggssize, "eggssize", 0.1f);
			Scribe_Values.Look(ref canbefertilized, "canbefertilized", true);
			Scribe_Collections.Look(ref eggGenes, label: "genes", lookMode: LookMode.Def);
			Scribe_Collections.Look(ref babies, saveDestroyedThings: true, label: "babies", lookMode: LookMode.Deep, ctorArgs: new object[0]);
		}
		public override void Notify_PawnDied()
		{
			base.Notify_PawnDied();
			GiveBirth();
		}

		//should someday remake into birth eggs and then within few ticks hatch them
		[SyncMethod]
		public void GiveBirth()
		{
			Pawn mother = pawn;
			Pawn baby = null;

			if (RJWSettings.DevMode) ModLog.Message($"{pawn} is giving birth to an InsectEgg");
			if (fertilized)
			{
				if (RJWSettings.DevMode) ModLog.Message($"Egg was fertilized - Fertilizer {father} and Implanter {implanter}");
				PawnKindDef spawn_kind_def = implanter.kindDef;

				Faction spawn_faction = DetermineSpawnFaction(mother);
				spawn_kind_def = AdjustSpawnKindDef(mother, spawn_kind_def);

				if (!spawn_kind_def.RaceProps.Humanlike)
				{
					BirthFertilizedAnimalEgg(mother, spawn_kind_def);
				}
				else //humanlike baby
				{
					baby = ProcessHumanLikeInsectEgg(mother, spawn_kind_def, spawn_faction);
				}
				UpdateBirtherRecords(mother);
			}
			else
			{
				BirthUnfertilizedEggs(mother);
			}
			// Post birth
			ProcessPostBirth(mother, baby);

			mother.health.RemoveHediff(this);
		}

		/// <summary>
		/// Bumps the Birth-Statistic for eggs and maybe adds fitting quirks for the mother.
		/// </summary>
		/// <param name="mother">The pawn that birthed a new egg</param>
		private static void UpdateBirtherRecords(Pawn mother)
		{

			if (RJWSettings.DevMode) ModLog.Message($"Updating {mother}s records after birthing an egg");

			mother.records.AddTo(xxx.CountOfBirthEgg, 1);
			if (mother.records.GetAsInt(xxx.CountOfBirthEgg) > 100)
			{
				mother.Add(Quirk.Incubator);
				mother.Add(Quirk.ImpregnationFetish);
			}
		}

		/// <summary>
		/// Adjusts the Pawn_Kind_Def if it is a Nymph.
		/// Issue is that later processing cannot nicely deal with Nymphs.
		/// </summary>
		/// <param name="mother">The mother, for potential back-up lookup</param>
		/// <param name="spawn_kind_def">The current spawn_kind_def</param>
		/// <returns></returns>
		private PawnKindDef AdjustSpawnKindDef(Pawn mother, PawnKindDef spawn_kind_def)
		{
			if (spawn_kind_def.defName.Contains("Nymph"))
			{
				//child is nymph, try to find other PawnKindDef
				var spawn_kind_def_list = new List<PawnKindDef>();
				spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == spawn_kind_def.race && !x.defName.Contains("Nymph")));
				//no other PawnKindDef found try mother
				if (spawn_kind_def_list.NullOrEmpty())
					spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == mother.kindDef.race && !x.defName.Contains("Nymph")));
				//no other PawnKindDef found try implanter
				if (spawn_kind_def_list.NullOrEmpty() && implanter != null)
					spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == implanter.kindDef.race && !x.defName.Contains("Nymph")));
				//no other PawnKindDef found fallback to generic colonist
				if (spawn_kind_def_list.NullOrEmpty())
					spawn_kind_def = PawnKindDefOf.Colonist;

				spawn_kind_def = spawn_kind_def_list.RandomElement();
			}

			return spawn_kind_def;
		}

		private void ProcessPostBirth(Pawn mother, Pawn baby)
		{
			if (mother.Spawned)
			{
				// Spawn guck
				if (mother.caller != null)
				{
					mother.caller.DoCall();
				}
				if (baby != null)
				{
					if (baby.caller != null)
					{
						baby.caller.DoCall();
					}
				}
				FilthMaker.TryMakeFilth(mother.Position, mother.Map, ThingDefOf.Filth_AmnioticFluid, mother.LabelIndefinite(), 5);
				int howmuch = xxx.has_quirk(mother, "Incubator") ? Rand.Range(1, 3) * 2 : Rand.Range(1, 3);
				int i = 0;
				if (xxx.is_insect(baby) || xxx.is_insect(mother) || xxx.is_insect(implanter) || xxx.is_insect(father))
				{
					while (i++ < howmuch)
					{
						Thing jelly = ThingMaker.MakeThing(ThingDefOf.InsectJelly);
						if (mother.MapHeld?.areaManager?.Home[mother.PositionHeld] == false)
							jelly.SetForbidden(true, false);
						GenPlace.TryPlaceThing(jelly, mother.PositionHeld, mother.MapHeld, ThingPlaceMode.Near);
					}
				}
			}
		}

		/// <summary>
		/// Pops out eggs that are not fertilized.
		/// </summary>
		/// <param name="mother"></param>
		private void BirthUnfertilizedEggs(Pawn mother)
		{
			if (RJWSettings.DevMode) ModLog.Message($"{mother} is giving 'birth' to unfertilized Insect-Eggs.");

			if (PawnUtility.ShouldSendNotificationAbout(pawn) && (pawn.IsColonist || pawn.IsPrisonerOfColony))
			{
				string key = "EggDead";
				string text = TranslatorFormattedStringExtensions.Translate(key, pawn.LabelIndefinite()).CapitalizeFirst();
				Messages.Message(text, pawn, MessageTypeDefOf.SituationResolved);
			}
			Thing egg;
			var UEgg = DefDatabase<ThingDef>.GetNamedSilentFail(HediffDefOfEgg.UnFertEggDef);
			if (UEgg == null)
				UEgg = (DefDatabase<ThingDef>.GetNamedSilentFail("RJW_EggInsectUnfertilized"));
			egg = ThingMaker.MakeThing(UEgg);

			if (mother.MapHeld?.areaManager?.Home[mother.PositionHeld] == false)
				egg.SetForbidden(true, false);
			GenPlace.TryPlaceThing(egg, mother.PositionHeld, mother.MapHeld, ThingPlaceMode.Near);
		}

		private Pawn ProcessHumanLikeInsectEgg(Pawn mother, PawnKindDef spawn_kind_def, Faction spawn_faction)
		{
			ModLog.Message("Hediff_InsectEgg::BirthBaby() " + spawn_kind_def + " of " + spawn_faction + " in " + (int)(50 * implanter.GetStatValue(StatDefOf.PsychicSensitivity)) + " chance!");

			PawnGenerationRequest request = new PawnGenerationRequest(
				kind: spawn_kind_def,
				faction: spawn_faction,
				forceGenerateNewPawn: true,
				developmentalStages: DevelopmentalStage.Newborn,
				allowDowned: true,
				canGeneratePawnRelations: false,
				colonistRelationChanceFactor: 0,
				allowFood: false,
				allowAddictions: false,
				relationWithExtraPawnChanceFactor: 0,
				//forceNoIdeo: true,
				forbidAnyTitle: true,
				forceNoBackstory: true);

			Pawn baby = PawnGenerator.GeneratePawn(request);
			// If we have genes, and the baby has not set any, add the ones stored from fertilization as endogenes
			if (RJWPregnancySettings.egg_pregnancy_genes && eggGenes != null && eggGenes.Count > 0)
			{
				if (RJWSettings.DevMode) ModLog.Message($"Setting {eggGenes.Count} genes for {baby}");
				foreach (var gene in baby.genes.GenesListForReading)
					baby.genes.RemoveGene(gene);
				foreach (var gene in eggGenes)
					baby.genes.AddGene(gene, false);
			} else
			{
				if (RJWSettings.DevMode) ModLog.Message($"Did not find genes for {baby}");
			}

			DetermineIfBabyIsPrisonerOrSlave(mother, baby);

			SetBabyRelations(baby,implanter,father, mother);

			PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother);

			if (spawn_faction == Faction.OfInsects || (spawn_faction != null && (spawn_faction.def.defName.Contains("insect") || spawn_faction == implanter.Faction)))
			{
				Lord lord = implanter.GetLord();
				if (lord != null)
				{
					lord.AddPawn(baby);
				}
			}

			Hediff_BasePregnancy.BabyPostBirth(mother, father, baby);
			Sexualizer.sexualize_pawn(baby);

			// Move the baby in front of the mother, rather than on top
			if (mother.Spawned)
				if (mother.CurrentBed() != null)
				{
					baby.Position = baby.Position + new IntVec3(0, 0, 1).RotatedBy(mother.CurrentBed().Rotation);
				}

			return baby;
		}

		private void SetBabyRelations(Pawn Baby, Pawn EggProducer, Pawn Fertilizer, Pawn Host)
		{
			if (RJWSettings.DevMode) ModLog.Message($"Setting Relations for {Baby}: Egg-Mother={EggProducer}, Egg-Fertilizer={Fertilizer}, Egg-Host={Host}");
            if (!RJWSettings.Disable_egg_pregnancy_relations)
				if (Baby.RaceProps.IsFlesh)
				{
					if (EggProducer != null)
						Baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, EggProducer);
					// Note: Depending on the Sex of the Fertilizer, it's labelled as "Mother" too.
					if (Fertilizer != null)
						Baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, Fertilizer);
					if (Host != null && Host != EggProducer)
						Baby.relations.AddDirectRelation(PawnRelationDefOf.ParentBirth, Host);
				}
		}

		private static void DetermineIfBabyIsPrisonerOrSlave(Pawn mother, Pawn baby)
		{
			if (mother.IsSlaveOfColony)
			{
				if (mother.SlaveFaction != null)
					baby.SetFaction(mother.SlaveFaction);
				else if (mother.HomeFaction != null)
					baby.SetFaction(mother.HomeFaction);
				else if (mother.Faction != null)
					baby.SetFaction(mother.Faction);
				else
					baby.SetFaction(Faction.OfPlayer);
				baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Slave);
			}
			else if (mother.IsPrisonerOfColony)
			{
				if (mother.HomeFaction != null)
					baby.SetFaction(mother.HomeFaction);
				baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Prisoner);
			}
		}

		private void BirthFertilizedAnimalEgg(Pawn mother, PawnKindDef spawn_kind_def)
		{
			if (RJWSettings.DevMode) ModLog.Message($"{mother} is birthing an fertilized egg of {spawn_kind_def} ");

			Thing egg;
			ThingDef EggDef;
			string childrendef = "";
			PawnKindDef children = null;

			//make egg
			EggDef = DefDatabase<ThingDef>.GetNamedSilentFail(HediffDefOfEgg.FertEggDef);			//try to find predefined
			if (EggDef == null)                                                                     //fail, use generic
				EggDef = (DefDatabase<ThingDef>.GetNamedSilentFail("RJW_EggInsectFertilized"));
			egg = ThingMaker.MakeThing(EggDef);

			//make child
			List<string> childlist = new List<string>();
			if (!HediffDefOfEgg.childrenDefs.NullOrEmpty())
			{
				foreach (var child in HediffDefOfEgg.childrenDefs)
				{
					if (DefDatabase<PawnKindDef>.GetNamedSilentFail(child) != null)
						childlist.AddDistinct(child);
				}
				childrendef = childlist.RandomElement();                                                    //try to find predefined
			}
			if (!childrendef.NullOrEmpty())
				children = DefDatabase<PawnKindDef>.GetNamedSilentFail(childrendef);

			if (children == null)                                                                           //use fatherDef
				children = spawn_kind_def;

			//put child into egg
			if (children != null)
			{
				var t = egg.TryGetComp<CompHatcher>();
				t.Props.hatcherPawn = children;
				t.hatcheeParent = implanter;
				t.otherParent = father;
				t.hatcheeFaction = implanter.Faction;
			}

			if (mother.MapHeld?.areaManager?.Home[mother.PositionHeld] == false)
				egg.SetForbidden(true, false);

			//poop egg
			GenPlace.TryPlaceThing(egg, mother.PositionHeld, mother.MapHeld, ThingPlaceMode.Near);
		}

		private Faction DetermineSpawnFaction(Pawn mother)
		{
			Faction spawn_faction;

			//core Hive Insects... probably
			if (implanter.Faction == Faction.OfInsects || father.Faction == Faction.OfInsects || mother.Faction == Faction.OfInsects)
			{
				spawn_faction = Faction.OfInsects;
				int chance = 5;

				//random chance to make insect neutral/tamable
				if (father.Faction == Faction.OfInsects)
					chance = 5;
				if (father.Faction != Faction.OfInsects)
					chance = 10;
				if (father.Faction == Faction.OfPlayer)
					chance = 25;
				if (implanter.Faction == Faction.OfPlayer)
					chance += 25;
				if (implanter.Faction == Faction.OfPlayer && xxx.is_human(implanter))
					chance += (int)(25 * implanter.GetStatValue(StatDefOf.PsychicSensitivity));
				if (Rand.Range(0, 100) <= chance)
					spawn_faction = null;

				//chance tame insect on birth
				if (spawn_faction == null)
					if (implanter.Faction == Faction.OfPlayer && xxx.is_human(implanter))
						if (Rand.Range(0, 100) <= (int)(50 * implanter.GetStatValue(StatDefOf.PsychicSensitivity)))
							spawn_faction = Faction.OfPlayer;
			}
			//humanlikes
			else if (xxx.is_human(implanter))
			{
				spawn_faction = implanter.Faction;
			}
			//animal, spawn implanter faction (if not player faction/not tamed)
			else if (!xxx.is_human(implanter) && !(implanter.Faction?.IsPlayer ?? false))
			{
				spawn_faction = implanter.Faction;
			}
			//spawn factionless(tamable, probably)
			else
			{
				spawn_faction = null;
			}

			return spawn_faction;
		}

		/// <summary>
		/// Sets the Father of an egg.
		/// If possible, the genes of the baby are determined too and stored in the egg-pregnancy.
		/// Exception is for androids or immortals (mod content).
		/// </summary>
		/// <param name="Pawn"> Pawn to be set as Father </param>
		public void Fertilize(Pawn Pawn)
		{
			if (implanter == null)
			{
				return;
			}
			if (xxx.ImmortalsIsActive && (Pawn.health.hediffSet.HasHediff(xxx.IH_Immortal) || pawn.health.hediffSet.HasHediff(xxx.IH_Immortal)))
			{
				return;
			}
			if (AndroidsCompatibility.IsAndroid(pawn))
			{
				return;
			}

			if (!fertilized && canbefertilized && GestationProgress < 0.5)
			{
				if (RJWSettings.DevMode) ModLog.Message($"{xxx.get_pawnname(pawn)} had eggs inside fertilized (Fertilized by {xxx.get_pawnname(father)}): {this}");
				father = Pawn;
				ChangeEgg(implanter);
			}

			// If both parents are human / have genes, set the genes of the offspring
			if (RJWPregnancySettings.egg_pregnancy_genes && father != null && implanter != null && father.genes != null && implanter.genes != null)
			{
				if (RJWSettings.DevMode) ModLog.Message($"Setting InsectEgg-Genes for {this}");
				DetermineChildGenes();
			} else
			{
				if (RJWSettings.DevMode) ModLog.Message($"Could not set Genes for {this}");
			}
		}

		/// <summary>
		/// Sets the genes of the child if both Implanter and Fertilizer have genes.
		/// Intentionally in it's own method for easier access with Harmony Patches.
		/// </summary>
		private void DetermineChildGenes()
		{
			eggGenes = PregnancyUtility.GetInheritedGenes(father, implanter);
			if (RJWSettings.DevMode) ModLog.Message($"Set Genes for {this} from {father} & {implanter} - total of {eggGenes.Count} genes set.");
		}

		/// <summary>
		/// Sets the Implanter and thus the base-egg type.
		/// If the egg is Self-Fertilized, the egg also gets fertilized (with Implanter as father, too).
		/// </summary>
		/// <param name="Pawn">Pawn to be implanter.</param>
		public void Implanter(Pawn Pawn)
		{
			if (implanter == null)
			{
				if (RJWSettings.DevMode) ModLog.Message("Hediff_InsectEgg:: set implanter:" + xxx.get_pawnname(Pawn));
				implanter = Pawn;
				ChangeEgg(implanter);

				if (!implanter.health.hediffSet.HasHediff(xxx.sterilized))
				{
					if (HediffDefOfEgg.selffertilized)
						Fertilize(implanter);
				}
				else
					canbefertilized = false;
			}
		}

		//Change egg type after implanting/fertilizing
		public void ChangeEgg(Pawn Pawn)
		{
			if (Pawn != null)
			{
				float p_end_tick_mods = 1;

				if (Pawn.RaceProps?.gestationPeriodDays < 1)
				{
					p_end_tick_mods = GenDate.TicksPerDay;
				}
				else
				{
					p_end_tick_mods = Pawn.RaceProps.gestationPeriodDays * GenDate.TicksPerDay;
				}

				if (xxx.has_quirk(pawn, "Incubator") || pawn.health.hediffSet.HasHediff(HediffDef.Named("FertilityEnhancer")))
					p_end_tick_mods /= 2f;

				p_end_tick_mods *= RJWPregnancySettings.egg_pregnancy_duration;

				p_start_tick = Find.TickManager.TicksGame;
				p_end_tick = p_start_tick + p_end_tick_mods;
				lastTick = p_start_tick;

				eggssize = Pawn.RaceProps.baseBodySize / 5;
				if (!Genital_Helper.has_ovipositorF(pawn)) // non ovi egg full size
					Severity = eggssize;
				else if (eggssize > 0.1f) // cap egg size in ovi to 10%
					Severity = 0.1f;
				else
					Severity = eggssize;
			}
		}

		//for setting implanter/fertilize eggs
		public bool IsParent(Pawn parent)
		{
			//anyone can fertilize
			if (RJWPregnancySettings.egg_pregnancy_fertilize_anyone) return true;

			//only set egg parent or implanter can fertilize
			else return parentDef == parent.kindDef.defName
						|| parentDefs.Contains(parent.kindDef.defName)
						|| implanter.kindDef == parent.kindDef; // unknown eggs
		}

		public override string DebugString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(base.DebugString());
			stringBuilder.AppendLine(" Gestation progress: " + GestationProgress.ToStringPercent());
			if (RJWSettings.DevMode) stringBuilder.AppendLine(" Implanter: " + xxx.get_pawnname(implanter));
			if (RJWSettings.DevMode) stringBuilder.AppendLine(" Father: " + xxx.get_pawnname(father));
			return stringBuilder.ToString();
		}
	}
}


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

namespace rjw
{
	public class Hediff_Orgasm : HediffWithComps
	{
		public override void PostAdd(DamageInfo? dinfo)
		{
			string key = "FeltOrgasm";
			string text = TranslatorFormattedStringExtensions.Translate(key, pawn.LabelIndefinite()).CapitalizeFirst();
			Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);
		}
	}

	public class Hediff_TransportCums : HediffWithComps
	{
		public override void PostAdd(DamageInfo? dinfo)
		{
			if (pawn.gender == Gender.Female)
			{
				string key = "CumsTransported";
				string text = TranslatorFormattedStringExtensions.Translate(key, pawn.LabelIndefinite()).CapitalizeFirst();
				Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);
				PawnGenerationRequest req = new PawnGenerationRequest(PawnKindDefOf.Drifter, fixedGender:Gender.Male );
				Pawn cumSender = PawnGenerator.GeneratePawn(req);
				Find.WorldPawns.PassToWorld(cumSender);
				//Pawn cumSender = (from p in Find.WorldPawns.AllPawnsAlive where p.gender == Gender.Male select p).RandomElement<Pawn>();
				//--ModLog.Message("" + this.GetType().ToString() + "PostAdd() - Sending " + xxx.get_pawnname(cumSender) + "'s cum into " + xxx.get_pawnname(pawn) + "'s vagina");
				//PregnancyHelper.impregnate(pawn, cumSender, xxx.rjwSextype.Vaginal);
			}
			pawn.health.RemoveHediff(this);
		}
	}

	public class Hediff_TransportEggs : HediffWithComps
	{
		public override void PostAdd(DamageInfo? dinfo)
		{
			if (pawn.gender == Gender.Female)
			{
				string key = "EggsTransported";
				string text = TranslatorFormattedStringExtensions.Translate(key, pawn.LabelIndefinite()).CapitalizeFirst();
				Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);

				PawnKindDef spawn = PawnKindDefOf.Megascarab;
				PawnGenerationRequest req1 = new PawnGenerationRequest(spawn, fixedGender:Gender.Female );
				PawnGenerationRequest req2 = new PawnGenerationRequest(spawn, fixedGender:Gender.Male );
				Pawn implanter = PawnGenerator.GeneratePawn(req1);
				Pawn fertilizer = PawnGenerator.GeneratePawn(req2);
				Find.WorldPawns.PassToWorld(implanter);
				Find.WorldPawns.PassToWorld(fertilizer);
				Sexualizer.sexualize_pawn(implanter);
				Sexualizer.sexualize_pawn(fertilizer);
				//Pawn cumSender = (from p in Find.WorldPawns.AllPawnsAlive where p.gender == Gender.Male select p).RandomElement<Pawn>();
				//--ModLog.Message("" + this.GetType().ToString() + "PostAdd() - Sending " + xxx.get_pawnname(cumSender) + "'s cum into " + xxx.get_pawnname(pawn) + "'s vagina");
				//PregnancyHelper.impregnate(pawn, implanter, xxx.rjwSextype.Vaginal);
				//PregnancyHelper.impregnate(pawn, fertilizer, xxx.rjwSextype.Vaginal);
			}
			pawn.health.RemoveHediff(this);
		}
	}
}

repo_name: Timura/rjw

license: mit


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

namespace rjw
{
	internal class Hediff_MechImplants : HediffWithComps
	{
		public override bool TryMergeWith(Hediff other)
		{
			return false;
		}
	}
}


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

namespace rjw
{
	///<summary>
	///This hediff class simulates pregnancy with mechanoids, mother may be human. It is not intended to be reasonable.
	///Differences from bestial pregnancy are that ... it is lethal
	///TODO: extend with something "friendlier"? than Mech_Scyther.... two Mech_Scyther's? muhahaha
	///</summary>
	[RJWAssociatedHediff("RJW_pregnancy_mech")]
	public class Hediff_MechanoidPregnancy : Hediff_BasePregnancy
	{
		public override bool canBeAborted
		{
			get
			{
				return false;
			}
		}

		public override bool canMiscarry
		{
			get
			{
				return false;
			}
		}

		public override void PregnancyMessage()
		{
			string message_title = "RJW_PregnantTitle".Translate(pawn.LabelIndefinite()).CapitalizeFirst();
			string message_text1 = "RJW_PregnantText".Translate(pawn.LabelIndefinite()).CapitalizeFirst();
			string message_text2 = "RJW_PregnantMechStrange".Translate();
			Find.LetterStack.ReceiveLetter(message_title, message_text1 + "\n" + message_text2, LetterDefOf.ThreatBig, pawn);
		}

		public void Hack()
		{
			is_hacked = true;
		}

		public override void Notify_PawnDied()
		{
			base.Notify_PawnDied();
			GiveBirth();
		}

		//Handles the spawning of pawns
		public override void GiveBirth()
		{
			Pawn mother = pawn;
			if (mother == null)
				return;

			if (!babies.NullOrEmpty())
			{
				foreach (Pawn baby in babies)
				{
					baby.Destroy();
					baby.Discard(true);
				}
				babies.Clear();
			}

			Faction spawn_faction = null;

			if (!is_hacked)
				spawn_faction = Faction.OfMechanoids;
			else if (ModsConfig.BiotechActive)
				spawn_faction = Faction.OfPlayer;

			PawnKindDef children = null;

			if (RJWSettings.DevMode) ModLog.Message(xxx.get_pawnname(pawn) + " birth:" + this.ToString());

			foreach (HediffDef_MechImplants implant in DefDatabase<HediffDef_MechImplants>.AllDefs.Where(x => x.parentDefs.Contains(father.kindDef.ToString())))         //try to find predefined
			{
				string childrendef;																			//try to find predefined
				List<string> childlist = new List<string>();
				if (!implant.childrenDefs.NullOrEmpty())
				{
					foreach (var child in (implant.childrenDefs))
					{
						if (DefDatabase<PawnKindDef>.GetNamedSilentFail(child) != null)
							childlist.AddDistinct(child);
					}
					childrendef = childlist.RandomElement();												//try to find predefined
					children = DefDatabase<PawnKindDef>.GetNamedSilentFail(childrendef);
					if (children != null)
						continue;
				}
			}

			if (children == null)                                                                           //fallback, use fatherDef
				children = father.kindDef;

			PawnGenerationRequest request = new PawnGenerationRequest(
				kind: children,
				faction: spawn_faction,
				forceGenerateNewPawn: true,
				developmentalStages: DevelopmentalStage.Newborn
				);

			Pawn mech = PawnGenerator.GeneratePawn(request);
			PawnUtility.TrySpawnHatchedOrBornPawn(mech, mother);
			if (!is_hacked)
			{
				LordJob_MechanoidsDefend lordJob = new LordJob_MechanoidsDefend();
				Lord lord = LordMaker.MakeNewLord(mech.Faction, lordJob, mech.Map);
				lord.AddPawn(mech);
			}
			FilthMaker.TryMakeFilth(mech.PositionHeld, mech.MapHeld, mother.RaceProps.BloodDef, mother.LabelIndefinite());

			IEnumerable<BodyPartRecord> source = from x in mother.health.hediffSet.GetNotMissingParts() where
												x.IsInGroup(BodyPartGroupDefOf.Torso)
												&& !x.IsCorePart
												//&& x.groups.Contains(BodyPartGroupDefOf.Torso)
												//&& x.depth == BodyPartDepth.Inside
												//&& x.height == BodyPartHeight.Bottom
												//someday include depth filter
												//so it doesn't cut out external organs (breasts)?
												//vag  is genital part and genital is external
												//anal is internal
												//make sep part of vag?
												//&& x.depth == BodyPartDepth.Inside
												select x;

			if (source.Any())
			{
				foreach (BodyPartRecord part in source)
				{
					mother.health.DropBloodFilth();
				}
				if (RJWPregnancySettings.safer_mechanoid_pregnancy && is_checked)
				{
					foreach (BodyPartRecord part in source)
					{
						DamageDef surgicalCut = DamageDefOf.SurgicalCut;
						float amount = 5f;
						float armorPenetration = 999f;
						mother.TakeDamage(new DamageInfo(surgicalCut, amount, armorPenetration, -1f, null, part, null, DamageInfo.SourceCategory.ThingOrUnknown, null));
					}
				}
				else
				{
					foreach (BodyPartRecord part in source)
					{
						Hediff_MissingPart hediff_MissingPart = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, mother, part);
						hediff_MissingPart.lastInjury = HediffDefOf.Cut;
						hediff_MissingPart.IsFresh = true;
						mother.health.AddHediff(hediff_MissingPart);
					}
				}
			}
			mother.health.RemoveHediff(this);
		}
	}
}


code:
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using Multiplayer.API;

namespace rjw
{
	internal class Hediff_Parasite : Hediff_Pregnant
	{
		[SyncMethod]
		new public static void DoBirthSpawn(Pawn mother, Pawn father)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			int num = (mother.RaceProps.litterSizeCurve == null) ? 1 : Mathf.RoundToInt(Rand.ByCurve(mother.RaceProps.litterSizeCurve));
			if (num < 1)
			{
				num = 1;
			}
			PawnGenerationRequest request = new PawnGenerationRequest(
					kind: father.kindDef,
					faction: father.Faction,
					forceGenerateNewPawn: true,
					developmentalStages: DevelopmentalStage.Newborn,
					allowDowned: true,
					canGeneratePawnRelations: false,
					mustBeCapableOfViolence: true,
					colonistRelationChanceFactor: 0,
					allowFood: false,
					allowAddictions: false,
					relationWithExtraPawnChanceFactor: 0
				);

			Pawn pawn = null;
			for (int i = 0; i < num; i++)
			{
				pawn = PawnGenerator.GeneratePawn(request);
				if (PawnUtility.TrySpawnHatchedOrBornPawn(pawn, mother))
				{
					if (pawn.playerSettings != null && mother.playerSettings != null)
					{
						pawn.playerSettings.AreaRestriction = father.playerSettings.AreaRestriction;
					}
					if (pawn.RaceProps.IsFlesh)
					{
						pawn.relations.AddDirectRelation(PawnRelationDefOf.Parent, mother);
						if (father != null)
						{
							pawn.relations.AddDirectRelation(PawnRelationDefOf.Parent, father);
						}
					}
				}
				else
				{
					Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Discard);
				}
			}
			if (mother.Spawned)
			{
				FilthMaker.TryMakeFilth(mother.Position, mother.Map, ThingDefOf.Filth_AmnioticFluid, mother.LabelIndefinite(), 5);
				if (mother.caller != null)
				{
					mother.caller.DoCall();
				}
				if (pawn.caller != null)
				{
					pawn.caller.DoCall();
				}
			}
		}
	}
}


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

namespace rjw
{
	public static class AgeStage
	{
		public const int Baby = 0;
		public const int Toddler = 1;
		public const int Child = 2;
		public const int Teenager = 3;
		public const int Adult = 4;
	}

	public class Hediff_SimpleBaby : HediffWithComps
	{
		// Keeps track of what stage the pawn has grown to
		private int grown_to = 0;
		private int stage_completed = 0;
		public float lastTick = 0;
		public float dayTick = 0;

		//Properties
		public int Grown_To
		{
			get
			{
				return grown_to;
			}
		}

		public override string DebugString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(base.DebugString());
			stringBuilder.Append("Current CurLifeStageIndex: " + pawn.ageTracker.CurLifeStageIndex + ", grown_to: " + grown_to);
			return stringBuilder.ToString();
		}

		public override void PostMake()
		{
			Severity = Math.Max(0.01f, Severity);
			if (grown_to == 0 && !pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_NoManipulationFlag")))
			{
				pawn.health.AddHediff(HediffDef.Named("RJW_NoManipulationFlag"), null, null);
			}
			base.PostMake();

			if (!pawn.RaceProps.lifeStageAges.Any(x => x.def.reproductive))
			{
				if (pawn.health.hediffSet.HasHediff(xxx.RJW_NoManipulationFlag))
				{
					pawn.health.hediffSet.hediffs.Remove(pawn.health.hediffSet.GetFirstHediffOfDef(xxx.RJW_NoManipulationFlag));
				}
				pawn.health.RemoveHediff(this);
			}
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref grown_to, "grown_to", 0);
			Scribe_Values.Look(ref lastTick, "lastTick", 0);
			Scribe_Values.Look(ref dayTick, "dayTick", 0);
		}

		internal void GrowUpTo(int stage)
		{
			GrowUpTo(stage, true);
		}

		[SyncMethod]
		internal void GrowUpTo(int stage, bool generated)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (grown_to < stage)
				grown_to = stage;

			// Update the Colonist Bar
			PortraitsCache.SetDirty(pawn);
			pawn.Drawer.renderer.graphics.ResolveAllGraphics();

			// At the toddler stage. Now we can move and talk.
			if ((stage >= 1 || pawn.ageTracker.CurLifeStage.reproductive) && stage_completed < 1)
			{
				Severity = Math.Max(0.5f, Severity);
				stage_completed++;
			}
			// Re-enable skills that were locked out from toddlers
			if ((stage >= 2 || pawn.ageTracker.CurLifeStage.reproductive) && stage_completed < 2)
			{
				if (!generated)
				{
					if (xxx.RimWorldChildrenIsActive)
					{
						pawn.story.Childhood = DefDatabase<BackstoryDef>.GetNamed("CustomBackstory_Rimchild");
					}
					// Remove the hidden hediff stopping pawns from manipulating
				}
				if (pawn.health.hediffSet.HasHediff(xxx.RJW_NoManipulationFlag))
				{
					pawn.health.hediffSet.hediffs.Remove(pawn.health.hediffSet.GetFirstHediffOfDef(xxx.RJW_NoManipulationFlag));
				}
				Severity = Math.Max(0.75f, Severity);
				stage_completed++;
			}
			// The child has grown to a teenager so we no longer need this effect
			if ((stage >= 3 || pawn.ageTracker.CurLifeStage.reproductive) && stage_completed < 3)
			{
				if (!generated && pawn.story.Childhood != null && pawn.story.Childhood.title == "Child")
				{
					if (xxx.RimWorldChildrenIsActive)
					{
						pawn.story.Childhood = DefDatabase<BackstoryDef>.GetNamed("CustomBackstory_Rimchild");
					}
				}

				// Gain traits from life experiences
				if (pawn.story.traits.allTraits.Count < 3)
				{
					List<Trait> life_traitpool = new List<Trait>();
					// Try get cannibalism
					if (pawn.needs.mood.thoughts.memories.Memories.Find(x => x.def == ThoughtDefOf.AteHumanlikeMeatAsIngredient) != null)
					{
						life_traitpool.Add(new Trait(TraitDefOf.Cannibal, 0, false));
					}
					// Try to get bloodlust
					if (pawn.records.GetValue(RecordDefOf.KillsHumanlikes) > 0 || pawn.records.GetValue(RecordDefOf.AnimalsSlaughtered) >= 2)
					{
						life_traitpool.Add(new Trait(TraitDefOf.Bloodlust, 0, false));
					}
					// Try to get shooting accuracy
					if (pawn.records.GetValue(RecordDefOf.ShotsFired) > 100 && (int)pawn.records.GetValue(RecordDefOf.PawnsDowned) > 0)
					{
						life_traitpool.Add(new Trait(TraitDef.Named("ShootingAccuracy"), 1, false));
					}
					else if (pawn.records.GetValue(RecordDefOf.ShotsFired) > 100 && (int)pawn.records.GetValue(RecordDefOf.PawnsDowned) == 0)
					{
						life_traitpool.Add(new Trait(TraitDef.Named("ShootingAccuracy"), -1, false));
					}
					// Try to get brawler
					else if (pawn.records.GetValue(RecordDefOf.ShotsFired) < 15 && pawn.records.GetValue(RecordDefOf.PawnsDowned) > 1)
					{
						life_traitpool.Add(new Trait(TraitDefOf.Brawler, 0, false));
					}
					// Try to get necrophiliac
					if (pawn.records.GetValue(RecordDefOf.CorpsesBuried) > 50)
					{
						life_traitpool.Add(new Trait(xxx.necrophiliac, 0, false));
					}
					// Try to get nymphomaniac
					if (pawn.records.GetValue(RecordDefOf.BodiesStripped) > 50)
					{
						life_traitpool.Add(new Trait(xxx.nymphomaniac, 0, false));
					}
					// Try to get rapist
					if (pawn.records.GetValue(RecordDefOf.TimeAsPrisoner) > 300)
					{
						life_traitpool.Add(new Trait(xxx.rapist, 0, false));
					}
					// Try to get Dislikes Men/Women
					int male_rivals = 0;
					int female_rivals = 0;
					foreach (Pawn colinist in Find.AnyPlayerHomeMap.mapPawns.AllPawnsSpawned)
					{
						if (pawn.relations.OpinionOf(colinist) <= -20)
						{
							if (colinist.gender == Gender.Male)
								male_rivals++;
							else
								female_rivals++;
						}
					}
					// Find which gender we hate
					if (male_rivals > 3 || female_rivals > 3)
					{
						if (male_rivals > female_rivals)
							life_traitpool.Add(new Trait(TraitDefOf.DislikesMen, 0, false));
						else if (female_rivals > male_rivals)
							life_traitpool.Add(new Trait(TraitDefOf.DislikesWomen, 0, false));
					}
					// Pyromaniac never put out any fires. Seems kinda stupid
					/*if ((int)pawn.records.GetValue (RecordDefOf.FiresExtinguished) == 0) {
						life_traitpool.Add (new Trait (TraitDefOf.Pyromaniac, 0, false));
					}*/
					// Neurotic
					if (pawn.records.GetValue(RecordDefOf.TimesInMentalState) > 6)
					{
						life_traitpool.Add(new Trait(TraitDef.Named("Neurotic"), 2, false));
					}
					else if (pawn.records.GetValue(RecordDefOf.TimesInMentalState) > 3)
					{
						life_traitpool.Add(new Trait(TraitDef.Named("Neurotic"), 1, false));
					}

					// People(male or female) can turn gay during puberty
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					if (Rand.Value <= 0.03f && pawn.story.traits.allTraits.Count <= 3)
					{
						pawn.story.traits.GainTrait(new Trait(TraitDefOf.Gay, 0, true));
					}

					// Now let's try to add some life experience traits
					if (life_traitpool.Count > 0)
					{
						int i = 3;
						while (pawn.story.traits.allTraits.Count < 3 && i > 0)
						{
							Trait newtrait = life_traitpool.RandomElement();
							if (pawn.story.traits.HasTrait(newtrait.def) == false)
								pawn.story.traits.GainTrait(newtrait);
							i--;
						}
					}
				}

				stage_completed++;
				pawn.health.RemoveHediff(this);
			}
		}

		//everyday grow 1 year until reproductive
		public void GrowFast()
		{
			if (RJWPregnancySettings.phantasy_pregnancy)
			{
				if (!pawn.ageTracker.CurLifeStage.reproductive || pawn.ageTracker.Growth >= 1)
				{
					pawn.ageTracker.AgeBiologicalTicks = (pawn.ageTracker.AgeBiologicalYears + 1) * GenDate.TicksPerYear + 1;
					pawn.ageTracker.DebugForceBirthdayBiological();
					GrowUpTo(pawn.ageTracker.CurLifeStageIndex, false);
				}
			}
		}

		public void TickRare()
		{
			//--ModLog.Message("Hediff_SimpleBaby::TickRare is called");
			if (pawn.ageTracker.CurLifeStageIndex > Grown_To)
			{
				GrowUpTo(pawn.ageTracker.CurLifeStageIndex, false);
			}

			// Update the graphics set
			//if (pawn.ageTracker.CurLifeStageIndex >= AgeStage.Toddler)
			//	pawn.Drawer.renderer.graphics.ResolveAllGraphics();

			if (xxx.RimWorldChildrenIsActive)
			{
				//if (Prefs.DevMode)
				//	Log.Message("RJW child tick - CnP active");
				//we do not disable our hediff anymore
	//			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_NoManipulationFlag")))
				//{
				//	pawn.health.hediffSet.hediffs.Remove(pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_NoManipulationFlag")));
				//	pawn.health.AddHediff(HediffDef.Named("NoManipulationFlag"), null, null);
				//}
				//if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_BabyState")))
				//{
				//	pawn.health.hediffSet.hediffs.Remove(pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_BabyState")));
				//	pawn.health.AddHediff(HediffDef.Named("BabyState"), null, null);
	//				if (Prefs.DevMode) Log.Message("RJW_Babystate self-removing");
	//			}
				if (pawn.ageTracker.CurLifeStageIndex <= 1)
				{   //The UnhappBaby feature is not included in RJW, but will
					// Check if the baby is hungry, and if so, add the whiny baby hediff
					var hunger = pawn.needs.food;
					var joy = pawn.needs.joy;
					if ((joy != null)&&(hunger!=null))
					{ //There's no way to patch out the CnP adressing nill fields
						if (hunger.CurLevelPercentage<hunger.PercentageThreshHungry || joy.CurLevelPercentage <0.1)
						{
							if (!pawn.health.hediffSet.HasHediff(HediffDef.Named("UnhappyBaby"))){
								//--Log.Message("Adding unhappy baby hediff");
								pawn.health.AddHediff(HediffDef.Named("UnhappyBaby"), null, null);
							}
						}
					}
				}
			}
		}

		public override void Tick()
		{
			/*
			if (xxx.RimWorldChildrenIsActive)
			{
				if (pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_BabyState"))!=null)
				{
					pawn.health.RemoveHediff(this);
				}
				return;
			}
			*/
			//This void call every frame. should not logmes no reason
			//--ModLog.Message("Hediff_SimpleBaby::PostTick is called");

			base.Tick();
			if (pawn.Spawned)
			{
				var thisTick = Find.TickManager.TicksGame;

				if ((thisTick - dayTick) >= GenDate.TicksPerDay)
				{
					GrowFast();
					dayTick = thisTick;
				}
				if ((thisTick - lastTick) >= 250)
				{
					TickRare();
					lastTick = thisTick;
				}
			}
		}

		public override bool Visible
		{
			get { return false; }
		}
	}
}


code:
using System;

namespace rjw
{
	public class RJWAssociatedHediffAttribute : Attribute
	{
		public string defName { get; private set; }

		public RJWAssociatedHediffAttribute(string defName)
		{
			this.defName = defName;
		}
	}
}


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
			//straight, female (partner) recives egg insertion from other/sex starter (pawn)

			if (CouldBeEgging(props, giver, receiver, pawnparts, partnerparts))
			{
				//TODO: add widget toggle for bind all/neutral/hostile pawns
				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"));
							}

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

					List<Hediff_InsectEgg> eggs = new();
					props.partner.health.hediffSet.GetHediffs(ref 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);

					props.pawn.health.hediffSet.GetHediffs(ref eggs);
					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);
							foreach (var egg in eggs)
								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;
				}
			}
		}
	}
}


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