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,
					newborn: true,
					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;

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

		public override string DebugString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(base.DebugString());
			return stringBuilder.ToString();
		}

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

		public override void ExposeData()
		{
			//Scribe_Values.LookValue<int> (ref grown_to, "grown_to", 0);
			Scribe_Values.Look<int>(ref grown_to, "grown_to", 0);

			base.ExposeData();
		}

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

		[SyncMethod]
		internal void GrowUpTo(int stage, bool generated)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			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)
			{
				Severity = Math.Max(0.5f, Severity);
				//pawn.needs.food.
			}
			// Re-enable skills that were locked out from toddlers
			if (stage == 2)
			{
				if (!generated)
				{
					if (xxx.RimWorldChildrenIsActive)
					{
						pawn.story.childhood = BackstoryDatabase.allBackstories["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);
			}
			// The child has grown to a teenager so we no longer need this effect
			if (stage == 3)
			{
				if (!generated && pawn.story.childhood != null && pawn.story.childhood.title == "Child")
				{
					if (xxx.RimWorldChildrenIsActive)
					{
						pawn.story.childhood = BackstoryDatabase.allBackstories["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--;
						}
					}
				}

				pawn.health.RemoveHediff(this);
			}
		}

		public void GrowFast()
		{
			if (RJWPregnancySettings.phantasy_pregnancy)
			{
				if (!pawn.ageTracker.CurLifeStage.reproductive && pawn.RaceProps.lifeStageAges.Any(x => x.def.reproductive))
				{
					pawn.ageTracker.DebugMake1YearOlder();
					if (pawn.ageTracker.CurLifeStageIndex != Grown_To)
						GrowUpTo(pawn.ageTracker.CurLifeStageIndex, false);
				}
			}
		}
		public void TickRare()
		{
			//--ModLog.Message("Hediff_SimpleBaby::TickRare is called");
			if (pawn.ageTracker.CurLifeStageIndex > Grown_To)
			{
				GrowUpTo(Grown_To + 1, 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 PostTick()
		{
			/*
			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.PostTick();
			if (pawn.Spawned)
			{
				if (pawn.IsHashIntervalTick(250))
				{
					TickRare();
				}
				if (pawn.IsHashIntervalTick(60000))
				{
					GrowFast();
				}
			}
		}

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

repo_name: ElToro/rjw

license: mit


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;

///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
	{
		//called by aftersex (including rape, breed, etc)
		//called by mcevent

		//pawn - "father"; partner = mother
		public static void impregnate(Pawn pawn, Pawn partner, xxx.rjwSextype sextype = xxx.rjwSextype.None)
		{

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

			//"mech" pregnancy
			if (sextype == xxx.rjwSextype.MechImplant)
			{
				if (RJWPregnancySettings.mechanoid_pregnancy_enabled)
				{
					var preg = GetPregnancy(partner);
					if (preg != null) // removing old pregnancies
					{
						if (preg is Hediff_MechanoidPregnancy)
						{
							if (RJWSettings.DevMode) ModLog.Message(" already pregnant by mechanoid");
							return;
						}

						if (preg is Hediff_BasePregnancy)
						{
							if (RJWSettings.DevMode) ModLog.Message(" removing rjw normal pregnancy");
							(preg as Hediff_BasePregnancy).Kill();
						}
						else
						{
							if (RJWSettings.DevMode) ModLog.Message(" removing vanilla or other mod pregnancy");
							partner.health.RemoveHediff(preg);
						}
					}

					// new pregnancy
					if (RJWSettings.DevMode) ModLog.Message(" mechanoid pregnancy started");
					Hediff_MechanoidPregnancy hediff = Hediff_BasePregnancy.Create<Hediff_MechanoidPregnancy>(partner, 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;
			}

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

			//ModLog.Message(" RaceImplantEggs()" + pawn.RaceImplantEggs());
			//"insect" pregnancy
			//straight, female (partner) recives egg insertion from other/sex starter (pawn)
			var pawnpartBPR = Genital_Helper.get_genitalsBPR(pawn);
			var pawnparts = Genital_Helper.get_PartsHediffList(pawn, pawnpartBPR);
			var partnerpartBPR = Genital_Helper.get_genitalsBPR(partner);
			var partnerparts = Genital_Helper.get_PartsHediffList(partner, partnerpartBPR);

			if (((sextype == xxx.rjwSextype.Vaginal && Genital_Helper.has_vagina(partner, partnerparts)) ||
				(AnalOk && Genital_Helper.has_anus(partner)) ||	(OralOk)) &&
					(Genital_Helper.has_ovipositorF(pawn, pawnparts) ||
						(Genital_Helper.has_ovipositorM(pawn, pawnparts) ||
							(Genital_Helper.has_penis_fertile(pawn, pawnparts) && (pawn.RaceImplantEggs() || partner.health.hediffSet.GetHediffs<Hediff_InsectEgg>().Any())))
					)
				)
				{
				//TODO: add widget toggle for bind all/neutral/hostile pawns
				if (pawn.HostileTo(partner) || partner.IsPrisonerOfColony || partner.health.hediffSet.HasHediff(xxx.submitting))
					//if (!pawn.IsColonist)
					if (!partner.health.hediffSet.HasHediff(HediffDef.Named("RJW_Cocoon")))
							//if (!partner.health.hediffSet.HasHediff(HediffDef.Named("RJW_Cocoon")) && pawn.Faction != partner.Faction)
							//if (!partner.health.hediffSet.HasHediff(HediffDef.Named("RJW_Cocoon")) && pawn.Faction != partner.Faction && pawn.HostileTo(partner))
							partner.health.AddHediff(HediffDef.Named("RJW_Cocoon"));

						DoEgg(pawn, partner, sextype);
					return;
				}
			//reverse, female (pawn) starts sex/passive bestiality and fills herself with eggs - this is likely fucked up and needs fixing at jobdriver, processsex and aftersex levels
			//else
			//if (Genital_Helper.has_vagina(pawn, pawnparts) &&
			//		(Genital_Helper.has_ovipositorF(partner, partnerparts) ||
			//			(Genital_Helper.has_ovipositorM(partner, partnerparts) ||
			//				(Genital_Helper.has_penis_fertile(partner, partnerparts) && pawn.RaceImplantEggs())))
			//	)
			//{
			//	DoEgg(partner, pawn);
			//	return;
			//}

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

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

			//futa-futa docking?
			//if (CanImpregnate(partner, pawn, sextype) && CanImpregnate(pawn, partner, sextype))
			//{
			//ModLog.Message(" futa-futa docking...");
			//return;
			//Doimpregnate(pawn, partner);
			//Doimpregnate(partner, pawn);
			//}
			//normal, when female is passive/recives interaction
			if (Genital_Helper.has_penis_fertile(pawn, pawnparts) && Genital_Helper.has_vagina(partner, partnerparts) && CanImpregnate(pawn, partner, sextype))
			{
				if (RJWSettings.DevMode) ModLog.Message(" impregnate forward");
				Doimpregnate(pawn, partner);
			}
			//reverse, when female active/starts interaction
			else if (Genital_Helper.has_vagina(pawn, pawnparts) && Genital_Helper.has_penis_fertile(partner, partnerparts) && CanImpregnate(partner, pawn, sextype))
			{
				if (RJWSettings.DevMode) ModLog.Message(" impregnate reverse");
				Doimpregnate(partner, pawn);
			}
		}

		public static Hediff GetPregnancy(Pawn pawn)
		{
			var set = pawn.health.hediffSet;

			Hediff preg =
				Hediff_BasePregnancy.KnownPregnancies()
				.Select(x => set.GetFirstHediffOfDef(HediffDef.Named(x)))
				.FirstOrDefault(x => x != null) ??
				set.GetFirstHediffOfDef(HediffDef.Named("Pregnant"));

			return preg;
		}

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

		[SyncMethod]
		public static void DoEgg(Pawn pawn, Pawn partner, xxx.rjwSextype sextype)
		{
			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(pawn) && !Genital_Helper.has_penis_fertile(pawn)) ||
					(Rand.Value > 0.5f && Genital_Helper.has_ovipositorF(pawn)))
					//penis eggs someday?
					//(Rand.Value > 0.5f && (Genital_Helper.has_ovipositorF(pawn) || Genital_Helper.has_penis_fertile(pawn) && pawn.RaceImplantEggs())))
				{
					float maxeggssize = (partner.BodySize / 5) * (xxx.has_quirk(partner, "Incubator") ? 2f : 1f) * (Genital_Helper.has_ovipositorF(partner) ? 2f : 1f);
					float eggedsize = 0;

					foreach (Hediff_InsectEgg egg in partner.health.hediffSet.GetHediffs<Hediff_InsectEgg>())
					{
						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(partner) + " size of eggs inside: " + eggedsize + ", max: " + maxeggssize);

					var eggs = pawn.health.hediffSet.GetHediffs<Hediff_InsectEgg>().ToList();
					BodyPartRecord partnerGenitals = null;

					if (sextype == xxx.rjwSextype.Anal)
						partnerGenitals = Genital_Helper.get_anusBPR(partner);
					else if (sextype == xxx.rjwSextype.Oral)
						partnerGenitals = Genital_Helper.get_stomachBPR(partner);
					else
						partnerGenitals = Genital_Helper.get_genitalsBPR(partner);

					var removepreg = true;
					while (eggs.Any() && eggedsize < maxeggssize)
					{
						if (removepreg && sextype == xxx.rjwSextype.Vaginal)
						{
							var preg = GetPregnancy(partner);
							if (preg != null) // removing old pregnancies
							{
								if (preg is Hediff_MechanoidPregnancy)
								{
									if (RJWSettings.DevMode) ModLog.Message(" egging - pregnant by mechanoid, skip");
								}

								if (preg is Hediff_BasePregnancy)
								{
									if (RJWSettings.DevMode) ModLog.Message(" egging - removing rjw normal pregnancy");
									(preg as Hediff_BasePregnancy).Kill();
								}
								else
								{
									if (RJWSettings.DevMode) ModLog.Message(" egging - removing vanilla or other mod pregnancy");
									partner.health.RemoveHediff(preg);
								}
								removepreg = false;
							}
						}

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

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

						egg.Implanter(pawn);

						eggedsize += egg.eggssize * (egg.def as HediffDef_InsectEgg).eggsize * RJWPregnancySettings.egg_pregnancy_eggs_size;
					}
				}
				//male or futa fertilize eggs
				else if (!pawn.health.hediffSet.HasHediff(xxx.sterilized))
				{
					if (Genital_Helper.has_penis_fertile(pawn))
						if ((Genital_Helper.has_ovipositorF(pawn) || Genital_Helper.has_ovipositorM(pawn)) || (pawn.health.capacities.GetLevel(xxx.reproduction) > 0))
						{
							foreach (var egg in partner.health.hediffSet.GetHediffs<Hediff_InsectEgg>())
								egg.Fertilize(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 fertility = RJWPregnancySettings.humanlike_impregnation_chance / 100f;
			if (xxx.is_animal(partner))
				fertility = RJWPregnancySettings.animal_impregnation_chance / 100f;
			// IUD fertility check
			if (partner.health.hediffSet.GetFirstHediffOfDef(DefDatabase<HediffDef>.GetNamed("RJW_IUD")) != null)
				fertility /= 99f;

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

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float ReproductionFactor = Math.Min(pawn.health.capacities.GetLevel(xxx.reproduction), partner.health.capacities.GetLevel(xxx.reproduction));
			float pregnancy_chance = fertility * ReproductionFactor;
			float pregnancy_roll_to_fail = Rand.Value;
			//BodyPartRecord torso = partner.RaceProps.body.AllParts.Find(x => x.def == BodyPartDefOf.Torso);

			if (pregnancy_roll_to_fail > pregnancy_chance || pregnancy_chance <= 0)
			{
				if (RJWSettings.DevMode) ModLog.Message(" Impregnation failed. Chance: " + pregnancy_chance.ToStringPercent() + " < " + pregnancy_roll_to_fail.ToStringPercent());
				return;
			}
			if (RJWSettings.DevMode) ModLog.Message(" Impregnation succeeded. Chance: " + pregnancy_chance.ToStringPercent() + " > " + pregnancy_roll_to_fail.ToStringPercent());

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

			if ((from x in fucked.health.hediffSet.GetHediffs<Hediff_InsectEgg>() 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 pawnpartBPR = Genital_Helper.get_genitalsBPR(fucker);
			var pawnparts = Genital_Helper.get_PartsHediffList(fucker, pawnpartBPR);
			var partnerpartBPR = Genital_Helper.get_genitalsBPR(fucked);
			var partnerparts = Genital_Helper.get_PartsHediffList(fucked, partnerpartBPR);
			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;
			}

			return true;
		}

		//Plant babies for human/bestiality pregnancy
		public static void PregnancyDecider(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
					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)
				{
					// cant fertilize eggs
				}
				else
					Hediff_BasePregnancy.Create<Hediff_BestialPregnancy>(mother, father);
			}
			//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 (mother.kindDef == father.kindDef)
						compEggLayer.Fertilize(father);
				}
				else if (RJWPregnancySettings.animal_pregnancy_enabled)
				{
					Hediff_BasePregnancy.Create<Hediff_BestialPregnancy>(mother, father);
				}
			}
		}

		//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 preg = GetPregnancy(target);
					if (preg != null)
					{
						if (preg is Hediff_BasePregnancy)
						{
							(preg as Hediff_BasePregnancy).Kill();
						}
						else
						{
							target.health.RemoveHediff(preg);
						}
					}
				}

				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 < 10)
			{
				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(HediffDef.Named("Pregnant"));
			if (h != null && h.ageTicks < 10)
			{
				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_checked) // Find checked pregnancies (should be visible pregnancies there?)
					.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 preg = (PregnancyHelper.GetPregnancy(pawn) as Hediff_BasePregnancy);

			if (preg != null)
			{
				preg.CheckPregnancy();
			}
			else
			{
				Messages.Message(xxx.get_pawnname(billDoer) + " has determined " + xxx.get_pawnname(pawn) + " is not pregnant.", MessageTypeDefOf.NeutralEvent);
			}
		}
	}
	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.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 preg = (PregnancyHelper.GetPregnancy(pawn) as Hediff_BasePregnancy);

			if (preg != null)
			{
				Messages.Message(xxx.get_pawnname(billDoer) + " has determined "+ xxx.get_pawnname(pawn) + " is pregnant and " + preg.father + " is the father.", MessageTypeDefOf.NeutralEvent);

				preg.CheckPregnancy();
			}
			else
			{
				Messages.Message(xxx.get_pawnname(billDoer) + " has determined " + xxx.get_pawnname(pawn) + " is not pregnant.", MessageTypeDefOf.NeutralEvent);
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// IUD - prevent pregnancy
	/// </summary>
	public class Recipe_InstallIUD : Recipe_InstallImplantToExistParts
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			if (!xxx.is_female(pawn))
			{
				return new List<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)
					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 RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_ItchyCrotch : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			int sev = std.genital_rash_severity(p);
			if (sev <= 0)
				return ThoughtState.Inactive;
			else if (sev == 1)
				return ThoughtState.ActiveAtStage(0);
			else
				return ThoughtState.ActiveAtStage(1);
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_SyphiliticThoughts : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			//Log.Message("0");
			var syp = p.health.hediffSet.GetFirstHediffOfDef(std.syphilis.hediff_def);
			//Log.Message("1");
			if (syp != null)
			{
				//Log.Message("2");
				if (syp.Severity >= 0.80f)
				{
					//Log.Message("3");
					return ThoughtState.ActiveAtStage(1);
				}
				else if (syp.Severity >= 0.50f)
				{
					//Log.Message("4");
					return ThoughtState.ActiveAtStage(0);
				}
				//Log.Message("5");
			}
			//Log.Message("6");
			return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_WastingAway : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			if (!std.is_wasting_away(p))
				return ThoughtState.Inactive;
			else
				return ThoughtState.ActiveAtStage(0);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Common functions and constants relevant to STDs.
	/// </summary>
	public static class std
	{
		public static std_def hiv = DefDatabase<std_def>.GetNamed("HIV");
		public static std_def herpes = DefDatabase<std_def>.GetNamed("Herpes");
		public static std_def warts = DefDatabase<std_def>.GetNamed("Warts");
		public static std_def syphilis = DefDatabase<std_def>.GetNamed("Syphilis");
		public static std_def boobitis = DefDatabase<std_def>.GetNamed("Boobitis");

		public static readonly HediffDef immunodeficiency = DefDatabase<HediffDef>.GetNamed("Immunodeficiency");

		public static List<std_def> all => DefDatabase<std_def>.AllDefsListForReading;

		// Returns how severely affected this pawn's crotch is by rashes and warts, on a scale from 0 to 3.
		public static int genital_rash_severity(Pawn p)
		{
			int tr = 0;

			Hediff her = p.health.hediffSet.GetFirstHediffOfDef(herpes.hediff_def);
			if (her != null && her.Severity >= 0.25f)
				++tr;

			Hediff war = p.health.hediffSet.GetFirstHediffOfDef(warts.hediff_def);
			if (war != null)
				tr += war.Severity < 0.40f ? 1 : 2;

			return tr;
		}

		public static Hediff get_infection(Pawn p, std_def sd)
		{
			return p.health.hediffSet.GetFirstHediffOfDef(sd.hediff_def);
		}

		public static BodyPartRecord GetRelevantBodyPartRecord(Pawn pawn, std_def std)
		{
			if (std.appliedOnFixedBodyParts == null)
			{
				return null;
			}

			BodyPartDef target = std.appliedOnFixedBodyParts.Single();
			return pawn?.RaceProps.body.GetPartsWithDef(target).Single();
			//return pawn?.RaceProps.body.GetPartsWithDef(std.appliedOnFixedBodyParts.Single()).Single();
		}

		public static bool is_wasting_away(Pawn p)
		{
			Hediff id = p.health.hediffSet.GetFirstHediffOfDef(immunodeficiency);
			return id != null && id.CurStageIndex > 0;
		}

		public static bool IsImmune(Pawn pawn)
		{
			// Archotech genitalia automagically purge STDs.
			return !RJWSettings.stds_enabled
				|| pawn.health.hediffSet.HasHediff(Genital_Helper.archotech_vagina)
				|| pawn.health.hediffSet.HasHediff(Genital_Helper.archotech_penis)
				|| xxx.is_demon(pawn)
				|| xxx.is_slime(pawn)
				|| xxx.is_mechanoid(pawn);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Defines a disease that has a chance to spread during sex.
	/// </summary>
	public class std_def : Def
	{
		public HediffDef hediff_def;
		public HediffDef cohediff_def = null;
		public float catch_chance;
		public float environment_pitch_chance = 0.0f;
		public float spawn_chance = 0.0f;
		public float spawn_severity = 0.0f;
		public float autocure_below_severity = -1.0f;
		public List<BodyPartDef> appliedOnFixedBodyParts = null;
	}
}


code:
using Multiplayer.API;
using RimWorld;
using System.Text;
using UnityEngine;
using Verse;

namespace rjw
{
	/// <summary>
	/// Responsible for spreading STDs (adding STD hediffs). Usually happens during sex.
	/// </summary>
	public static class std_spreader
	{
		/// <summary>
		/// Check for spreading of every STD the pitcher has to the catcher.
		/// Includes a small chance to spread STDs that pitcher doesn't have.
		/// </summary>
		[SyncMethod]
		public static void roll_to_catch(Pawn catcher, Pawn pitcher)
		{
			if (std.IsImmune(catcher) || std.IsImmune(pitcher))
			{
				return;
			}
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());

			float cleanliness_factor = GetCleanlinessFactor(catcher);

			foreach (std_def sd in std.all)
			{
				if (!catcher.health.hediffSet.HasHediff(sd.hediff_def))
				{
					if (catcher.health.immunity.GetImmunity(sd.hediff_def) <= 0.0f)
					{
						var bodyPartRecord = std.GetRelevantBodyPartRecord(catcher, sd);
						var artificial = bodyPartRecord != null && catcher.health.hediffSet.HasDirectlyAddedPartFor(bodyPartRecord);

						float catch_chance = GetCatchChance(catcher, sd);
						float catch_rv = Rand.Value;
						//if (xxx.config.std_show_roll_to_catch)
						//--Log.Message("  Chance to catch " + sd.label + ": " + catch_chance.ToStringPercent() + "; rolled: " + catch_rv.ToString());
						if (catch_rv < catch_chance)
						{
							string pitch_source; float pitch_chance;
							{
								if (get_severity(pitcher, sd) >= xxx.config.std_min_severity_to_pitch)
								{
									pitch_source = xxx.get_pawnname(pitcher);
									pitch_chance = 1.0f;
								}
								else
								{
									pitch_source = "the environment";
									pitch_chance = sd.environment_pitch_chance * cleanliness_factor;
									if (!RJWSettings.std_floor)
									{
										pitch_chance = -9001f;
									}
								}
							}
							float pitch_rv = Rand.Value;

							//if (xxx.config.std_show_roll_to_catch)
							//--Log.Message("	Chance to pitch (from " + pitch_source + "): " + pitch_chance.ToStringPercent() + "; rolled: " + pitch_rv.ToString());
							if (pitch_rv < pitch_chance)
							{
								infect(catcher, sd);
								show_infection_letter(catcher, sd, pitch_source, catch_chance * pitch_chance);
								//if (xxx.config.std_show_roll_to_catch)
								//--Log.Message("	  INFECTED!");
							}
						}
					}
					//else
					//if (xxx.config.std_show_roll_to_catch)
					//--Log.Message("  Still immune to " + sd.label);
				}
				//else
				//if (xxx.config.std_show_roll_to_catch)
				//--Log.Message("  Already infected with " + sd.label);
			}
		}

		public static float get_severity(Pawn p, std_def sd)
		{
			Hediff hed = std.get_infection(p, sd);
			return hed?.Severity ?? 0.0f;
		}

		public static Hediff infect(Pawn p, std_def sd, bool include_coinfection = true)
		{
			Hediff existing = std.get_infection(p, sd);
			if (existing != null)
			{
				return existing;
			}

			BodyPartRecord part = std.GetRelevantBodyPartRecord(p, sd);
			p.health.AddHediff(sd.hediff_def, part);
			if (include_coinfection && sd.cohediff_def != null)
			{
				p.health.AddHediff(sd.cohediff_def, part);
			}
			//--ModLog.Message("std::infect genitals std");
			return std.get_infection(p, sd);
		}

		static float GetCatchChance(Pawn pawn, std_def sd)
		{
			var bodyPartRecord = std.GetRelevantBodyPartRecord(pawn, sd);
			float artificialFactor = 1f;

			if (bodyPartRecord == null && pawn.health.hediffSet.HasDirectlyAddedPartFor(Genital_Helper.get_genitalsBPR(pawn)))
			{
				artificialFactor = .15f;
			}
			else if (pawn.health.hediffSet.HasDirectlyAddedPartFor(bodyPartRecord))
			{
				artificialFactor = 0f;
			}

			return sd.catch_chance * artificialFactor;
		}

		public static void show_infection_letter(Pawn p, std_def sd, string source = null, float? chance = null)
		{
			StringBuilder info;
			{
				info = new StringBuilder();
				info.Append(xxx.get_pawnname(p) + " has caught " + sd.label + (source != null ? " from " + source + "." : ""));
				if (chance.HasValue)
					info.Append(" (" + chance.Value.ToStringPercent() + " chance)");
				info.AppendLine(); info.AppendLine();
				info.Append(sd.description);
			}
			Find.LetterStack.ReceiveLetter("Infection: " + sd.label, info.ToString(), LetterDefOf.ThreatSmall, p);
		}

		static float GetCleanlinessFactor(Pawn catcher)
		{
			Room room = catcher.GetRoom();
			float cle = room?.GetStat(RoomStatDefOf.Cleanliness) ?? xxx.config.std_outdoor_cleanliness;
			float exa = cle >= 0.0f ? xxx.config.std_env_pitch_cleanliness_exaggeration : xxx.config.std_env_pitch_dirtiness_exaggeration;
			return Mathf.Max(0.0f, 1.0f - exa * cle);
		}

		// Not called anywhere?
		[SyncMethod]
		public static void generate_on(Pawn p)
		{
			if (p == null) return;
			//prevent error on world gen for pawns with broken bodies(no genitals)
			if (p.RaceProps.body.HasPartWithTag(BodyPartTagDefOf.RJW_Fertility))
				return;
			if (!xxx.is_human(p))
				return;
			float nymph_mul = !xxx.is_nympho(p) ? 1.0f : xxx.config.nymph_spawn_with_std_mul;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			foreach (std_def sd in std.all)
				if (Rand.Value < sd.spawn_chance * nymph_mul)
				{
					Hediff hed = infect(p, sd, false);
					float sev;
					{
						float r = Rand.Range(sd.hediff_def.minSeverity, sd.hediff_def.maxSeverity);
						sev = Mathf.Clamp(sd.spawn_severity * r, sd.hediff_def.minSeverity, sd.hediff_def.maxSeverity);
					}
					hed.Severity = sev;
				}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Responsible for handling the periodic effects of having an STD hediff.
	/// Not technically tied to the infection vector itself,
	/// but some of the STD effects are weird and complicated.
	/// </summary>
	public static class std_updater
	{
		public const float UpdatesPerDay = 60000f / 150f / (float)Need_Sex.needsex_tick_timer;

		public static void update(Pawn p)
		{
			update_immunodeficiency(p);

			// Check if any infections are below the autocure threshold and cure them if so
			foreach (std_def sd in std.all)
			{
				Hediff inf = std.get_infection(p, sd);
				if (inf != null && (inf.Severity < sd.autocure_below_severity || std.IsImmune(p)))
				{
					p.health.RemoveHediff(inf);
					if (sd.cohediff_def != null)
					{
						Hediff coinf = p.health.hediffSet.GetFirstHediffOfDef(sd.cohediff_def);
						if (coinf != null)
							p.health.RemoveHediff(coinf);
					}
				}
			}

			UpdateBoobitis(p);
			roll_for_syphilis_damage(p);
		}

		[SyncMethod]
		public static void roll_for_syphilis_damage(Pawn p)
		{
			Hediff syp = p.health.hediffSet.GetFirstHediffOfDef(std.syphilis.hediff_def);
			if (syp == null || !(syp.Severity >= 0.60f) || syp.FullyImmune()) return;

			// A 30% chance per day of getting any permanent damage works out to ~891 in 1 million for each roll
			// The equation is (1-x)^(60000/150)=.7
			// Important Note:
			// this function is called from Need_Sex::NeedInterval(), where it involves a needsex_tick and a std_tick to actually trigger this roll_for_syphilis_damage.
			// j(this is not exactly the same as the value in Need_Sex, that value is 0, but here j should be 1) std_ticks per this function called, k needsex_ticks per std_tick, 150 ticks per needsex_tick, and x is the chance per 150 ticks,
			// The new equation should be .7 = (1-x)^(400/kj)
			// 1-x = .7^(kj/400), x =1-.7^(kj/400)
			// Since k=10,j=1, so kj=10, new x is 1-.7^(10/400)=0.0088772362, let it be 888/100000
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (Rand.RangeInclusive(1, 100000) <= 888)
			{
				BodyPartRecord part;
				float sev;
				var parts = p.RaceProps.body.AllParts;

				float rv = Rand.Value;
				if (rv < 0.10f)
				{
					part = parts.Find(bpr => string.Equals(bpr.def.defName, "Brain"));
					sev = 1.0f;
				}
				else if (rv < 0.50f)
				{
					part = parts.Find(bpr => string.Equals(bpr.def.defName, "Liver"));
					sev = Rand.RangeInclusive(1, 3);
				}
				else if (rv < 0.75f)
				{
					//LeftKidney, probably
					part = parts.Find(bpr => string.Equals(bpr.def.defName, "Kidney"));
					sev = Rand.RangeInclusive(1, 2);
				}
				else
				{
					//RightKidney, probably
					part = parts.FindLast(bpr => string.Equals(bpr.def.defName, "Kidney"));
					sev = Rand.RangeInclusive(1, 2);
				}

				if (part != null && !p.health.hediffSet.PartIsMissing(part) && !p.health.hediffSet.HasDirectlyAddedPartFor(part))
				{
					DamageDef vir_dam = DefDatabase<DamageDef>.GetNamed("ViralDamage");
					HediffDef dam_def = HealthUtility.GetHediffDefFromDamage(vir_dam, p, part);
					Hediff_Injury inj = (Hediff_Injury)HediffMaker.MakeHediff(dam_def, p, null);
					inj.Severity = sev;
					inj.TryGetComp<HediffComp_GetsPermanent>().IsPermanent = true;
					p.health.AddHediff(inj, part, null);
					string message_title = std.syphilis.label + " Damage";
					string baby_pronoun = p.gender == Gender.Male ? "his" : "her";
					string message_text = "RJW_Syphilis_Damage_Message".Translate(xxx.get_pawnname(p), baby_pronoun, part.def.label, std.syphilis.label).CapitalizeFirst();
					Find.LetterStack.ReceiveLetter(message_title, message_text, LetterDefOf.ThreatSmall, p);
				}
			}
		}

		[SyncMethod]
		public static void update_immunodeficiency(Pawn p)
		{
			float min_bf_for_id = 1.0f - std.immunodeficiency.minSeverity;
			Hediff id = p.health.hediffSet.GetFirstHediffOfDef(std.immunodeficiency);
			float bf = p.health.capacities.GetLevel(PawnCapacityDefOf.BloodFiltration);
			bool has = id != null;
			bool should_have = bf <= min_bf_for_id;

			if (has && !should_have)
			{
				p.health.RemoveHediff(id);
				id = null;
			}
			else if (!has && should_have)
			{
				p.health.AddHediff(std.immunodeficiency);
				id = p.health.hediffSet.GetFirstHediffOfDef(std.immunodeficiency);
			}

			if (id == null) return;

			id.Severity = 1.0f - bf;

			// Roll for and apply opportunistic infections:
			// Pawns will have a 90% chance for at least one infection each year at 0% filtration, and a 0%
			// chance at 40% filtration, scaling linearly.
			// Let x = chance infected per roll
			// Then chance not infected per roll = 1 - x
			// And chance not infected on any roll in one day = (1 - x) ^ (60000 / 150) = (1 - x) ^ 400
			// And chance not infected on any roll in one year = (1 - x) ^ (400 * 60) = (1 - x) ^ 24000
			// So 0.10 = (1 - x) ^ 24000
			//	log (0.10) = 24000 log (1 - x)
			//	x = 0.00009593644334648975435114691213 = ~96 in 1 million
			// Important Note:
			// this function is called from Need_Sex::NeedInterval(), where it involves a needsex_tick and a std_tick to actually trigger this update_immunodeficiency.
			// j(this is not exactly the same as the value in Need_Sex, that value is 0, but here j should be 1) std_ticks per this function called, k needsex_ticks per std_tick, 150 ticks per needsex_tick, and x is the chance per 150 ticks,
			// The new equation should be .1 = (1-x)^(24000/kj)
			// log(.1) = (24000/kj) log(1-x),  so log(1-x)= (kj/24000) log(.1), 1-x = .1^(kj/24000), x= 1-.1^(kj/24000)
			// Since k=10,j=1, so kj=10, new x is 1-.1^(10/24000)=0.0009589504, let it be 959/1000000
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (Rand.RangeInclusive(1, 1000000) <= 959 && Rand.Value < bf / min_bf_for_id)
			{
				BodyPartRecord part;
				{
					float rv = Rand.Value;
					var parts = p.RaceProps.body.AllParts;
					if (rv < 0.25f)
						part = parts.Find(bpr => string.Equals(bpr.def.defName, "Jaw"));
					else if (rv < 0.50f)
						part = parts.Find(bpr => string.Equals(bpr.def.defName, "Lung"));
					else if (rv < 0.75f)
						part = parts.FindLast(bpr => string.Equals(bpr.def.defName, "Lung"));
					else
						part = parts.RandomElement();
				}

				if (part != null &&
					!p.health.hediffSet.PartIsMissing(part) && !p.health.hediffSet.HasDirectlyAddedPartFor(part) &&
					p.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.WoundInfection) == null && // If the pawn already has a wound infection, we can't properly set the immunity for the new one
					p.health.immunity.GetImmunity(HediffDefOf.WoundInfection) <= 0.0f)
				{ // Dont spawn infection if pawn already has immunity
					p.health.AddHediff(HediffDefOf.WoundInfection, part);
					p.health.HealthTick(); // Creates the immunity record
					ImmunityRecord ir = p.health.immunity.GetImmunityRecord(HediffDefOf.WoundInfection);
					if (ir != null)
						ir.immunity = xxx.config.opp_inf_initial_immunity;
					const string message_title = "Opportunistic Infection";
					string message_text = "RJW_Opportunistic_Infection_Message".Translate(xxx.get_pawnname(p)).CapitalizeFirst();
					Find.LetterStack.ReceiveLetter(message_title, message_text, LetterDefOf.ThreatSmall);
				}
			}
		}

		/// <summary>
		/// For meanDays = 1.0, will return true on average once per day. For 2.0, will return true on average once every two days.
		/// </summary>
		[SyncMethod]
		static bool RollFor(float meanDays)
		{
			return Rand.Chance(1.0f / meanDays / UpdatesPerDay);
		}

		public static void UpdateBoobitis(Pawn pawn)
		{
			//var partBPR = Genital_Helper.get_breastsBPR(pawn);
			//var Parts = Genital_Helper.get_PartsHediffList(pawn, partBPR);

			//if (!Parts.NullOrEmpty())
			//{
			//	foreach (Hediff hed in Parts)
			//	{
			//		if (!(hed is Hediff_PartBaseNatural))
			//			continue;

			//		GenderHelper.ChangeSex(pawn, () =>
			//		{
			//			hed.Severity += 0.1f;
			//		});
			//		var message = "RJW_BreastsHaveGrownFromBoobitis".Translate(pawn);
			//		Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
			//	}
			//}

			//var hediff = std.get_infection(pawn, std.boobitis);
			//if (hediff == null
			//	|| !(hediff.Severity >= 0.20f)
			//	|| hediff.FullyImmune()
			//	|| !BreastSize_Helper.TryGetBreastSize(pawn, out var oldSize, out var oldBoobs)
			//	|| oldSize >= BreastSize_Helper.MaxSize
			//	|| !RollFor(hediff.Severity > 0.90f ? 5f : 15f))
			//{
			//	return;
			//}
			//var chest = Genital_Helper.get_breastsBPR(pawn);
			//var newSize = oldSize + 1;
			//var newBoobs = BreastSize_Helper.GetHediffDef(newSize);

			//GenderHelper.ChangeSex(pawn, () =>
			//{
			//	if (oldBoobs != null)
			//	{
			//		pawn.health.RemoveHediff(oldBoobs);
			//	}
			//	pawn.health.AddHediff(newBoobs, chest);
			//});

			//var message = "RJW_BreastsHaveGrownFromBoobitis".Translate(pawn);
			//Messages.Message(message, pawn, MessageTypeDefOf.SilentInput);
		}
		}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	public static class BukkakeContent
	{
		//UI:
		public static readonly Texture2D SemenIcon_little = ContentFinder<Texture2D>.Get("Bukkake/SemenIcon_little", true);
		public static readonly Texture2D SemenIcon_some = ContentFinder<Texture2D>.Get("Bukkake/SemenIcon_some", true);
		public static readonly Texture2D SemenIcon_dripping = ContentFinder<Texture2D>.Get("Bukkake/SemenIcon_dripping", true);
		public static readonly Texture2D SemenIcon_drenched = ContentFinder<Texture2D>.Get("Bukkake/SemenIcon_drenched", true);

		//on pawn:
		public static readonly Material semenSplatch1 = MaterialPool.MatFrom("Bukkake/splatch_1", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch2 = MaterialPool.MatFrom("Bukkake/splatch_2", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch3 = MaterialPool.MatFrom("Bukkake/splatch_3", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch4 = MaterialPool.MatFrom("Bukkake/splatch_4", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch5 = MaterialPool.MatFrom("Bukkake/splatch_5", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch6 = MaterialPool.MatFrom("Bukkake/splatch_6", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch7 = MaterialPool.MatFrom("Bukkake/splatch_7", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch8 = MaterialPool.MatFrom("Bukkake/splatch_8", ShaderDatabase.Cutout);
		public static readonly Material semenSplatch9 = MaterialPool.MatFrom("Bukkake/splatch_9", ShaderDatabase.Cutout);

		[SyncMethod]
		public static Material pickRandomSplatch()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			int rand = Rand.Range(0, 8);
			switch (rand)
			{
				case 0:
					return semenSplatch1;
				case 1:
					return semenSplatch2;
				case 2:
					return semenSplatch3;
				case 3:
					return semenSplatch4;
				case 4:
					return semenSplatch5;
				case 5:
					return semenSplatch6;
				case 6:
					return semenSplatch7;
				case 7:
					return semenSplatch8;
				case 8:
					return semenSplatch9;
			}
			return null;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	//I took the liberty of adding the new DefOf files since I find that easier than managing all the defs via xxx.cs
	[DefOf]
	public static class RJW_SemenoOverlayHediffDefOf
	{
		public static HediffDef Hediff_Semen;//for humans & animals; also parent for insect and mech spunk
		public static HediffDef Hediff_InsectSpunk;
		public static HediffDef Hediff_MechaFluids;

		public static HediffDef Hediff_Bukkake;//Master + manager

	}

}


code:
using RimWorld;
using Verse;

namespace rjw
{
	[DefOf]
	static class RJW_SemenOverlayJobDefOf
	{
		public static JobDef CleanSelf;
	}
}


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

namespace rjw
{
	class Hediff_Bukkake : HediffWithComps
	{
		/*
		Whenever semen is applied, this hediff is also added to the pawn.
		Since there is always only a single hediff of this type on the pawn, it serves as master controller, adding up the individual Semen hediffs, applying debuffs and drawing overlays
		*/

		private static readonly float semenWeight = 0.2f;//how much individual semen_hediffs contribute to the overall bukkake severity
		List<Hediff> hediffs_semen;
		Dictionary<string, SemenSplatch> splatches;

		public override void ExposeData()
		{
			base.ExposeData();
			//Scribe_Values.Look<Dictionary<string, SemenSplatch>>(ref splatches, "splatches", new Dictionary<string, SemenSplatch>()); - causes errors when loading. for now, just make a new dictionary
			splatches = new Dictionary<string, SemenSplatch>();//instead of loading, just recreate anew
			hediffs_semen = new List<Hediff>();
		}

		public override void PostMake()
		{
			base.PostMake();
			splatches = new Dictionary<string, SemenSplatch>();
		}

		public override void PostTick()
		{
			if (pawn.RaceProps.Humanlike)//for now, only humans are supported
			{
				hediffs_semen = this.pawn.health.hediffSet.hediffs.FindAll(x => (x.def == RJW_SemenoOverlayHediffDefOf.Hediff_Semen || x.def == RJW_SemenoOverlayHediffDefOf.Hediff_InsectSpunk || x.def == RJW_SemenoOverlayHediffDefOf.Hediff_MechaFluids));
				float bukkakeLevel = CalculateBukkakeLevel();//sum of severity of all the semen hediffs x semenWeight
				this.Severity = bukkakeLevel;
				bool updatePortrait = false;

				//loop through all semen hediffs, add missing ones to dictionary
				for (int i = 0; i < hediffs_semen.Count(); i++)
				{
					Hediff_Semen h = (Hediff_Semen)hediffs_semen[i];
					string ID = h.GetUniqueLoadID();//unique ID for each hediff
					if (!splatches.ContainsKey(ID))//if it isn't here yet, make new object
					{
						updatePortrait = true;
						bool leftSide = h.Part.Label.Contains("left") ? true : false;//depending on whether the body part is left or right, drawing-offset on x-aixs may be inverted

						splatches[ID] = new SemenSplatch(h, pawn.story.bodyType, h.Part.def, leftSide, h.semenType);
					}
				}

				//remove splatch objects once their respective semen hediff is gone
				List<string> removeKeys = new List<string>();
				foreach (string key in splatches.Keys)
				{
					SemenSplatch s = splatches[key];

					if (!hediffs_semen.Contains(s.hediff_Semen))
					{
						removeKeys.Add(key);
						updatePortrait = true;
					}
				}
				//loop over and remove elements that should be destroyed:
				foreach (string key in removeKeys)
				{
					SemenSplatch s = splatches[key];
					splatches.Remove(key);
				}

				if (updatePortrait)//right now, portraits are only updated when a completely new semen hediff is added or an old one removed - maybe that should be done more frequently
				{
					PortraitsCache.SetDirty(pawn);
				}
			}
		}

		//called from the PawnWoundDrawer (see HarmonyPatches)
		public void DrawSemen(Vector3 drawLoc, Quaternion quat, bool forPortrait, float angle, bool inBed = false)
		{
			Rot4 bodyFacing = pawn.Rotation;
			int facingDir = bodyFacing.AsInt;//0: north, 1:east, 2:south, 3:west
			foreach (string key in splatches.Keys)
			{
				SemenSplatch s = splatches[key];
				s.Draw(drawLoc, quat, forPortrait, facingDir, angle, inBed);
			}
		}

		//new Hediff_Bukkake added to pawn -> just combine the two
		public override bool TryMergeWith(Hediff other)
		{
			if (other == null || other.def != this.def)
			{
				return false;
			}
			return true;
		}

		private float CalculateBukkakeLevel()
		{
			float num = 0f;
			for (int i = 0; i < hediffs_semen.Count; i++)
			{
				num += hediffs_semen[i].Severity * semenWeight;
			}
			return num;
		}

		//class for handling drawing of the individual splatches
		private class SemenSplatch
		{
			public readonly Hediff_Semen hediff_Semen;
			public readonly Material semenMaterial;
			public readonly BodyPartDef bodyPart;
			private bool mirrorMesh;

			private const float maxSize = 0.20f;//1.0 = 1 tile
			private const float minSize = 0.05f;

			//data taken from SemenHelper.cs:
			private readonly float[] xAdjust;
			private readonly float[] zAdjust;
			private readonly float[] yAdjust;

			public SemenSplatch(Hediff_Semen hediff, BodyTypeDef bodyType, BodyPartDef bodyPart, bool leftSide = false, int semenType = SemenHelper.CUM_NORMAL)
			{
				hediff_Semen = hediff;
				semenMaterial = new Material(BukkakeContent.pickRandomSplatch());//needs to create new material in order to allow for different colors
				semenMaterial.SetTextureScale("_MainTex", new Vector2(-1, 1));
				this.bodyPart = bodyPart;
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());

				//set color:
				switch (semenType)
				{
					case SemenHelper.CUM_NORMAL:
						semenMaterial.color = SemenHelper.color_normal;
						break;
					case SemenHelper.CUM_INSECT:
						semenMaterial.color = SemenHelper.color_insect;
						break;
					case SemenHelper.CUM_MECHA:
						semenMaterial.color = SemenHelper.color_mecha;
						break;
				}

				if (!MP.enabled)
					mirrorMesh = (Rand.Value > 0.5f);//in 50% of the cases, flip mesh horizontally for more variance

				//x,y,z adjustments to draw splatches over the approximately correct locations; values stored in semen helper - accessed by unique combinations of bodyTypes and bodyParts
				SemenHelper.key k = new SemenHelper.key(bodyType, bodyPart);
				if (SemenHelper.splatchAdjust.Keys.Contains(k))
				{
					SemenHelper.values helperValues = SemenHelper.splatchAdjust[k];

					//invert, x-adjust (horizontal) depending on left/right body side:
					if (!leftSide)
					{
						float[] xAdjTemp = new float[4];
						for (int i = 0; i < xAdjTemp.Length; i++)
						{
							xAdjTemp[i] = helperValues.x[i] * -1f;
						}
						xAdjust = xAdjTemp;
					}
					else
					{
						xAdjust = helperValues.x;
					}

					zAdjust = helperValues.z;//vertical adjustment

				}
				else//fallback in the the key can't be found
				{
					if (RJWSettings.DevMode)
					{
						ModLog.Message("created semen splatch for undefined body type or part. BodyType: " + bodyType + " , BodyPart: " + bodyPart);
					}
					xAdjust = new float[] { 0f, 0f, 0f, 0f };
					zAdjust = new float[] { 0f, 0f, 0f, 0f };
				}

				//y adjustments: plane/layer of drawing, > 0 -> above certain objects, < 0 -> below
				SemenHelper.key_layer k2 = new SemenHelper.key_layer(leftSide, bodyPart);

				if (SemenHelper.layerAdjust.Keys.Contains(k2))
				{
					SemenHelper.values_layer helperValues_layer = SemenHelper.layerAdjust[k2];
					yAdjust = helperValues_layer.y;
				}
				else
				{
					yAdjust = new float[] { 0.02f, 0.02f, 0.02f, 0.02f };//over body in every direction
				}

			}

			public void Draw(Vector3 drawPos, Quaternion quat, bool forPortrait, int facingDir = 0, float angle = 0,bool inBed=false)
			{
				if (inBed)
				{
					if (this.bodyPart != BodyPartDefOf.Jaw && this.bodyPart != BodyPartDefOf.Head)//when pawn is in bed (=bed with sheets), only draw semen on head
					{
						return;
					}
				}

				//these two create new mesh instance and never destroying it, filling ram and crashing
				//float size = minSize+((maxSize-minSize)*hediff_Semen.Severity);
				//mesh = MeshMakerPlanes.NewPlaneMesh(size);

				//use core MeshPool.plane025 instead

				//if (mirrorMesh)//horizontal flip
				//{
					//mesh = flipMesh(mesh);
				//}

				//rotation:
				if (angle == 0)//normal situation (pawn standing upright)
				{
					drawPos.x += xAdjust[facingDir];
					drawPos.z += zAdjust[facingDir];
				}
				else//if downed etc, more complex calculation becomes necessary
				{
					float radian = angle / 180 * (float)Math.PI;
					radian = -radian;
					drawPos.x += Mathf.Cos(radian) * xAdjust[hediff_Semen.pawn.Rotation.AsInt] - Mathf.Sin(radian) * zAdjust[facingDir];//facingDir doesn't appear to be chosen correctly in all cases
					drawPos.z += Mathf.Cos(radian) * zAdjust[hediff_Semen.pawn.Rotation.AsInt] + Mathf.Sin(radian) * xAdjust[facingDir];
				}

				//drawPos.y += yAdjust[facingDir];// 0.00: over body; 0.01: over body but under face, 0.02: over face, but under hair, -99 = "never" visible
				drawPos.y += yAdjust[facingDir];

				GenDraw.DrawMeshNowOrLater(MeshPool.plane025, drawPos, quat, semenMaterial, forPortrait);
			}

			//flips mesh UV horizontally, thereby mirroring the texture
			private Mesh flipMesh(Mesh meshToFlip)
			{
				var uvs = meshToFlip.uv;
				if (uvs.Length != 4)
				{
					return (meshToFlip);
				}
				for (var i = 0; i < uvs.Length; i++)
				{
					if (Mathf.Approximately(uvs[i].x, 1.0f))
						uvs[i].x = 0.0f;
					else
						uvs[i].x = 1.0f;
				}
				meshToFlip.uv = uvs;
				return (meshToFlip);
			}
		}
	}
}


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

namespace rjw
{
	public class Hediff_Semen : HediffWithComps
	{
		public int semenType = SemenHelper.CUM_NORMAL;//-> different colors

		public string giverName = null;//not utilized right now, maybe in the future save origin of the semen

		public override string LabelInBrackets
		{
			get
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append(base.LabelInBrackets);
				if (this.sourceHediffDef != null)
				{
					if (stringBuilder.Length != 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(this.sourceHediffDef.label);
				}
				else if (this.source != null)
				{
					if (stringBuilder.Length != 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(this.source.label);
					if (this.sourceBodyPartGroup != null)
					{
						stringBuilder.Append(" ");
						stringBuilder.Append(this.sourceBodyPartGroup.LabelShort);
					}
				}
				return stringBuilder.ToString();
			}
		}

		public override string SeverityLabel
		{
			get
			{
				if (this.Severity == 0f)
				{
					return null;
				}
				return this.Severity.ToString("F1");
			}
		}

		[SyncMethod]
		public override bool TryMergeWith(Hediff other)
		{
			//if a new Semen hediff is added to the same body part, they are combined. if severity reaches more than 1, spillover to other body parts occurs

			Hediff_Semen hediff_Semen = other as Hediff_Semen;
			if (hediff_Semen != null && hediff_Semen.def == this.def && hediff_Semen.Part == base.Part && this.def.injuryProps.canMerge)
			{
				semenType = hediff_Semen.semenType;//take over new creature color

				float totalAmount = hediff_Semen.Severity + this.Severity;
				if (totalAmount > 1.0f)
				{
					BodyPartDef spillOverTo = SemenHelper.spillover(this.Part.def);//SemenHelper saves valid other body parts for spillover
					if (spillOverTo != null)
					{
						//Rand.PopState();
						//Rand.PushState(RJW_Multiplayer.PredictableSeed());
						IEnumerable<BodyPartRecord> availableParts = SemenHelper.getAvailableBodyParts(pawn);//gets all non missing, valid body parts
						IEnumerable<BodyPartRecord> filteredParts = availableParts.Where(x => x.def == spillOverTo);//filters again for valid spill target
						if (!filteredParts.EnumerableNullOrEmpty())
						{
							BodyPartRecord spillPart = null;
							spillPart = filteredParts.RandomElement<BodyPartRecord>();//then pick one
							if (spillPart != null)
							{
								SemenHelper.cumOn(pawn, spillPart, totalAmount - this.Severity, null, semenType);
							}
						}
					}
				}

				return (base.TryMergeWith(other));

			}
			return (false);
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look<int>(ref semenType, "semenType", SemenHelper.CUM_NORMAL);

			if (Scribe.mode == LoadSaveMode.PostLoadInit && base.Part == null)
			{
				//Log.Error("Hediff_Semen has null part after loading.", false);
				this.pawn.health.hediffSet.hediffs.Remove(this);
				return;
			}
		}

		//handles the icon in the health tab and its color
		public override TextureAndColor StateIcon
		{
			get
			{
				TextureAndColor tex = TextureAndColor.None;
				Color color = Color.white;
				switch (semenType)
				{
					case SemenHelper.CUM_NORMAL:
						color = SemenHelper.color_normal;
						break;
					case SemenHelper.CUM_INSECT:
						color = SemenHelper.color_insect;
						break;
					case SemenHelper.CUM_MECHA:
						color = SemenHelper.color_mecha;
						break;
				}

				Texture2D tex2d = BukkakeContent.SemenIcon_little;
				switch (this.CurStageIndex)
				{
					case 0:
						tex2d = BukkakeContent.SemenIcon_little;
						break;
					case 1:
						tex2d = BukkakeContent.SemenIcon_some;
						break;
					case 2:
						tex2d = BukkakeContent.SemenIcon_dripping;
						break;
					case 3:
						tex2d = BukkakeContent.SemenIcon_drenched;
						break;
				}

				tex = new TextureAndColor(tex2d, color);

				return tex;
			}
		}

	}
}


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

namespace rjw
{
	class JobDriver_CleanSelf : JobDriver
	{
		float cleanAmount = 1f;//severity of a single SemenHediff removed per cleaning-round; 1f = remove entirely
		int cleaningTime = 120;//ticks - 120 = 2 real seconds, 3 in-game minutes

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

		protected override IEnumerable<Toil> MakeNewToils()
		{
			this.FailOn(delegate
			{
				List<Hediff> hediffs = pawn.health.hediffSet.hediffs;
				return !hediffs.Exists(x => x.def == RJW_SemenoOverlayHediffDefOf.Hediff_Bukkake);//fail if bukkake disappears - means that also all the semen is gone
			});
			Toil cleaning = Toils_General.Wait(cleaningTime, TargetIndex.None);//duration of
			cleaning.WithProgressBarToilDelay(TargetIndex.A);

			yield return cleaning;
			yield return new Toil()
			{
				initAction = delegate ()
				{
					//get one of the semen hediffs, reduce its severity
					Hediff hediff = pawn.health.hediffSet.hediffs.Find(x => (x.def == RJW_SemenoOverlayHediffDefOf.Hediff_Semen || x.def == RJW_SemenoOverlayHediffDefOf.Hediff_InsectSpunk || x.def == RJW_SemenoOverlayHediffDefOf.Hediff_MechaFluids));
					if (hediff != null)
					{
						hediff.Severity -= cleanAmount;
					}
				}
			};
			yield break;

		}
	}
}