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

namespace rjw
{
	public static class bondage_gear_tradeability
	{
		public static void init()
		{
			// Allows bondage gear to be selled by traders
			if (xxx.config.bondage_gear_enabled)
			{
				foreach (var def in DefDatabase<bondage_gear_def>.AllDefs)
					def.tradeability = Tradeability.Sellable;
			}
			// Forbids bondage gear to be selled by traders
			else
			{
				foreach (var def in DefDatabase<bondage_gear_def>.AllDefs)
					def.tradeability = Tradeability.None;
			}
		}
	}

	public static class bondage_gear_extensions
	{
		public static bool has_lock(this Apparel app)
		{
			return (app.TryGetComp<CompHoloCryptoStamped>() != null);
		}

		public static bool is_wearing_locked_apparel(this Pawn p)
		{
			if (p.apparel != null)
				foreach (var app in p.apparel.WornApparel)
					if (app.has_lock())
						return true;
			return false;
		}

		// Tries to get p started on the job of using an item on either another pawn or on themself (if "other" is null).
		// Of course in order for this method to work, the item's useJob has to be able to handle use on another pawn. This
		// is true for the holokey and bondage gear in RJW but not the items in the core game
		public static void start_job(this CompUsable usa, Pawn p, LocalTargetInfo tar)
		{
			if (p.CanReserveAndReach(usa.parent, PathEndMode.Touch, Danger.Some) &&
				((tar == null) || p.CanReserveAndReach(tar, PathEndMode.Touch, Danger.Some)))
			{
				var comfor = usa.parent.GetComp<CompForbiddable>();
				if (comfor != null)
					comfor.Forbidden = false;
				var job = new Job(((CompProperties_Usable)usa.props).useJob, usa.parent, tar);
				p.jobs.TryTakeOrderedJob(job);
			}
		}

		// Creates a menu option to use an item. "tar" is expected to be a pawn, corpse or null if it doesn't apply (in which
		// case the pawn will presumably use the item on themself). "required_work" can also be null.
		public static FloatMenuOption make_option(this CompUsable usa, string label, Pawn p, LocalTargetInfo tar, WorkTypeDef required_work)
		{
			if ((tar != null) && (!p.CanReserve(tar)))
			{
				string key = "Reserved";
				string text = TranslatorFormattedStringExtensions.Translate(key);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else if ((tar != null) && (!p.CanReach(tar, PathEndMode.Touch, Danger.Some)))
			{
				string key = "NoPath";
				string text = TranslatorFormattedStringExtensions.Translate(key);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else if ((required_work != null) && p.story.WorkTypeIsDisabled(required_work))
			{
				string key = "CannotPrioritizeWorkTypeDisabled";
				string text = TranslatorFormattedStringExtensions.Translate(key, required_work.gerundLabel);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else
				return new FloatMenuOption(
					label,
					delegate
					{
						usa.start_job(p, tar);
					},
					MenuOptionPriority.Default);
		}
	}

	public class bondage_gear_def : ThingDef
	{
		public Type soul_type;
		public HediffDef equipped_hediff = null;
		public bool gives_bound_moodlet = false;
		public bool gives_gagged_moodlet = false;
		public bool blocks_hands = false;
		public bool blocks_oral = false;
		public bool blocks_penis = false;
		public bool blocks_vagina = false;
		public bool blocks_anus = false;
		public bool blocks_breasts = false;
		private bondage_gear_soul soul_ins = null;

		public List<BodyPartDef> HediffTargetBodyPartDefs;      //field for optional list of targeted parts for hediff applying
		public List<BodyPartGroupDef> BoundBodyPartGroupDefs;   //field for optional list of groups restrained of verbcasting

		public bondage_gear_soul soul
		{
			get
			{
				if (soul_ins == null)
					soul_ins = (bondage_gear_soul)Activator.CreateInstance(soul_type);
				return soul_ins;
			}
		}
	}

	public class bondage_gear_soul
	{
		// Adds the bondage gear's associated HediffDef and spawns a matching holokey
		public virtual void on_wear(Pawn wearer, Apparel gear)
		{
			var def = (bondage_gear_def)gear.def;

			if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs != null)
			{
				foreach (BodyPartDef partDef in def.HediffTargetBodyPartDefs)                           //getting BodyPartDef, for example "Arm"
				{
					foreach (BodyPartRecord partRec in wearer.RaceProps.body.GetPartsWithDef(partDef))  //applying hediff to every single arm found on pawn
					{
						wearer.health.AddHediff(def.equipped_hediff, partRec);
					}
				}
			}
			else if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs == null)               //backward compatibility/simplified gear define without HediffTargetBodyPartDefs
			{                                                                                //Hediff applyed to whole body
				wearer.health.AddHediff(def.equipped_hediff);
			}

			var gear_stamp = gear.TryGetComp<CompHoloCryptoStamped>();
			if (gear_stamp != null)
			{
				var key = ThingMaker.MakeThing(ThingDef.Named("Holokey"));
				var key_stamp = key.TryGetComp<CompHoloCryptoStamped>();
				key_stamp.copy_stamp_from(gear_stamp);
				if (wearer.Map != null)
					GenSpawn.Spawn(key, wearer.Position, wearer.Map);
				else
					wearer.inventory.TryAddItemNotForSale(key);
			}
		}

		// Removes the gear's HediffDef
		public virtual void on_remove(Apparel gear, Pawn former_wearer)
		{
			var def = (bondage_gear_def)gear.def;
			if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs != null)
			{                                                                                //getting all Hediffs according with equipped_hediff def
				List<Hediff> hediffs = former_wearer.health.hediffSet.hediffs.Where(x => x.def == def.equipped_hediff).ToList();
				foreach (Hediff hedToRemove in hediffs)
				{
					if (def.HediffTargetBodyPartDefs.Contains(hedToRemove.Part.def))            //removing if applyed by this bondage_gear
						former_wearer.health.RemoveHediff(hedToRemove);                         //assuming there can be several different bondages
				}                                                                               //with the same equipped_hediff def
			}
			else if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs == null)       //backward compatibility/simplified gear define without HediffTargetBodyPartDefs
			{
				var hed = former_wearer.health.hediffSet.GetFirstHediffOfDef(def.equipped_hediff);
				if (hed != null)
					former_wearer.health.RemoveHediff(hed);
			}
		}
	}

	// Give bondage gear an extremely low score when it's not being worn so pawns never equip it on themselves and give
	// it an extremely high score when it is being worn so pawns never try to take it off to equip something "better".
	public class bondage_gear : Apparel
	{
		public override float GetSpecialApparelScoreOffset()
		{
			return (Wearer == null) ? -1e5f : 1e5f;
		}

		// made this method universal for any bondage_gear, won't affect anything if gear's BoundBodyPartGroupDefs is empty or null
		public override bool AllowVerbCast(IntVec3 root, Map map, LocalTargetInfo targ, Verb verb)
		{
			if ((this.def as bondage_gear_def).BoundBodyPartGroupDefs != null &&
				verb.tool != null &&
				(this.def as bondage_gear_def).BoundBodyPartGroupDefs.Contains(verb.tool.linkedBodyPartsGroup))
			{
				return false;
			}
			return true;
		}

		//needed for save compatibility only
		public override void ExposeData()
		{
			base.ExposeData();
			//if (Scribe.mode == LoadSaveMode.PostLoadInit)
			//	CheckHediffs();
		}

		//save compatibility insurance, will prevent Armbinder hediff with 0part efficiency on whole body
		private void CheckHediffs()
		{
			var def = (bondage_gear_def)this.def;
			if (this.Wearer == null || def.equipped_hediff == null) return;

			bool changedHediff = false;
			void ApplyHediffDirect(HediffDef hedDef, BodyPartRecord partRec)
			{
				Hediff hediff = (Hediff)HediffMaker.MakeHediff(hedDef, Wearer);
				if (partRec != null)
					hediff.Part = partRec;
				Wearer.health.hediffSet.AddDirect(hediff);
				changedHediff = true;
			}

			void RemoveHediffDirect(Hediff hed)
			{
				Wearer.health.hediffSet.hediffs.Remove(hed);
				changedHediff = true;
			}

			List<bondage_gear> wornBondageGear = Wearer.apparel.WornApparel.Where(x => x is bondage_gear).Cast<bondage_gear>().ToList();
			List<Hediff> hediffs = new List<Hediff>();
			foreach (Hediff h in this.Wearer.health.hediffSet.hediffs) hediffs.Add(h);

			//checking current hediffs defined by bondage_gear for being on defined place, cleaning up the misplaced
			bool equippedHediff;
			bool onPlace;
			foreach (Hediff hed in hediffs)
			{
				equippedHediff = false;
				onPlace = false;
				foreach (bondage_gear gear in wornBondageGear)
				{
					if (hed.def == (gear.def as bondage_gear_def).equipped_hediff)
					{
						//if hediff from bondage_gear and on it's defined place then don't touch it else remove
						//assuming there can be several different bondages with the same equipped_hediff def and different hediff target parts, don't know why
						equippedHediff = true;

						if ((hed.Part != null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null))
						{
							//pass
						}
						else if ((hed.Part == null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null))
						{
							onPlace = true;
							break;
						}
						else if (hed.Part != null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs.Contains(hed.Part.def))
						{
							onPlace = true;
							break;
						}
					}
				}
				if (equippedHediff && !onPlace)
				{
					Log.Message("Removing Hediff " + hed.Label + " from " + Wearer + (hed.Part == null ? "'s body" : "'s " + hed.Part));
					RemoveHediffDirect(hed);
				}
			}

			// now iterating every gear for having all hediffs in place, adding missing
			foreach (bondage_gear gear in wornBondageGear)
			{
				if ((gear.def as bondage_gear_def).equipped_hediff == null) continue;
				if ((gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null)                        //handling gear without HediffTargetBodyPartDefs
				{

					Hediff hed = Wearer.health.hediffSet.hediffs.Find(x => (x.def == (gear.def as bondage_gear_def).equipped_hediff && x.Part == null));//checking hediff defined by gear on whole body
					if (hed == null)                                                                        //if no legit hediff, adding
					{
						Log.Message("Adding missing Hediff " + (gear.def as bondage_gear_def).equipped_hediff.label + " to " + Wearer + "'s body");
						ApplyHediffDirect((gear.def as bondage_gear_def).equipped_hediff, null);
					}
				}
				else                                                                                //handling gear with defined HediffTargetBodyPartDefs
				{
					foreach (BodyPartDef partDef in (gear.def as bondage_gear_def).HediffTargetBodyPartDefs)    //getting every partDef
					{
						foreach (BodyPartRecord partRec in Wearer.RaceProps.body.GetPartsWithDef(partDef))      //checking all parts of def for applyed hediff
						{
							Hediff hed = Wearer.health.hediffSet.hediffs.Find(x => (x.def == (gear.def as bondage_gear_def).equipped_hediff && x.Part == partRec));//checking hediff defined by gear on defined place
							if (hed == null)                                                                    //if hediff missing, adding
							{
								Log.Message("Adding missing Hediff " + (gear.def as bondage_gear_def).equipped_hediff.label + " to " + Wearer + "'s " + partRec.Label);
								ApplyHediffDirect((gear.def as bondage_gear_def).equipped_hediff, partRec);
							}
						}
					}
				}
				//possibility of several different items with THE SAME equipped_hediff def ON THE SAME PART is not considered, that's sick
			}
			if (changedHediff)
			{
				//Possible error in current toil on Notify_HediffChanged() if capacity.Manipulation is involved so making it in the end.
				//Probably harmless, shouldn't happen again after corrected hediffs will be saved
				Wearer.health.Notify_HediffChanged(null);
			}
		}
	}

	public class armbinder : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class yoke : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class Restraints : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class force_off_gear_def : RecipeDef
	{
		public ThingDef removes_apparel;
		public BodyPartDef failure_affects;
		public List<BodyPartDef> destroys_one_of = null;
		public List<BodyPartDef> major_burns_on = null;
		public List<BodyPartDef> minor_burns_on = null;
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class CompMilkableHuman : CompHasGatherableBodyResource
	{
		protected override int GatherResourcesIntervalDays => Props.milkIntervalDays;

		protected override int ResourceAmount => Props.milkAmount;

		protected override ThingDef ResourceDef => Props.milkDef;

		protected override string SaveKey => "milkFullness";

		public CompProperties_MilkableHuman Props => (CompProperties_MilkableHuman)props;

		protected override bool Active
		{
			get
			{
				if (!Active)
				{
					return false;
				}
				Pawn pawn = parent as Pawn;
				if (pawn != null)
				{
					//idk should probably remove non rjw stuff
					//should merge Lactating into .cs hediff?
					//vanilla
					//C&P?
					//rjw human
					//rjw animal
					if ((!pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_lactating"), false))
					&& ((pawn.health.hediffSet.HasHediff(HediffDef.Named("Pregnant"), false) && pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("Pregnant"), false).Visible)
					//|| (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumanPregnancy"), false) && pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("HumanPregnancy"), false).Visible)
					|| (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy"), false) && pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy"), false).Visible)
					|| (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_beast"), false) && pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_beast"), false).Visible)))
					{
						pawn.health.AddHediff(HediffDef.Named("RJW_lactating"), null, null, null);
					}
					if ((!Props.milkFemaleOnly || pawn.gender == Gender.Female)
					&& (pawn.ageTracker.CurLifeStage.reproductive)
					&& (pawn.RaceProps.Humanlike)
					&& (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_lactating"), false)
						//|| pawn.health.hediffSet.HasHediff(HediffDef.Named("Lactating_Permanent"), false)
						//|| pawn.health.hediffSet.HasHediff(HediffDef.Named("Lactating_Natural"), false)
						//|| pawn.health.hediffSet.HasHediff(HediffDef.Named("Lactating_Drug"), false)
						))
					{
						return true;
					}
				}
				return false;
			}
		}

		public override string CompInspectStringExtra()
		{
			if (!Active)
			{
				return null;
			}
			return Translator.Translate("MilkFullness") + ": " + GenText.ToStringPercent(this.Fullness);
		}
	}
}

license: mit


code:
using Verse;

namespace rjw
{
	public class CompProperties_MilkableHuman : CompProperties
	{
		public int milkIntervalDays;

		public int milkAmount = 8;

		public ThingDef milkDef;

		public bool milkFemaleOnly = true;

		public CompProperties_MilkableHuman()
		{
			compClass = typeof(CompMilkableHuman);
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	[DefOf]
	public static class JobDefOfZ
	{
		public static JobDef MilkHuman;

		static JobDefOfZ()
		{
			DefOfHelper.EnsureInitializedInCtor(typeof(JobDefOf));
		}
	}
}


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

namespace rjw
{
	public abstract class JobDriver_GatherHumanBodyResources : JobDriver_GatherAnimalBodyResources
	{
		private float gatherProgress;

		/*
		//maybe change?
		protected abstract int GatherResourcesIntervalDays
		{
			get;
		}

		//add breastsize modifier?
		protected abstract int ResourceAmount
		{
			get;
		}
		//add more  milks?
		protected abstract ThingDef ResourceDef
		{
			get;
		}
		*/

		protected override IEnumerable<Toil> MakeNewToils()
		{
			ToilFailConditions.FailOnDespawnedNullOrForbidden<JobDriver_GatherHumanBodyResources>(this, TargetIndex.A);
			ToilFailConditions.FailOnNotCasualInterruptible<JobDriver_GatherHumanBodyResources>(this, TargetIndex.A);
			yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch);
			Toil wait = new Toil();
			wait.initAction = delegate
			{
				Pawn milker = base.pawn;
				LocalTargetInfo target = base.job.GetTarget(TargetIndex.A);
				Pawn target2 = (Pawn)target.Thing;
				milker.pather.StopDead();
				PawnUtility.ForceWait(target2, 15000, null, true);
			};
			wait.tickAction = delegate
			{
				Pawn milker = base.pawn;
				milker.skills.Learn(SkillDefOf.Animals, 0.13f, false);
				gatherProgress += StatExtension.GetStatValue(milker, StatDefOf.AnimalGatherSpeed, true);
				if (gatherProgress >= WorkTotal)
				{
					GetComp((Pawn)base.job.GetTarget(TargetIndex.A)).Gathered(base.pawn);
					milker.jobs.EndCurrentJob(JobCondition.Succeeded, true);
				}
			};
			wait.AddFinishAction((Action)delegate
			{
				Pawn milker = base.pawn;
				LocalTargetInfo target = base.job.GetTarget(TargetIndex.A);
				Pawn target2 = (Pawn)target.Thing;
				if (target2 != null && target2.CurJobDef == JobDefOf.Wait_MaintainPosture)
				{
					milker.jobs.EndCurrentJob(JobCondition.InterruptForced, true);
				}
			});
			ToilFailConditions.FailOnDespawnedOrNull<Toil>(wait, TargetIndex.A);
			ToilFailConditions.FailOnCannotTouch<Toil>(wait, TargetIndex.A, PathEndMode.Touch);
			wait.AddEndCondition((Func<JobCondition>)delegate
			{
				if (GetComp((Pawn)base.job.GetTarget(TargetIndex.A)).ActiveAndFull)
				{
					return JobCondition.Ongoing;
				}
				return JobCondition.Incompletable;
			});
			wait.defaultCompleteMode = ToilCompleteMode.Never;
			ToilEffects.WithProgressBar(wait, TargetIndex.A, (Func<float>)(() => gatherProgress / WorkTotal), false, -0.5f);
			wait.activeSkill = (() => SkillDefOf.Animals);
			yield return wait;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class JobDriver_MilkHuman : JobDriver_GatherHumanBodyResources
	{
		protected override float WorkTotal => 400f;

		protected override CompHasGatherableBodyResource GetComp(Pawn animal)
		{
			return ThingCompUtility.TryGetComp<CompMilkableHuman>(animal);
		}
	}
}


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

namespace rjw
{
	public abstract class WorkGiver_GatherHumanBodyResources : WorkGiver_GatherAnimalBodyResources
	{
		public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
		{
			//List<Pawn> pawns = pawn.Map.mapPawns.SpawnedPawnsInFaction(pawn.Faction);
			//int i = 0;
			//if (i < pawns.Count)
			//{
			//	yield return (Thing)pawns[i];
				/*Error: Unable to find new state assignment for yield return*/
			//	;
			//}
			foreach (Pawn targetpawn in pawn.Map.mapPawns.FreeColonistsAndPrisonersSpawned)
			{
				yield return targetpawn;
			}
		}

		public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn pawn2 = t as Pawn;
			if (pawn2 == null || !pawn2.RaceProps.Humanlike)
			{
				return false;
			}
			CompHasGatherableBodyResource comp = GetComp(pawn2);
			if (comp != null && comp.ActiveAndFull && PawnUtility.CanCasuallyInteractNow(pawn2, false) && pawn2 != pawn)
			{
				LocalTargetInfo target = pawn2;
				bool ignoreOtherReservations = forced;
				if (ReservationUtility.CanReserve(pawn, target, 1, -1, null, ignoreOtherReservations))
				{
					return true;
				}
			}
			return false;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class WorkGiver_MilkHuman : WorkGiver_GatherHumanBodyResources
	{
		protected override JobDef JobDef => JobDefOfZ.MilkHuman;

		protected override CompHasGatherableBodyResource GetComp(Pawn animal)
		{
			return ThingCompUtility.TryGetComp<CompMilkableHuman>(animal);
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	public static class RJW_Multiplayer
	{
		static RJW_Multiplayer()
		{
			if (!MP.enabled) return;

			// This is where the magic happens and your attributes
			// auto register, similar to Harmony's PatchAll.
			MP.RegisterAll();

				/*
				Log.Message("RJW MP compat testing");
				var type = AccessTools.TypeByName("rjw.RJWdesignations");
				//Log.Message("rjw MP compat " + type.Name);
				Log.Message("is host " + MP.IsHosting);
				Log.Message("PlayerName " + MP.PlayerName);
				Log.Message("IsInMultiplayer " + MP.IsInMultiplayer);

				//MP.RegisterSyncMethod(type, "Comfort");
				/*
				MP.RegisterSyncMethod(type, "<GetGizmos>Service");
				MP.RegisterSyncMethod(type, "<GetGizmos>BreedingHuman");
				MP.RegisterSyncMethod(type, "<GetGizmos>BreedingAnimal");
				MP.RegisterSyncMethod(type, "<GetGizmos>Breeder");
				MP.RegisterSyncMethod(type, "<GetGizmos>Milking");
				MP.RegisterSyncMethod(type, "<GetGizmos>Hero");
				*/

			// You can choose to not auto register and do it manually
			// with the MP.Register* methods.

			// Use MP.IsInMultiplayer to act upon it in other places
			// user can have it enabled and not be in session
		}

		//generate PredictableSeed for Verse.Rand
		public static int PredictableSeed()
		{
			int seed = 0;
			try
			{
				Map map = Find.CurrentMap;
				//int seedHourOfDay = GenLocalDate.HourOfDay(map);
				//int seedDayOfYear = GenLocalDate.DayOfYear(map);
				//int seedYear = GenLocalDate.Year(map);
				seed = (GenLocalDate.HourOfDay(map) + GenLocalDate.DayOfYear(map)) * GenLocalDate.Year(map);
				//int seed = (seedHourOfDay + seedDayOfYear) * seedYear;
				//Log.Warning("seedHourOfDay: " + seedHourOfDay + "\nseedDayOfYear: " + seedDayOfYear + "\nseedYear: " + seedYear + "\n" + seed);
			}
			catch
			{
				seed = Rand.Int;
			}
			return seed;
		}

		//generate PredictableSeed for Verse.Rand
		[SyncMethod]
		public static float RJW_MP_RAND()
		{
			return Rand.Value;
		}
	}
}


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

namespace rjw
{
	public class IncidentWorker_NymphJoins : IncidentWorker
	{
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			if (!RJWSettings.nymphos) return false;
			Map map = (Map)parms.target;
			float colonist_count = map.mapPawns.FreeColonistsCount;
			float nymph_count = map.mapPawns.FreeColonists.Count(xxx.is_nympho);

			float nymph_fraction = nymph_count / colonist_count;
			return colonist_count >= 1 && (nymph_fraction < xxx.config.max_nymph_fraction);
		}

		[SyncMethod]
		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			//--Log.Message("IncidentWorker_NymphJoins::TryExecute() called");

			if (!RJWSettings.nymphos) return false;
			Map map = (Map) parms.target;

			if (map == null)
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - map is null, abort!");
				return false;
			}
			else
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - map is ok");
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (!RCellFinder.TryFindRandomPawnEntryCell(out IntVec3 loc, map, CellFinder.EdgeRoadChance_Friendly + 0.2f))
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - no entry, abort!");
				return false;
			}

			Pawn pawn = Nymph_Generator.spawn_nymph(loc, ref map, Faction.OfPlayer);
			Find.LetterStack.ReceiveLetter("Nymph Joins", "A wandering nymph has decided to join your colony.",
				LetterDefOf.PositiveEvent, pawn);

			return true;
		}
	}
}


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

namespace rjw
{
	public class IncidentWorker_NymphVisitorGroup : IncidentWorker_NeutralGroup
	{

		private static readonly SimpleCurve PointsCurve = new SimpleCurve
		{
			new CurvePoint(45f, 0f),
			new CurvePoint(50f, 1f),
			new CurvePoint(100f, 1f),
			new CurvePoint(200f, 0.25f),
			new CurvePoint(300f, 0.1f),
			new CurvePoint(500f, 0f)
		};

		[SyncMethod]
		protected override void ResolveParmsPoints(IncidentParms parms)
		{
			if (!(parms.points >= 0f))
			{
				parms.points = Rand.ByCurve(PointsCurve);
			}
		}

		[SyncMethod]
		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			//--Log.Message("IncidentWorker_NymphVisitorGroup::TryExecute() called");

			if (!RJWSettings.nymphos)
			{
				return false;
			}

			Map map = (Map)parms.target;

			if (map == null)
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - map is null, abort!");
				return false;
			}
			else
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - map is ok");
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (!RCellFinder.TryFindRandomPawnEntryCell(out IntVec3 loc, map, CellFinder.EdgeRoadChance_Friendly + 0.2f))
			{
				//--Log.Message("IncidentWorker_NymphJoins::TryExecute() - no entry, abort!");
				return false;
			}

			Pawn pawn = Nymph_Generator.spawn_nymph(loc, ref map);

			Find.LetterStack.ReceiveLetter("Nymph wanders in", "A wandering nymph has decided to visit your colony.", LetterDefOf.NeutralEvent, pawn);
			return true;
		}
	}
}


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

namespace rjw
{
	public class IncidentWorker_TestInc : IncidentWorker
	{
		public static void list_backstories()
		{
			foreach (var bs in BackstoryDatabase.allBackstories.Values)
				Log.Message("Backstory \"" + bs.title + "\" has identifier \"" + bs.identifier + "\"");
		}

		public static void inject_designator()
		{
			//var des = new Designator_ComfortPrisoner();
			//Find.ReverseDesignatorDatabase.AllDesignators.Add(des);
			//Find.ReverseDesignatorDatabase.AllDesignators.Add(new Designator_Breed());
		}

		[SyncMethod]
		public static void spawn_nymphs(Map m)
		{
			IntVec3 loc;
			if (!CellFinder.TryFindRandomEdgeCellWith(m.reachability.CanReachColony, m, 1.0f, out loc)) // TODO fix ROADCHANCE
				return;

			for (int i = 1; i <= 10; ++i)
				Nymph_Generator.spawn_nymph(loc, ref m);
			//Find.LetterStack.ReceiveLetter("Nymphs!", "A whole group of nymphs has wandered into your colony.", LetterDefOf.BadNonUrgent, null);
			Find.LetterStack.ReceiveLetter("Nymphs!", "A whole group of nymphs has wandered into your colony.", LetterDefOf.NegativeEvent, null);
		}

		// Applies permanent damage to a randomly chosen colonist, to test that this works
		[SyncMethod]
		public static void damage_virally(Map m)
		{
			var vir_dam = DefDatabase<DamageDef>.GetNamed("ViralDamage");
			var p = m.mapPawns.FreeColonists.RandomElement();
			var lun = p.RaceProps.body.AllParts.Find((BodyPartRecord bpr) => String.Equals(bpr.def.defName, "LeftLung"));
			var dam_def = HealthUtility.GetHediffDefFromDamage(vir_dam, p, lun);
			var inj = (Hediff_Injury)HediffMaker.MakeHediff(dam_def, p, null);
			inj.Severity = 2.0f;
			inj.TryGetComp<HediffComp_GetsPermanent>().IsPermanent = true;
			p.health.AddHediff(inj, lun, null);
		}

		// Gives all colonists on the map a severe syphilis or HIV infection
		[SyncMethod]
		public static void infect_the_colonists(Map m)
		{
			foreach (var p in m.mapPawns.FreeColonists)
			{
				if (Rand.Value < 0.50f)
					std_spreader.infect(p, std.syphilis);
				// var std_hed_def = (Rand.Value < 0.50f) ? std.syphilis.hediff_def : std.hiv.hediff_def;
				// p.health.AddHediff (std_hed_def);
				// p.health.hediffSet.GetFirstHediffOfDef (std_hed_def).Severity = Rand.Range (0.50f, 0.90f);
			}
		}

		// Reduces the sex need of the selected pawn
		public static void reduce_sex_need_on_select(Map m)
		{
			Pawn pawn = Find.Selector.SingleSelectedThing as Pawn;
			if (pawn != null)
			{
				if (pawn.needs.TryGetNeed<Need_Sex>() != null)
				{
					//--Log.Message("[RJW]TestInc::reduce_sex_need_on_select is called");
					pawn.needs.TryGetNeed<Need_Sex>().CurLevel -= 0.5f;
				}
			}
		}

		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			var m = (Map)parms.target;

			// list_backstories ();
			// inject_designator ();
			// spawn_nymphs (m);
			// damage_virally (m);
			//infect_the_colonists(m);
			reduce_sex_need_on_select(m);

			return true;
		}
	}
}


code:
using RimWorld;
using UnityEngine;
using Verse;

namespace rjw
{
	public class IncidentWorker_TestInc2 : IncidentWorker
	{
		// Testing the mechanism of some build-in functions
		public static void test_funcion()
		{
			float a = Mathf.InverseLerp(0, 2, 3); //gives 1
			float b = Mathf.InverseLerp(0.2f, 2, 0.1f); //gives 0
			float c = Mathf.InverseLerp(2f, 1, 2.5f); //gives 0
													  //--Log.Message("[RJW]TestInc2::test_function is called - value a is " + a);
													  //--Log.Message("[RJW]TestInc2::test_function is called - value b is " + b);
													  //--Log.Message("[RJW]TestInc2::test_function is called - value c is " + c);
		}

		// Gives the wanted information of the selected thing
		public static void info_on_select(Map m)
		{
			Pawn p = Find.Selector.SingleSelectedThing as Pawn;
			if (p != null)
			{
				//--Log.Message("[RJW]TestInc2::info_on_select is called");
				foreach (var q in m.mapPawns.AllPawns)
				{
					SexAppraiser.would_fuck(p, q, true);
				}
			}
		}

		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			var m = (Map)parms.target;

			info_on_select(m);

			return true;
		}
	}
}


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

namespace rjw
{
	public struct nymph_story
	{
		public Backstory child;
		public Backstory adult;
		public List<Trait> traits;
	}

	public struct nymph_passion_chances
	{
		public float major;
		public float minor;

		public nymph_passion_chances(float maj, float min)
		{
			major = maj;
			minor = min;
		}
	}

	public static class nymph_backstories
	{
		public struct child
		{
			public static Backstory vatgrown_sex_slave;
		};

		public struct adult
		{
			public static Backstory feisty;
			public static Backstory curious;
			public static Backstory tender;
			public static Backstory chatty;
			public static Backstory broken;
		};

		public static void init()
		{
			{
				Backstory bs = new Backstory();
				bs.identifier = ""; // identifier will be set by ResolveReferences
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_vatgrown_sex_slave");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Vat-Grown Sex Slave", "Vat-Grown Sex Slave");
					bs.SetTitleShort("SexSlave", "SexSlave");
					bs.baseDesc = "NAME is made as a sex machine, rather than a human.During HIS growing period in the factory, HE was taught plenty of social skills and sex skills, just to satisfy various sex demands.";
				}
				// bs.skillGains = new Dictionary<string, int> ();
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Social"), 8);
				// bs.skillGainsResolved = new Dictionary<SkillDef, int> (); // populated by ResolveReferences
				bs.workDisables = WorkTags.Intellectual;
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Childhood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				//bs.bodyTypeFemale = BodyType.Female;
				//bs.bodyTypeMale = BodyType.Thin;
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				child.vatgrown_sex_slave = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed0");
			}
			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_feisty");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Feisty Nymph", "Feisty Nymph");
					bs.SetTitleShort("Nymph", "Nymph");
					bs.baseDesc = "NAME struts around the colony like a conqueror examining a newly annexed territory. You try to focus on your work but HE won't let you.";
				}
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Social"), -3);
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Shooting"), 2);
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Melee"), 9);
				bs.workDisables = (WorkTags.Cleaning | WorkTags.Animals | WorkTags.Caring | WorkTags.Artistic | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.feisty = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed1");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_curious");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Curious Nymph", "Curious Nymph");
					bs.SetTitleShort("Nymph", "Nymph");
					bs.baseDesc = "Hold the covering plate back and place the actuator in slot C. Line the rod up. No, that won't do, it needs to be perfect. Now pop the JC-444 chip in place.";
				}
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Construction"), 2);
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Crafting"), 6);
				bs.workDisables = (WorkTags.Animals | WorkTags.Artistic | WorkTags.Caring | WorkTags.Cooking | WorkTags.Mining | WorkTags.PlantWork | WorkTags.Violent | WorkTags.ManualDumb);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.curious = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed2");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_tender");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Tender Nymph", "Tender Nymph");
					bs.SetTitleShort("Nymph", "Nymph");
					bs.baseDesc = "NAME has a pair of charming eyes, and HIS voice is soft and sweet. Due to HIS previous nurse job, HE tends people quite well.";
				}
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Medicine"), 4);
				bs.workDisables = (WorkTags.Animals | WorkTags.Artistic | WorkTags.Hauling | WorkTags.Violent | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.tender = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed3");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_chatty");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Chatty Nymph", "Chatty Nymph");
					bs.SetTitleShort("Nymph", "Nymph");
					bs.baseDesc = "No one can be more loquacious than NAME. HECAP has a flexible tongue, a sharp mind, and hot lips.";
				}
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Social"), 6);
				bs.workDisables = (WorkTags.Animals | WorkTags.Caring | WorkTags.Artistic | WorkTags.Violent | WorkTags.ManualDumb | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.chatty = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed4");
			}

			{
				Backstory bs = new Backstory();
				bs.identifier = "";
				MiscTranslationDef MTdef = DefDatabase<MiscTranslationDef>.GetNamedSilentFail("rjw_broken");
				if (MTdef != null)
				{
					bs.SetTitle(MTdef.label, MTdef.label);
					bs.SetTitleShort(MTdef.stringA, MTdef.stringA);
					bs.baseDesc = MTdef.description;
				}
				else
				{
					bs.SetTitle("Broken Nymph", "Broken Nymph");
					bs.SetTitleShort("Nymph", "Nymph");
					bs.baseDesc = "Maybe NAME suffered some terrible Things, HE looks rather emaciated, and no one wants to speak with HIM.\nHECAP only behaves a bit vivaciously while staring at some rod-like stuffs.";
				}
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Social"), -5);
				bs.skillGainsResolved.Add(DefDatabase<SkillDef>.GetNamed("Artistic"), 8);
				bs.workDisables = (WorkTags.Cleaning | WorkTags.Animals | WorkTags.Caring | WorkTags.Violent | WorkTags.ManualSkilled);
				bs.requiredWorkTags = WorkTags.None;
				bs.slot = BackstorySlot.Adulthood;
				bs.spawnCategories = new List<string>() { "rjw_nymphsCategory", "Slave" }; // Not necessary (I Think)
				Unprivater.SetProtectedValue("bodyTypeGlobal", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeFemale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeMale", bs, "Thin");
				Unprivater.SetProtectedValue("bodyTypeGlobalResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeFemaleResolved", bs, BodyTypeDefOf.Thin);
				Unprivater.SetProtectedValue("bodyTypeMaleResolved", bs, BodyTypeDefOf.Thin);
				bs.forcedTraits = new List<TraitEntry>();
				bs.forcedTraits.Add(new TraitEntry(xxx.nymphomaniac, 0));
				bs.disallowedTraits = new List<TraitEntry>();
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesMen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.DislikesWomen, 0));
				bs.disallowedTraits.Add(new TraitEntry(TraitDefOf.TooSmart, 0));
				bs.shuffleable = true;
				bs.ResolveReferences();
				BackstoryDatabase.AddBackstory(bs);
				adult.broken = bs;
				//--Log.Message("[RJW]nymph_backstories::init() succeed5");
			}
		}

		public static nymph_passion_chances get_passion_chances(Backstory child_bs, Backstory adult_bs, SkillDef skill_def)
		{
			var maj = 0.0f;
			var min = 0.0f;

			if (adult_bs == adult.feisty)
			{
				if (skill_def == SkillDefOf.Melee) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Shooting) { maj = 0.25f; min = 0.75f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.10f; min = 0.67f; }
			}
			else if (adult_bs == adult.curious)
			{
				if (skill_def == SkillDefOf.Construction) { maj = 0.15f; min = 0.40f; }
				else if (skill_def == SkillDefOf.Crafting) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.20f; min = 1.00f; }
			}
			else if (adult_bs == adult.tender)
			{
				if (skill_def == SkillDefOf.Medicine) { maj = 0.20f; min = 0.60f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.50f; min = 1.00f; }
			}
			else if (adult_bs == adult.chatty)
			{
				if (skill_def == SkillDefOf.Social) { maj = 1.00f; min = 1.00f; }
			}
			else if (adult_bs == adult.broken)
			{
				if (skill_def == SkillDefOf.Artistic) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.00f; min = 0.33f; }
			}

			return new nymph_passion_chances(maj, min);
		}

		// Randomly chooses backstories and traits for a nymph
		[SyncMethod]
		public static nymph_story generate()
		{
			var tr = new nymph_story();

			tr.child = child.vatgrown_sex_slave;

			tr.traits = new List<Trait>();
			tr.traits.Add(new Trait(xxx.nymphomaniac, 0, true));

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var beauty = 0;
			var rv = Rand.Value;
			var rv2 = Rand.Value;
			if (rv < 0.300)
			{
				tr.adult = adult.feisty;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Brawler));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.Bloodlust));
				else
					tr.traits.Add(new Trait(xxx.rapist));
			}
			else if (rv < 0.475)
			{
				tr.adult = adult.curious;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Transhumanist));
			}
			else if (rv < 0.650)
			{
				tr.adult = adult.tender;
				beauty = Rand.RangeInclusive(1, 2);
				if (rv2 < 0.50)
					tr.traits.Add(new Trait(TraitDefOf.Kind));
			}
			else if (rv < 0.825)
			{
				tr.adult = adult.chatty;
				beauty = 2;
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Greedy));
			}
			else
			{
				tr.adult = adult.broken;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 1));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 2));
			}

			if (beauty > 0)
				tr.traits.Add(new Trait(TraitDefOf.Beauty, beauty, false));

			return tr;
		}
	}
}


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

namespace rjw
{
	public static class Nymph_Generator
	{
		/// <summary>
		/// Returns true if the given pawnGenerationRequest is for a nymph pawnKind.
		/// </summary>
		public static bool IsNymph(PawnGenerationRequest pawnGenerationRequest)
		{
			return pawnGenerationRequest.KindDef != null && pawnGenerationRequest.KindDef.defName == "Nymph";
		}

		private static bool is_trait_conflicting_or_duplicate(Pawn pawn, Trait t)
		{
			foreach (var existing in pawn.story.traits.allTraits)
				if ((existing.def == t.def) || (t.def.ConflictsWith(existing)))
					return true;
			return false;
		}

		public static bool IsNymphBodyType(Pawn pawn)
		{
			return pawn.story.bodyType == BodyTypeDefOf.Female || pawn.story.bodyType == BodyTypeDefOf.Thin;
		}

		[SyncMethod]
		public static Gender RandomNymphGender()
		{
			//with males 100% its still 99%, coz im  to lazy to fix it
			//float rnd = Rand.Value;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float chance = RJWSettings.male_nymph_chance;

			Gender Pawngender = Rand.Chance(chance) ? Gender.Male: Gender.Female;
			//Log.Message("[RJW] setnymphsex: " + (rnd < chance) + " rnd:" + rnd + " chance:" + chance);
			//Log.Message("[RJW] setnymphsex: " + Pawngender);
			return Pawngender;
		}

		/// <summary>
		/// Replaces a pawn's backstory and traits to turn it into a nymph
		/// </summary>
		[SyncMethod]
		public static void set_story(Pawn pawn)
		{
			var gen_sto = nymph_backstories.generate();

			pawn.story.childhood = gen_sto.child;
			pawn.story.adulthood = gen_sto.adult;

			// add broken body to broken nymph
			if (pawn.story.adulthood == nymph_backstories.adult.broken)
			{
				pawn.health.AddHediff(xxx.feelingBroken);
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				(pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken)).Severity = Rand.Range(0.4f, 1.0f);
			}

			//The mod More Trait Slots will adjust the max number of traits pawn can get, and therefore,
			//I need to collect pawns' traits and assign other_traits back to the pawn after adding the nymph_story traits.
			Stack<Trait> other_traits = new Stack<Trait>();
			int numberOfTotalTraits = 0;
			if (!pawn.story.traits.allTraits.NullOrEmpty())
			{
				foreach (Trait t in pawn.story.traits.allTraits)
				{
					other_traits.Push(t);
					++numberOfTotalTraits;
				}
			}

			pawn.story.traits.allTraits.Clear();
			var trait_count = 0;
			foreach (var t in gen_sto.traits)
			{
				pawn.story.traits.GainTrait(t);
				++trait_count;
			}
			while (trait_count < numberOfTotalTraits)
			{
				Trait t = other_traits.Pop();
				if (!is_trait_conflicting_or_duplicate(pawn, t))
					pawn.story.traits.GainTrait(t);
				++trait_count;
			}
		}

		[SyncMethod]
		private static int sum_previous_gains(SkillDef def, Pawn_StoryTracker sto, Pawn_AgeTracker age)
		{
			int total_gain = 0;
			int gain;

			// Gains from backstories
			if (sto.childhood.skillGainsResolved.TryGetValue(def, out gain))
				total_gain += gain;
			if (sto.adulthood.skillGainsResolved.TryGetValue(def, out gain))
				total_gain += gain;

			// Gains from traits
			foreach (var trait in sto.traits.allTraits)
				if (trait.CurrentData.skillGains.TryGetValue(def, out gain))
					total_gain += gain;

			// Gains from age
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var rgain = Rand.Value * (float)total_gain * 0.35f;
			var age_factor = Mathf.Clamp01((age.AgeBiologicalYearsFloat - 17.0f) / 10.0f); // Assume nymphs are 17~27
			total_gain += (int)(age_factor * rgain);

			return Mathf.Clamp(total_gain, 0, 20);
		}

		/// <summary>
		/// Set a nymph's initial skills & passions from backstory, traits, and age
		/// </summary>
		[SyncMethod]
		public static void set_skills(Pawn pawn)
		{
			foreach (var skill_def in DefDatabase<SkillDef>.AllDefsListForReading)
			{
				var rec = pawn.skills.GetSkill(skill_def);
				if (!rec.TotallyDisabled)
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					rec.Level = sum_previous_gains(skill_def, pawn.story, pawn.ageTracker);
					rec.xpSinceLastLevel = rec.XpRequiredForLevelUp * Rand.Range(0.10f, 0.90f);

					var pas_cha = nymph_backstories.get_passion_chances(pawn.story.childhood, pawn.story.adulthood, skill_def);
					var rv = Rand.Value;
					if (rv < pas_cha.major) rec.passion = Passion.Major;
					else if (rv < pas_cha.minor) rec.passion = Passion.Minor;
					else rec.passion = Passion.None;
				}
				else
					rec.passion = Passion.None;
			}
		}

		public static PawnKindDef GetFixedNymphPawnKindDef()
		{
			var def = PawnKindDef.Named("Nymph");
			// This is 18 in the xml but something is overwriting it to 5.
			def.minGenerationAge = 18;
			return def;
		}

		[SyncMethod]
		public static Pawn spawn_nymph(IntVec3 around_loc, ref Map map, Faction faction = null)
		{
			// Most of the special properties of nymphs are in harmony patches to PawnGenerator.
			PawnGenerationRequest request = new PawnGenerationRequest(
				GetFixedNymphPawnKindDef(),
				faction,
				PawnGenerationContext.NonPlayer,
				map.Tile,	// tile(default is -1), since Inhabitant is true, then tile should have a value here; otherwise an error will pop
				false, // Force generate new pawn
				false, // Newborn
				false, // Allow dead
				false, // Allow downed
				false, // Can generate pawn relations
				false, // Must be capable of violence
				0.0f, // Colonist relation chance factor
				false, // Force add free warm layer if needed
				true, // Allow gay
				true, // Allow food
				true, // Inhabitant
				false, // Been in Cryosleep
				false, // ForceRedressWorldPawnIfFormerColonist
				false, // WorldPawnFactionDoesntMatter
				null, // ValidatorPreGear
				null, // ValidatorPostGear
				null, // MinChanceToRedressWorldPawn
				null, // Fixed biological age
				null, // Fixed chronological age
				null, // Fixed gender
				null, // Fixed melanin
				null); // Fixed last name

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			Pawn pawn = PawnGenerator.GeneratePawn(request);
			IntVec3 spawn_loc = CellFinder.RandomClosewalkCellNear(around_loc, map, 6);//RandomSpawnCellForPawnNear could be an alternative
			GenSpawn.Spawn(pawn, spawn_loc, map);
			return pawn;
		}
	}
}


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

namespace rjw
{
	//MicroComputer
	internal class Hediff_MicroComputer : Hediff_MechImplants
	{
		protected int nextEventTick = 60000;

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look<int>(ref this.nextEventTick, "nextEventTick", 60000, false);
		}

		[SyncMethod]
		public override void PostMake()
		{
			base.PostMake();
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			nextEventTick = Rand.Range(mcDef.minEventInterval, mcDef.maxEventInterval);
		}

		[SyncMethod]
		public override void Tick()
		{
			base.Tick();
			if (this.pawn.IsHashIntervalTick(1000))
			{
				if (this.ageTicks >= nextEventTick)
				{
					HediffDef randomEffectDef = DefDatabase<HediffDef>.GetNamed(randomEffect);
					if (randomEffectDef != null)
					{
						pawn.health.AddHediff(randomEffectDef);
					}
					else
					{
						//--Log.Message("[RJW]" + this.GetType().ToString() + "::Tick() - There is no Random Effect");
					}
					this.ageTicks = 0;
				}
			}
		}

		protected HediffDef_MechImplants mcDef
		{
			get
			{
				return ((HediffDef_MechImplants)def);
			}
		}

		protected List<string> randomEffects
		{
			get
			{
				return mcDef.randomHediffDefs;
			}
		}

		protected string randomEffect
		{
			get
			{
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				return randomEffects.RandomElement<string>();
			}
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	internal class HediffDef_EnemyImplants : HediffDef
	{
		//single parent eggs
		public string parentDef = "";
		//multiparent eggs
		public List<string> parentDefs = new List<string>();

		//for implanting eggs
		public bool IsParent(string defnam)
		{
			return
					//predefined parent eggs
					parentDef == defnam ||
					parentDefs.Contains(defnam) ||
					//dynamic egg
					(parentDef == "Unknown" && defnam == "Unknown" && RJWPregnancySettings.egg_pregnancy_implant_anyone);
		}
	}

	[StaticConstructorOnStartup]
	internal class HediffDef_InsectEgg : HediffDef_EnemyImplants
	{
		//this is filled from xml
		//1 day = 60000 ticks
		public float eggsize = 1;
		public bool selffertilized = false;
	}

	[StaticConstructorOnStartup]
	internal class HediffDef_MechImplants : HediffDef_EnemyImplants
	{
		public List<string> randomHediffDefs = new List<string>();
		public int minEventInterval = 30000;
		public int maxEventInterval = 90000;
	}
}


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

namespace rjw
{
	public abstract class Hediff_BasePregnancy : HediffWithComps
	{
		///<summary>
		///This hediff class simulates pregnancy.
		///</summary>
		/*It is different from vanilla in that if father disappears, this one still remembers him.
		Other weird variants can be derived
		As usuall, significant parts of this are completely stolen from Children and Pregnancy*/

		//Static fields
		private const int MiscarryCheckInterval = 1000;
		private const float MTBMiscarryStarvingDays = 0.5f;
		private const float MTBMiscarryWoundedDays = 0.5f;

		protected const int TicksPerDay = 60000;
		protected const int TicksPerHour = 2500;

		protected const string starvationMessage = "MessageMiscarriedStarvation";
		protected const string poorHealthMessage = "MessageMiscarriedPoorHealth";

		protected static readonly HashSet<string> non_genetic_traits = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("NonInheritedTraits").strings);

		//Fields
		///All babies should be premade and stored here
		protected List<Pawn> babies;
		///Reference to daddy, goes null sometimes
		protected Pawn father;
		///Is pregnancy visible?
		protected bool is_discovered;
		///Is pregnancy type checked?
		public bool is_checked = false;
		///Mechanoid pregnancy, false - spawn agressive mech
		public bool is_hacked = false;
		///Contractions duration, effectively additional hediff stage, a dirty hack to make birthing process notable
		protected int contractions;
		///Gestation progress per tick
		protected float progress_per_tick;
		//
		// Properties
		//
		public float GestationProgress
		{
			get => Severity;
			private set => Severity = value;
		}

		private bool IsSeverelyWounded
		{
			get
			{
				float num = 0;
				List<Hediff> hediffs = pawn.health.hediffSet.hediffs;
				foreach (Hediff h in hediffs)
				{
					if (h is Hediff_Injury && (!h.IsPermanent() || !h.IsTended()))
					{
						num += h.Severity;
					}
				}
				List<Hediff_MissingPart> missingPartsCommonAncestors = pawn.health.hediffSet.GetMissingPartsCommonAncestors();
				foreach (Hediff_MissingPart mp in missingPartsCommonAncestors)
				{
					if (mp.IsFresh)
					{
						num += mp.Part.def.GetMaxHealth(pawn);
					}
				}
				return num > 38 * pawn.RaceProps.baseHealthScale;
			}
		}

		/// <summary>
		/// Indicates pregnancy can be aborted using usual means.
		/// </summary>
		public virtual bool canBeAborted
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Indicates pregnancy can be miscarried.
		/// </summary>
		public virtual bool canMiscarry
		{
			get
			{
				return true;
			}
		}

		public override void PostMake()
		{
			// Ensure the hediff always applies to the torso, regardless of incorrect directive
			base.PostMake();
			BodyPartRecord torso = pawn.RaceProps.body.AllParts.Find(x => x.def.defName == "Torso");
			if (Part != torso)
				Part = torso;

			//(debug->add heddif)
			//init empty preg, instabirth, cause error during birthing
			//Initialize(pawn, father);
		}

		public override bool Visible => is_discovered;

		public virtual void DiscoverPregnancy()
		{
			is_discovered = true;
			if (PawnUtility.ShouldSendNotificationAbout(this.pawn))
			{
				if (!is_checked)
				{
					string key1 = "RJW_PregnantTitle";
					string message_title = TranslatorFormattedStringExtensions.Translate(key1, pawn.LabelIndefinite());
					string key2 = "RJW_PregnantText";
					string message_text = TranslatorFormattedStringExtensions.Translate(key2, pawn.LabelIndefinite());
					Find.LetterStack.ReceiveLetter(message_title, message_text, LetterDefOf.NeutralEvent, pawn, null);

				}
				else
				{
					PregnancyMessage();
				}
			}
		}

		public virtual void CheckPregnancy()
		{
			is_checked = true;
			if (!is_discovered)
				DiscoverPregnancy();
			else
				PregnancyMessage();
		}

		public virtual void PregnancyMessage()
		{
			string key1 = "RJW_PregnantTitle";
			string message_title = TranslatorFormattedStringExtensions.Translate(key1, pawn.LabelIndefinite());
			string key2 = "RJW_PregnantNormal";
			string message_text = TranslatorFormattedStringExtensions.Translate(key2, pawn.LabelIndefinite());
			Find.LetterStack.ReceiveLetter(message_title, message_text, LetterDefOf.NeutralEvent, pawn, null);
		}

		// Quick method to simply return a body part instance by a given part name
		internal static BodyPartRecord GetPawnBodyPart(Pawn pawn, String bodyPart)
		{
			return pawn.RaceProps.body.AllParts.Find(x => x.def == DefDatabase<BodyPartDef>.GetNamed(bodyPart));
		}

		public virtual void Miscarry()
		{
			pawn.health?.RemoveHediff(this);
		}

		/// <summary>
		/// Called on abortion (noy implemented yet)
		/// </summary>
		public virtual void Abort() { }

		/// <summary>
		/// Mechanoids can remove pregnancy
		/// </summary>
		public virtual void Kill()
		{
			pawn.health?.RemoveHediff(this);
		}

		[SyncMethod]
		public Pawn partstospawn(Pawn baby, Pawn mother, Pawn dad)
		{
			//decide what parts to inherit
			//default use own parts
			Pawn partstospawn = baby;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			//spawn with mother parts
			if (mother != null && Rand.Range(0, 100) <= 10)
				partstospawn = mother;
			//spawn with father parts
			if (dad != null && Rand.Range(0, 100) <= 10)
				partstospawn = dad;

			//Log.Message("[RJW] Pregnancy partstospawn " + partstospawn);
			return partstospawn;
		}

		[SyncMethod]
		public bool spawnfutachild(Pawn baby, Pawn mother, Pawn dad)
		{
			int futachance = 0;
			if (mother != null && Genital_Helper.is_futa(mother))
				futachance = futachance + 25;
			if (dad != null && Genital_Helper.is_futa(dad))
				futachance = futachance + 25;

			//Log.Message("[RJW] Pregnancy spawnfutachild " + futachance);
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			//theres 1% change baby will be futa
			//bug ... or ... feature ... nature finds a way
			return (Rand.Range(0, 100) <= futachance);
		}

		[SyncMethod]
		public static Pawn Trytogetfather(ref Pawn mother)
		{
			//birthing with debug has no father
			//Postmake.initialize() has no father
			Log.Warning("Hediff_BasePregnancy::Trytogetfather() - debug? no father defined, trying to add one");

			Pawn pawn = mother;
			Pawn father = null;

			//possible fathers
			List<Pawn> partners = pawn.relations.RelatedPawns.Where(x =>
					Genital_Helper.has_penis(x) && !x.Dead &&
					(pawn.relations.DirectRelationExists(PawnRelationDefOf.Lover, x) ||
					pawn.relations.DirectRelationExists(PawnRelationDefOf.Fiance, x) ||
					pawn.relations.DirectRelationExists(PawnRelationDefOf.Spouse, x))
					).ToList();

			//add bonded animal
			if (xxx.is_zoophile(mother) && RJWSettings.bestiality_enabled)
				partners.AddRange(pawn.relations.RelatedPawns.Where(x =>
					Genital_Helper.has_penis(x) &&
					pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, x)).ToList());

			if (partners.Any())
			{
				father = partners.RandomElement();
				Log.Warning("Hediff_BasePregnancy::Trytogetfather() - father set to: " + xxx.get_pawnname(father));
			}

			return father;
		}

		[SyncMethod]
		public override void Tick()
		{
			ageTicks++;
			if (pawn.IsHashIntervalTick(1000) && canMiscarry)
			{
				//Log.Message("[RJW] Pregnancy is ticking for " + pawn + " this is " + this.def.defName + " will end in " + 1/progress_per_tick/TicksPerDay + " days resulting in "+ babies[0].def.defName);
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				//miscarry after starving 0.5 day
				if (pawn.needs.food != null && pawn.needs.food.CurCategory == HungerCategory.Starving && Rand.MTBEventOccurs(0.5f, TicksPerDay, MiscarryCheckInterval))
				{
					if (Visible && PawnUtility.ShouldSendNotificationAbout(pawn))
					{
						string text = "MessageMiscarriedStarvation".Translate(pawn.LabelIndefinite()).CapitalizeFirst();
						Messages.Message(text, pawn, MessageTypeDefOf.NegativeHealthEvent);
					}
					Miscarry();
					return;
				}
				//let beatings only be important when pregnancy is developed somewhat
				//miscarry after SeverelyWounded 0.5 day
				if (Visible && IsSeverelyWounded && Rand.MTBEventOccurs(0.5f, TicksPerDay, MiscarryCheckInterval))
				{
					if (Visible && PawnUtility.ShouldSendNotificationAbout(pawn))
					{
						string text = "MessageMiscarriedPoorHealth".Translate(pawn.LabelIndefinite()).CapitalizeFirst();
						Messages.Message(text, pawn, MessageTypeDefOf.NegativeHealthEvent);
					}
					Miscarry();
					return;

				}
			}

			if (pawn.Has(Quirk.Breeder) || pawn.health.hediffSet.HasHediff(HediffDef.Named("FertilityEnhancer")))
				GestationProgress += progress_per_tick * 1.25f;
			else
				GestationProgress += progress_per_tick;

			// Check if pregnancy is far enough along to "show" for the body type
			if (!is_discovered)
			{
				BodyTypeDef bodyT = pawn?.story?.bodyType;
				//float threshold = 0f;

				if ((bodyT == BodyTypeDefOf.Thin && GestationProgress > 0.25f) ||
					(bodyT == BodyTypeDefOf.Female && GestationProgress > 0.35f) ||
					(GestationProgress > 0.50f)) // todo: Modded bodies? (FemaleBB for, example)
					DiscoverPregnancy();

				//switch (bodyT)
				//{
				//case BodyType.Thin: threshold = 0.3f; break;
				//case BodyType.Female: threshold = 0.389f; break;
				//case BodyType.Male: threshold = 0.41f; break;
				//default: threshold = 0.5f; break;
				//}
				//if (GestationProgress > threshold){ DiscoverPregnancy(); }
			}

			// no idea if it has any effect on guests that left player map
			if (CurStageIndex == 3)
			{
				if (contractions == 0)
				{
					if (PawnUtility.ShouldSendNotificationAbout(this.pawn))
					{
						string text = "RJW_Contractions".Translate(pawn.LabelIndefinite());
						Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);
					}
				}
				contractions++;
				if (contractions >= TicksPerHour)//birthing takes an hour
				{
					if (PawnUtility.ShouldSendNotificationAbout(this.pawn))
					{
						string message_title = "RJW_GaveBirthTitle".Translate(pawn.LabelIndefinite());
						string message_text = "RJW_GaveBirthText".Translate(pawn.LabelIndefinite());
						string baby_text = ((babies.Count == 1) ? "RJW_ABaby".Translate() : "RJW_NBabies".Translate(babies.Count));
						message_text = message_text + baby_text;
						Find.LetterStack.ReceiveLetter(message_title, message_text, LetterDefOf.PositiveEvent, pawn);
					}
					GiveBirth();
				}
			}
		}

		//
		//These functions are different from CnP
		//
		public override void ExposeData()//If you didn't know, this one is used to save the object for later loading of the game... and to load the data into the object, <sigh>
		{
			base.ExposeData();
			Scribe_References.Look(ref father, "father");
			Scribe_Values.Look(ref is_checked, "is_checked");
			Scribe_Values.Look(ref is_hacked, "is_hacked");
			Scribe_Values.Look(ref is_discovered, "is_discovered");
			Scribe_Values.Look(ref contractions, "contractions");
			Scribe_Collections.Look(ref babies, saveDestroyedThings: true, label: "babies", lookMode: LookMode.Deep, ctorArgs: new object[0]);
			Scribe_Values.Look(ref progress_per_tick, "progress_per_tick", 1);
		}

		//This should generate pawns to be born in due time. Should take into account all settings and parent races
		[SyncMethod]
		protected virtual void GenerateBabies()
		{
			Pawn mother = pawn;
			//Log.Message("Generating babies for " + this.def.defName);
			if (mother == null)
			{
				Log.Error("Hediff_BasePregnancy::GenerateBabies() - no mother defined");
				return;
			}

			if (father == null)
			{
				father = Trytogetfather(ref mother);
			}

			//Babies will have average number of traits of their parents, this way it will hopefully be compatible with various mods that change number of allowed traits
			//int trait_count = 0;
			// not anymore. Using number of traits originally generated by game as a guide

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			List<Trait> traitpool = new List<Trait>(); // if both parents has the same trait chanses to give it to child are doubled.
													   // It was a bug, now it is a feature.

			float skin_whiteness = Rand.Range(0, 1);

			//Log.Message("[RJW]Generating babies " + traitpool.Count + " traits at first");
			if (xxx.has_traits(mother) && mother.RaceProps.Humanlike)
			{
				foreach (Trait momtrait in mother.story.traits.allTraits)
				{
					if (!RJWPregnancySettings.trait_filtering_enabled || !non_genetic_traits.Contains(momtrait.def.defName))
						traitpool.Add(momtrait);
				}
				skin_whiteness = mother.story.melanin;
				//trait_count = mother.story.traits.allTraits.Count;
			}
			if (father != null && xxx.has_traits(father) && father.RaceProps.Humanlike)
			{
				foreach (Trait poptrait in father.story.traits.allTraits)
				{
					if (!RJWPregnancySettings.trait_filtering_enabled || !non_genetic_traits.Contains(poptrait.def.defName))
						traitpool.Add(poptrait);
				}
				skin_whiteness = Rand.Range(skin_whiteness, father.story.melanin);
				//trait_count = Mathf.RoundToInt((trait_count + father.story.traits.allTraits.Count) / 2f);
			}
			//this probably doesnt matter in 1.0 remove it and wait for bug reports =)
			//trait_count = trait_count > 2 ? trait_count : 2;//Tynan has hardcoded 2-3 traits per pawn.  In the above, result may be less than that. Let us not have disfunctional babies.

			//Log.Message("[RJW]Generating babies " + traitpool.Count + " traits aFter parents");
			Pawn parent = mother; //race of child
			Pawn parent2 = father; //name for child

			//Decide on which parent is first to be inherited
			if (father != null && RJWPregnancySettings.use_parent_method)
			{
				//Log.Message("The baby race needs definition");
				//humanality
				if (xxx.is_human(mother) && xxx.is_human(father))
				{
					//Log.Message("It's of two humanlikes");
					if (!Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother))
					{
						//Log.Message("Mother will birth father race");
						parent = father;
					}
					else
					{
						//Log.Message("Mother will birth own race");
					}
				}
				else
				{
					//bestiality
					if ((!xxx.is_human(mother) && xxx.is_human(father)) ||
						(xxx.is_human(mother) && !xxx.is_human(father)))
					{
						if (RJWPregnancySettings.bestiality_DNA_inheritance == 0.0f)
						{
							//Log.Message("mother will birth beast");
							if (xxx.is_human(mother))
								parent = father;
							else
								parent = mother;
						}
						else if (RJWPregnancySettings.bestiality_DNA_inheritance == 1.0f)
						{
							//Log.Message("mother will birth humanlike");
							if (xxx.is_human(mother))
								parent = mother;
							else
								parent = father;
						}
						else
						{
							if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother))
							{
								//Log.Message("Mother will birth father race");
								parent = father;
							}
							else
							{
								//Log.Message("Mother will birth own race");
							}
						}

					}
					//animality
					else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother))
					{
						//Log.Message("Mother will birth father race");
						parent = father;
					}
					else
					{
						//Log.Message("Mother will birth own race");
					}
				}
			}
			//androids can only birth non androids
			if (AndroidsCompatibility.IsAndroid(mother) && !AndroidsCompatibility.IsAndroid(father))
			{
				parent = father;
			}
			else if (!AndroidsCompatibility.IsAndroid(mother) && AndroidsCompatibility.IsAndroid(father))
			{
				parent = mother;
			}
			else if (AndroidsCompatibility.IsAndroid(mother) && AndroidsCompatibility.IsAndroid(father))
			{
				Log.Warning("Both parents are andoids, what have you done monster!");
				//this should never happen but w/e
			}
			//slimes birth only other slimes
			if (mother.kindDef.race.defName.ToLower().Contains("slime"))
			{
				parent = mother;
			}
			if (father != null)
				if (father.kindDef.race.defName.ToLower().Contains("slime"))
				{
					parent = father;
				}
			//Log.Message("[RJW] The main parent is " + parent);
			//Log.Message("Mother: " + xxx.get_pawnname(mother) + " kind: " + mother.kindDef);
			//Log.Message("Father: " + xxx.get_pawnname(father) + " kind: " + father.kindDef);
			//Log.Message("Baby base: " + xxx.get_pawnname(parent) + " kind: " + parent.kindDef);

			// Surname passing
			string last_name = "";
			if (xxx.is_human(father))
				last_name = NameTriple.FromString(father.Name.ToStringFull).Last;
			if (xxx.is_human(mother) && last_name == "")
				last_name = NameTriple.FromString(mother.Name.ToStringFull).Last;
			//Log.Message("[RJW] Bady surname will be " + last_name);

			//Pawn generation request
			int MapTile = -1;
			PawnKindDef spawn_kind_def = parent.kindDef;
			Faction spawn_faction = mother.IsPrisoner ? null : mother.Faction;
			PawnGenerationRequest request = new PawnGenerationRequest(spawn_kind_def, spawn_faction, PawnGenerationContext.NonPlayer, MapTile, true, true, false, false, false, false, 1, false, true, false, false, false, false, false, null, null, null, 0, 0, null, skin_whiteness, last_name);

			//Log.Message("[RJW] Generated request, making babies");
			//Litter size. Let's use the main parent litter size, increased by fertility.
			float litter_size = (parent.RaceProps.litterSizeCurve == null) ? 1 : Rand.ByCurve(parent.RaceProps.litterSizeCurve);
			//Log.Message("[RJW] base Litter size " + litter_size);
			litter_size *= Math.Min(mother.health.capacities.GetLevel(xxx.reproduction), 1);
			litter_size *= Math.Min(father == null ? 1 : father.health.capacities.GetLevel(xxx.reproduction), 1);
			litter_size = Math.Max(1, litter_size);
			//Log.Message("[RJW] Litter size (w fertility) " + litter_size);

			//Babies size vs mother body size
			//assuming mother belly is 1/3 of mother body size
			float baby_size = spawn_kind_def.RaceProps.lifeStageAges[0].def.bodySizeFactor * spawn_kind_def.RaceProps.baseBodySize; // adult size/5
																																	//Log.Message("[RJW] Baby size " + baby_size);
			float max_litter = 1f / 3f / baby_size;
			//Log.Message("[RJW] Max size " + max_litter);
			max_litter *= (mother.Has(Quirk.Breeder) || mother.Has(Quirk.Incubator)) ? 2 : 1;
			//Log.Message("[RJW] Max size (w quirks) " + max_litter);

			//Generate random amount of babies within litter/max size
			litter_size = (Rand.Range(litter_size, max_litter));
			//Log.Message("[RJW] Litter size roll 1:" + litter_size);
			litter_size = Mathf.RoundToInt(litter_size);
			//Log.Message("[RJW] Litter size roll 2:" + litter_size);
			litter_size = Math.Max(1, litter_size);
			//Log.Message("[RJW] final Litter size " + litter_size);

			for (int i = 0; i < litter_size; i++)
			{
				Pawn baby = PawnGenerator.GeneratePawn(request);
				//Choose traits to add to the child. Unlike CnP this will allow for some random traits
				if (xxx.is_human(baby) && traitpool.Count > 0)
				{
					updateTraits(baby, traitpool);
				}
				babies.Add(baby);
			}
		}

		/// <summary>
		/// Update pawns traits
		/// Uses original pawns trains and given list of traits as a source of traits to select.
		/// </summary>
		/// <param name="pawn">humanoid pawn</param>
		/// <param name="traitpool">list of traits</param>
		/// <param name="traitLimit">maximum allowed number of traits</param>
		void updateTraits(Pawn pawn, List<Trait> traitpool, int traitLimit = -1)
		{
			if (pawn?.story?.traits == null)
			{
				return;
			}

			if (traitLimit == -1)
			{
				traitLimit = pawn.story.traits.allTraits.Count;
			}

			// personal pool for this pawn. Mixing parents and game suggested traits.
			List<Trait> personalTraitPool = new List<Trait>(pawn.story.traits.allTraits);
			if (traitpool != null)
			{
				personalTraitPool.AddRange(traitpool);
			}

			List<Trait> selectedTraits = new List<Trait>();
			var forcedTraits = personalTraitPool
				.Where(x => x.ScenForced)
				.Distinct(new TraitComparer(ignoreDegree: true)); // result can be a mess, because game allows this mess to be created in scenario editor

			selectedTraits.AddRange(forcedTraits); // enforcing scenario forced traits

			var comparer = new TraitComparer(); // trait comparision implementation, because without game compares traits *by reference*, makeing them all unique.

			while (selectedTraits.Count < traitLimit && personalTraitPool.Count > 0)
			{
				int index = Rand.Range(0, personalTraitPool.Count); // getting trait and removing from the pull
				var trait = personalTraitPool[index];
				personalTraitPool.RemoveAt(index);

				if (!selectedTraits.Any(x => comparer.Equals(x, trait) ||  // skipping traits conflicting with already added
				                             x.def.ConflictsWith(trait)))
				{
					selectedTraits.Add(new Trait(trait.def, trait.Degree, false));
				}
			}

			pawn.story.traits.allTraits = selectedTraits;
		}

		//Handles the spawning of pawns and adding relations
		//this is extended by other scripts
		public abstract void GiveBirth();

		public virtual void PostBirth(Pawn mother, Pawn father, Pawn baby)
		{
			//inject RJW_BabyState to the newborn if RimWorldChildren is not active
			//cnp patches its hediff right into pawn generator, so its already in if it can
			if (xxx.RimWorldChildrenIsActive)
			{
				if (xxx.is_human(mother))
				{
					//BnC compatibility
					if (DefDatabase<HediffDef>.GetNamedSilentFail("BnC_RJW_PostPregnancy") == null)
					{
						mother.health.AddHediff(HediffDef.Named("PostPregnancy"), null, null);
						mother.health.AddHediff(HediffDef.Named("Lactating"), mother.RaceProps.body.AllParts.Find(x => x.def.defName == "Chest"), null);
					}
					if (xxx.is_human(baby))
						if (mother.records.GetAsInt(xxx.CountOfBirthHuman) == 0 &&
							mother.records.GetAsInt(xxx.CountOfBirthAnimal) == 0 &&
							mother.records.GetAsInt(xxx.CountOfBirthEgg) == 0)
						{
							mother.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("IGaveBirthFirstTime"));
						}
						else
						{
							mother.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("IGaveBirth"));
						}
				}

				if (xxx.is_human(baby))
					if (xxx.is_human(father))
					{
						father.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("PartnerGaveBirth"));
					}
			}

			BabyPostBirth(mother, father, baby);

			if (baby.playerSettings != null && mother.playerSettings != null)
			{
				baby.playerSettings.AreaRestriction = mother.playerSettings.AreaRestriction;
			}

			//spawn futa
			bool isfuta = spawnfutachild(baby, mother, father);

			SexPartAdder.add_anus(baby, partstospawn(baby, mother, father));

			if (baby.gender == Gender.Female || isfuta)
				SexPartAdder.add_breasts(baby, partstospawn(baby, mother, father), Gender.Female);

			if (isfuta)
			{
				SexPartAdder.add_genitals(baby, partstospawn(baby, mother, father), Gender.Male);
				SexPartAdder.add_genitals(baby, partstospawn(baby, mother, father), Gender.Female);
				baby.gender = Gender.Female;                //set gender to female for futas, should cause no errors since babies already generated with relations n stuff
			}
			else
				SexPartAdder.add_genitals(baby, partstospawn(baby, mother, father));

			if (mother.Spawned)
			{
				// Move the baby in front of the mother, rather than on top
				if (mother.CurrentBed() != null)
				{
					baby.Position = baby.Position + new IntVec3(0, 0, 1).RotatedBy(mother.CurrentBed().Rotation);
				}
				// Spawn guck
				FilthMaker.MakeFilth(mother.Position, mother.Map, ThingDefOf.Filth_AmnioticFluid, mother.LabelIndefinite(), 5);
				mother.caller?.DoCall();
				baby.caller?.DoCall();
				father?.caller?.DoCall();
			}

			if (xxx.is_human(baby))
				mother.records.AddTo(xxx.CountOfBirthHuman, 1);
			if (xxx.is_animal(baby))
				mother.records.AddTo(xxx.CountOfBirthAnimal, 1);

			if ((mother.records.GetAsInt(xxx.CountOfBirthHuman) > 10 || mother.records.GetAsInt(xxx.CountOfBirthAnimal) > 20))
			{
				mother.Add(Quirk.Breeder);
				mother.Add(Quirk.ImpregnationFetish);
			}
		}

		public static void BabyPostBirth(Pawn mother, Pawn father, Pawn baby)
		{
			if (!xxx.is_human(baby)) return;

			baby.story.childhood = null;
			baby.story.adulthood = null;

			if (!xxx.RimWorldChildrenIsActive)
			{
				if (baby.ageTracker.CurLifeStageIndex <= 1 && baby.ageTracker.AgeBiologicalYears < 1 && !baby.Dead)
				{
					// Clean out drugs, implants, and stuff randomly generated
					//no support for custom race stuff, if there is any
					baby.health.hediffSet.Clear();
					baby.health.AddHediff(HediffDef.Named("RJW_BabyState"), null, null);//RJW_Babystate.tick_rare actually forces CnP switch to CnP one if it can, don't know wat do
					Hediff_SimpleBaby babystate = (Hediff_SimpleBaby)baby.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_BabyState"));
					if (babystate != null)
					{
						babystate.GrowUpTo(0, true);
					}
				}
			}
			else
			{
				Log.Message("[RJW]PostBirth:: Rewriting story of " + baby);
				//var disabledBaby = BackstoryDatabase.allBackstories["CustomBackstory_NA_Childhood_Disabled"]; // should be this, but bnc/cnp is broken and cant undisable work
				var BabyStory = BackstoryDatabase.allBackstories["CustomBackstory_NA_Childhood"];
				if (BabyStory != null)
				{
					baby.story.childhood = BabyStory;
				}
				else
				{
					Log.Error("Couldn't find the required Backstory: CustomBackstory_NA_Childhood!");
				}
				//BnC compatibility
				if (DefDatabase<HediffDef>.GetNamedSilentFail("BnC_RJW_PostPregnancy") != null)
				{
					baby.health.AddHediff(HediffDef.Named("BabyState"), null, null);
					baby.health.AddHediff(HediffDef.Named("NoManipulationFlag"), null, null);
				}
			}

			foreach (var skill in baby.skills?.skills)
				skill.Level = 0;
		}

		//This method is doing the work of the constructor since hediffs are created through HediffMaker instead of normal oop way
		//This can't be in PostMake() because there wouldn't be father.
		public virtual void Initialize(Pawn mother, Pawn dad)
		{
			BodyPartRecord torso = mother.RaceProps.body.AllParts.Find(x => x.def.defName == "Torso");
			mother.health.AddHediff(this, torso);
			//Log.Message("[RJW]" + this.GetType().ToString() + " pregnancy hediff generated: " + this.Label);
			//Log.Message("[RJW]" + this.GetType().ToString() + " mother: " + mother + " father: " + dad);
			father = dad;
			if (father != null)
			{
				babies = new List<Pawn>();
				contractions = 0;
				//Log.Message("[RJW]" + this.GetType().ToString() + " generating babies before: " + this.babies.Count);
				GenerateBabies();
			}
			//progress_per_tick = babies.NullOrEmpty() ? 1f : (1.0f) / (babies[0].RaceProps.gestationPeriodDays * TicksPerDay/50);
			progress_per_tick = babies.NullOrEmpty() ? 1f : (1.0f) / (babies[0].RaceProps.gestationPeriodDays * TicksPerDay);
			//Log.Message("[RJW]" + this.GetType().ToString() + " generating babies after: " + this.babies.Count);
		}

		private static Dictionary<Type, string> _hediffOfClass = null;
		protected static Dictionary<Type, string> hediffOfClass
		{
			get
			{
				if (_hediffOfClass == null)
				{
					_hediffOfClass = new Dictionary<Type, string>();
					var allRJWPregnancies = AppDomain.CurrentDomain.GetAssemblies()
						.SelectMany(
							a => a
							.GetTypes()
							.Where(t => t.IsSubclassOf(typeof(Hediff_BasePregnancy)))
						);

					foreach (var pregClass in allRJWPregnancies)
					{
						var attribute = (RJWAssociatedHediffAttribute)pregClass.GetCustomAttributes(typeof(RJWAssociatedHediffAttribute), false).FirstOrDefault();
						if (attribute != null)
						{
							_hediffOfClass[pregClass] = attribute.defName;
						}
					}
				}

				return _hediffOfClass;
			}
		}

		/// <summary>
		/// Creates pregnancy hediff and assigns it to mother
		/// </summary>
		/// <typeparam name="T">type of pregnancy, should be subclass of Hediff_BasePregnancy</typeparam>
		/// <param name="mother"></param>
		/// <param name="father"></param>
		/// <returns>created hediff</returns>
		public static T Create<T>(Pawn mother, Pawn father) where T : Hediff_BasePregnancy
		{
			if (mother == null)
				return null;
			BodyPartRecord torso = mother.RaceProps.body.AllParts.Find(x => x.def.defName == "Torso");

			string defName = hediffOfClass.ContainsKey(typeof(T)) ? hediffOfClass[typeof(T)] : "RJW_pregnancy";
			if (Prefs.DevMode) Log.Message($"Hediff_BasePregnancy::create hediff:{defName} class:{typeof(T).FullName}");

			T hediff = HediffHelper.MakeHediff<T>(HediffDef.Named(defName), mother, torso);
			hediff.Initialize(mother, father);
			return hediff;
		}

		/// <summary>
		/// list of all known RJW pregnancy hediff names (new can be regicreted by mods)
		/// </summary>
		/// <returns></returns>
		public static IEnumerable<string> KnownPregnancies()
		{
			return hediffOfClass.Values.Distinct(); // todo: performance
		}

		public override string DebugString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(base.DebugString());
			stringBuilder.AppendLine("Gestation progress: " + GestationProgress.ToStringPercent());
			//stringBuilder.AppendLine("Time left: " + ((int)((1f - GestationProgress) * babies[0].RaceProps.gestationPeriodDays * TicksPerDay/50)).ToStringTicksToPeriod());
			stringBuilder.AppendLine("Time left: " + ((int)((1f - GestationProgress) * babies[0].RaceProps.gestationPeriodDays * TicksPerDay)).ToStringTicksToPeriod());
			stringBuilder.AppendLine(" Father: " + xxx.get_pawnname(father));
			return stringBuilder.ToString();
		}
	}
}


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

namespace rjw
{
	///<summary>
	///This hediff class simulates pregnancy with animal children, mother may be human. It is not intended to be reasonable.
	///Differences from humanlike pregnancy are that animals are given some training and that less punishing relations are used for parent-child.
	///</summary>
	[RJWAssociatedHediff("RJW_pregnancy_beast")]
	public class Hediff_BestialPregnancy : Hediff_BasePregnancy
	{
		private static readonly PawnRelationDef relation_birthgiver = DefDatabase<PawnRelationDef>.AllDefs.FirstOrDefault(d => d.defName == "RJW_Sire");
		private static readonly PawnRelationDef relation_spawn = DefDatabase<PawnRelationDef>.AllDefs.FirstOrDefault(d => d.defName == "RJW_Pup");
		//static int max_train_level = TrainableUtility.TrainableDefsInListOrder.Sum(tr => tr.steps);

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

		//Makes half-human babies start off better. They start obedient, and if mother is a human, they get hediff to boost their training
		protected void train(Pawn baby, Pawn mother, Pawn father)
		{
			bool _;
			if (!xxx.is_human(baby) && baby.Faction == Faction.OfPlayer)
			{
				if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Obedience, out _).Accepted)
				{
					baby.training.Train(TrainableDefOf.Obedience, mother);
				}
				if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Tameness, out _).Accepted)
				{
					baby.training.Train(TrainableDefOf.Tameness, mother);
				}
			}
			//baby.RaceProps.TrainableIntelligence.LabelCap.
			//if (xxx.is_human(mother))
			//{
			//	Let the animals be born as colony property
			//	if (mother.IsPrisonerOfColony || mother.IsColonist)
			//	{
			//		baby.SetFaction(Faction.OfPlayer);
			//	}
			//	let it be trained half to the max
			//	var baby_int = baby.RaceProps.TrainableIntelligence;
			//	int max_int = TrainableUtility.TrainableDefsInListOrder.FindLastIndex(tr => (tr.requiredTrainableIntelligence == baby_int));
			//	if (max_int == -1)
			//		return;
			//	Log.Message("RJW training " + baby + " max_int is " + max_int);
			//	var available_tricks = TrainableUtility.TrainableDefsInListOrder.GetRange(0, max_int + 1);
			//	int max_steps = available_tricks.Sum(tr => tr.steps);
			//	Log.Message("RJW training " + baby + " vill do " + max_steps/2 + " steps");
			//	int t_score = Rand.Range(Mathf.RoundToInt(max_steps / 4), Mathf.RoundToInt(max_steps / 2));
			//	for (int i = 1; i <= t_score; i++)
			//	{
			//		var tr = available_tricks.Where(t => !baby.training.IsCompleted(t)). RandomElement();
			//		Log.Message("RJW training " + baby + " for " + tr);
			//		baby.training.Train(tr, mother);
			//	}

			//	baby.health.AddHediff(HediffDef.Named("RJW_smartPup"));
			//}
		}

		//Handles the spawning of pawns and adding relations
		public override void GiveBirth()
		{
			Pawn mother = pawn;
			if (mother == null)
				return;
			try
			{
				//fail if hediff added through debug, since babies not initialized
				if (babies.Count > 9999)
					Log.Message("RJW beastiality/animal pregnancy birthing pawn count: " + babies.Count);
			}
			catch
			{
				if (father == null)
				{
					Log.Message("RJW beastiality/animal pregnancy father is null(debug?), setting father to mother");
					father = mother;
				}

				Initialize(mother, father);
			}
			List<Pawn> siblings = new List<Pawn>();
			foreach (Pawn baby in babies)
			{
				PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother);

				Need_Sex sex_need = mother.needs.TryGetNeed<Need_Sex>();
				if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null)
				{
					sex_need.CurLevel = 1.0f;
				}

				baby.relations.AddDirectRelation(relation_birthgiver, mother);
				mother.relations.AddDirectRelation(relation_spawn, baby);
				if (father != null && mother != father)
				{
					baby.relations.AddDirectRelation(relation_birthgiver, father);
					father.relations.AddDirectRelation(relation_spawn, baby);
				}

				foreach (Pawn sibling in siblings)
				{
					baby.relations.AddDirectRelation(PawnRelationDefOf.Sibling, sibling);
				}
				siblings.Add(baby);
				train(baby, mother, father);

				PostBirth(mother, father, baby);

				mother.health.RemoveHediff(this);
			}
		}
	}
}


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

namespace rjw
{
    [RJWAssociatedHediff("RJW_pregnancy")]
	public class Hediff_HumanlikePregnancy : Hediff_BasePregnancy
	///<summary>
	///This hediff class simulates pregnancy resulting in humanlike childs.
	///</summary>
	{
		//Handles the spawning of pawns and adding relations
		public override void GiveBirth()
		{
			Pawn mother = pawn;
			if (mother == null)
				return;
			try
			{
				//fail if hediff added through debug, since babies not initialized
				if (babies.Count > 9999)
					Log.Message("RJW humanlike pregnancy birthing pawn count: " + babies.Count);
			}
			catch
			{
				if (father == null)
				{
					Log.Message("RJW humanlike pregnancy father is null(debug?), setting father to mother");
					father = mother;
				}

				Initialize(mother, father);
			}
			List<Pawn> siblings = new List<Pawn>();
			foreach (Pawn baby in babies)
			{
				PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother);

				var sex_need = mother.needs.TryGetNeed<Need_Sex>();
				if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null)
				{
					sex_need.CurLevel = 1.0f;
				}
				if (mother.Faction != null)
				{
					if (mother.Faction != baby.Faction)
						baby.SetFaction(mother.Faction);
				}
				if (mother.IsPrisonerOfColony)
				{
					baby.guest.CapturedBy(Faction.OfPlayer);
				}

				baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, mother);
				if (father != null)
				{
					baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, father);
				}

				foreach (Pawn sibling in siblings)
				{
					baby.relations.AddDirectRelation(PawnRelationDefOf.Sibling, sibling);
				}
				siblings.Add(baby);

				PostBirth(mother, father, baby);

				mother.health.RemoveHediff(this);
			}
		}
	}
}