code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using Verse.Sound;
using Multiplayer.API;
using System.Collections.ObjectModel;
//using static RimWorld.Planet.CaravanInventoryUtility;
//using RimWorldChildren;

namespace rjw
{
	public static class xxx
	{
		public static readonly BindingFlags ins_public_or_no = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		public static readonly config config = DefDatabase<config>.GetNamed("the_one");

		//HARDCODED MAGIC USED ACROSS DOZENS OF FILES, this is as bad place to put it as any other
		//Should at the very least be encompassed in the related designation type
		public static readonly int max_rapists_per_prisoner = 6;

		//CombatExtended
		public static bool CombatExtendedIsActive;
		public static HediffDef MuscleSpasms;

		//RomanceDiversified
		public static bool RomanceDiversifiedIsActive; //A dirty way to check if the mod is active
		public static TraitDef straight;
		public static TraitDef faithful;
		public static TraitDef philanderer;
		public static TraitDef polyamorous;

		//Psychology
		public static bool PsychologyIsActive;
		public static TraitDef prude;
		public static TraitDef lecher;
		public static TraitDef polygamous;

		//Other poly mods
		public static bool AnyPolyamoryModIsActive;
		public static bool AnyPolygamyModIsActive;

		//[SYR] Individuality
		public static bool IndividualityIsActive;
		public static TraitDef SYR_CreativeThinker;
		public static TraitDef SYR_Haggler;

		//Rimworld of Magic
		public static bool RoMIsActive;
		public static TraitDef Succubus;
		public static TraitDef Warlock;
		public static NeedDef TM_Mana;
		public static HediffDef TM_ShapeshiftHD;

		//Nightmare Incarnation
		public static bool NightmareIncarnationIsActive;
		public static NeedDef NI_Need_Mana;

		//Consolidated
		public static bool CTIsActive;
		public static TraitDef RCT_NeatFreak;
		public static TraitDef RCT_Savant;
		public static TraitDef RCT_Inventor;
		public static TraitDef RCT_AnimalLover;

		//SimpleSlavery
		public static bool SimpleSlaveryIsActive;
		public static HediffDef Enslaved;

		//Dubs Bad Hygiene
		public static bool DubsBadHygieneIsActive;
		public static NeedDef DBHThirst;

		//Alien Framework
		public static bool AlienFrameworkIsActive;
		public static TraitDef xenophobia; // Degrees: 1: xenophobe, -1: xenophile

		//Immortals
		public static bool ImmortalsIsActive;
		public static HediffDef IH_Immortal;

		//Children&Pregnancy
		public static bool RimWorldChildrenIsActive;
		public static HediffDef BabyState;
		public static HediffDef PostPregnancy;
		public static HediffDef Lactating;
		public static HediffDef NoManipulationFlag;
		public static ThoughtDef IGaveBirthFirstTime;
		public static ThoughtDef IGaveBirth;
		public static ThoughtDef PartnerGaveBirth;

		//Babies and Children(pretty much above + this)
		public static HediffDef BnC_RJW_PostPregnancy;

		//RJW-EX
		public static bool RjwExIsActive;
		public static HediffDef RjwEx_Armbinder;

		//RJW Children
		public static HediffDef RJW_BabyState = DefDatabase<HediffDef>.GetNamed("RJW_BabyState");
		public static HediffDef RJW_NoManipulationFlag = DefDatabase<HediffDef>.GetNamed("RJW_NoManipulationFlag");

		//rjw
		public static readonly TraitDef nymphomaniac = DefDatabase<TraitDef>.GetNamed("Nymphomaniac");
		public static readonly TraitDef rapist = DefDatabase<TraitDef>.GetNamed("Rapist");
		public static readonly TraitDef masochist = DefDatabase<TraitDef>.GetNamed("Masochist");
		public static readonly TraitDef necrophiliac = DefDatabase<TraitDef>.GetNamed("Necrophiliac");
		public static readonly TraitDef zoophile = DefDatabase<TraitDef>.GetNamed("Zoophile");

		public static readonly TraitDef footSlut = DefDatabase<TraitDef>.GetNamed("FootSlut");
		public static readonly TraitDef cumSlut = DefDatabase<TraitDef>.GetNamed("CumSlut");
		public static readonly TraitDef buttSlut = DefDatabase<TraitDef>.GetNamed("ButtSlut");

		//The Hediff to prevent reproduction
		public static readonly HediffDef sterilized = DefDatabase<HediffDef>.GetNamed("Sterilized");

		//The Hediff for broken body(resulted from being raped as CP for too many times)
		public static readonly HediffDef feelingBroken = DefDatabase<HediffDef>.GetNamed("FeelingBroken");

		public static readonly HediffDef submitting = DefDatabase<HediffDef>.GetNamed("Hediff_Submitting");

		public static PawnCapacityDef reproduction = DefDatabase<PawnCapacityDef>.GetNamed("RJW_Fertility");

		//rjw Body parts
		public static readonly BodyPartDef genitalsDef = DefDatabase<BodyPartDef>.GetNamed("Genitals");
		public static readonly BodyPartDef breastsDef = DefDatabase<BodyPartDef>.GetNamed("Chest");

		public static readonly BodyPartDef flankDef = DefDatabase<BodyPartDef>.GetNamed("Flank");
		public static readonly BodyPartDef anusDef = DefDatabase<BodyPartDef>.GetNamed("Anus");

		//aftersex thoughts
		public static readonly ThoughtDef got_raped = DefDatabase<ThoughtDef>.GetNamed("GotRaped");
		public static readonly ThoughtDef got_anal_raped = DefDatabase<ThoughtDef>.GetNamed("GotAnalRaped");
		public static readonly ThoughtDef got_anal_raped_byfemale = DefDatabase<ThoughtDef>.GetNamed("GotAnalRapedByFemale");
		public static readonly ThoughtDef got_raped_unconscious = DefDatabase<ThoughtDef>.GetNamed("GotRapedUnconscious");
		public static readonly ThoughtDef masochist_got_raped_unconscious = DefDatabase<ThoughtDef>.GetNamed("MasochistGotRapedUnconscious");

		public static readonly ThoughtDef got_bred = DefDatabase<ThoughtDef>.GetNamed("GotBredByAnimal");
		public static readonly ThoughtDef got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("GotAnalBredByAnimal");
		public static readonly ThoughtDef got_licked = DefDatabase<ThoughtDef>.GetNamed("GotLickedByAnimal");
		public static readonly ThoughtDef got_groped = DefDatabase<ThoughtDef>.GetNamed("GotGropedByAnimal");

		public static readonly ThoughtDef masochist_got_raped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotRaped");
		public static readonly ThoughtDef masochist_got_anal_raped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalRaped");
		public static readonly ThoughtDef masochist_got_anal_raped_byfemale = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalRapedByFemale");
		public static readonly ThoughtDef masochist_got_bred = DefDatabase<ThoughtDef>.GetNamed("MasochistGotBredByAnimal");
		public static readonly ThoughtDef masochist_got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("MasochistGotAnalBredByAnimal");
		public static readonly ThoughtDef masochist_got_licked = DefDatabase<ThoughtDef>.GetNamed("MasochistGotLickedByAnimal");
		public static readonly ThoughtDef masochist_got_groped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotGropedByAnimal");
		public static readonly ThoughtDef allowed_animal_to_breed = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToBreed");
		public static readonly ThoughtDef allowed_animal_to_lick = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToLick");
		public static readonly ThoughtDef allowed_animal_to_grope = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToGrope");
		public static readonly ThoughtDef zoophile_got_bred = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotBredByAnimal");
		public static readonly ThoughtDef zoophile_got_anal_bred = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotAnalBredByAnimal");
		public static readonly ThoughtDef zoophile_got_licked = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotLickedByAnimal");
		public static readonly ThoughtDef zoophile_got_groped = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotGropedByAnimal");
		public static readonly ThoughtDef hate_my_rapist = DefDatabase<ThoughtDef>.GetNamed("HateMyRapist");
		public static readonly ThoughtDef kinda_like_my_rapist = DefDatabase<ThoughtDef>.GetNamed("KindaLikeMyRapist");
		public static readonly ThoughtDef allowed_me_to_get_raped = DefDatabase<ThoughtDef>.GetNamed("AllowedMeToGetRaped");
		public static readonly ThoughtDef stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("StoleSomeLovin");
		public static readonly ThoughtDef bloodlust_stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("BloodlustStoleSomeLovin");
		public static readonly ThoughtDef violated_corpse = DefDatabase<ThoughtDef>.GetNamed("ViolatedCorpse");
		public static readonly ThoughtDef gave_virginity = DefDatabase<ThoughtDef>.GetNamed("FortunateGaveVirginity");
		public static readonly ThoughtDef lost_virginity = DefDatabase<ThoughtDef>.GetNamed("UnfortunateLostVirginity");
		public static readonly ThoughtDef took_virginity = DefDatabase<ThoughtDef>.GetNamed("TookVirginity");

		public static readonly JobDef Masturbate = DefDatabase<JobDef>.GetNamed("RJW_Masturbate");
		public static readonly JobDef casual_sex = DefDatabase<JobDef>.GetNamed("JoinInBed");
		public static readonly JobDef knotted = DefDatabase<JobDef>.GetNamed("RJW_Knotted");
		public static readonly JobDef gettin_loved = DefDatabase<JobDef>.GetNamed("GettinLoved");
		public static readonly JobDef gettin_raped = DefDatabase<JobDef>.GetNamed("GettinRaped");
		public static readonly JobDef gettin_bred = DefDatabase<JobDef>.GetNamed("GettinBred");
		public static readonly JobDef RapeCP = DefDatabase<JobDef>.GetNamed("RapeComfortPawn");
		public static readonly JobDef RapeEnemy = DefDatabase<JobDef>.GetNamed("RapeEnemy");
		public static readonly JobDef RapeRandom = DefDatabase<JobDef>.GetNamed("RandomRape");
		public static readonly JobDef RapeCorpse = DefDatabase<JobDef>.GetNamed("ViolateCorpse");
		public static readonly JobDef bestiality = DefDatabase<JobDef>.GetNamed("Bestiality");
		public static readonly JobDef bestialityForFemale = DefDatabase<JobDef>.GetNamed("BestialityForFemale");
		public static readonly JobDef whore_inviting_visitors = DefDatabase<JobDef>.GetNamedSilentFail("WhoreInvitingVisitors");
		public static readonly JobDef whore_is_serving_visitors = DefDatabase<JobDef>.GetNamedSilentFail("WhoreIsServingVisitors");
		public static readonly JobDef animalMate = DefDatabase<JobDef>.GetNamed("RJW_Mate");
		public static readonly JobDef animalBreed = DefDatabase<JobDef>.GetNamed("Breed");
		public static readonly JobDef quick_sex = DefDatabase<JobDef>.GetNamed("Quickie");
		public static readonly JobDef getting_quickie = DefDatabase<JobDef>.GetNamed("GettingQuickie");
		public static readonly JobDef struggle_in_BondageGear = DefDatabase<JobDef>.GetNamed("StruggleInBondageGear");
		public static readonly JobDef unlock_BondageGear = DefDatabase<JobDef>.GetNamed("UnlockBondageGear");
		public static readonly JobDef give_BondageGear = DefDatabase<JobDef>.GetNamed("GiveBondageGear");

		public static readonly FleckDef mote_noheart = DefDatabase<FleckDef>.GetNamed("Mote_NoHeart");

		public static readonly StatDef sex_satisfaction = DefDatabase<StatDef>.GetNamed("SexSatisfaction");
		public static readonly StatDef vulnerability_stat = DefDatabase<StatDef>.GetNamed("Vulnerability");
		public static readonly StatDef sex_drive_stat = DefDatabase<StatDef>.GetNamed("SexFrequency");

		public static readonly RecordDef GetRapedAsComfortPawn = DefDatabase<RecordDef>.GetNamed("GetRapedAsComfortPrisoner");
		public static readonly RecordDef CountOfFappin = DefDatabase<RecordDef>.GetNamed("CountOfFappin");
		public static readonly RecordDef CountOfSex = DefDatabase<RecordDef>.GetNamed("CountOfSex");
		public static readonly RecordDef CountOfSexWithHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfSexWithHumanlikes");
		public static readonly RecordDef CountOfSexWithAnimals = DefDatabase<RecordDef>.GetNamed("CountOfSexWithAnimals");
		public static readonly RecordDef CountOfSexWithInsects = DefDatabase<RecordDef>.GetNamed("CountOfSexWithInsects");
		public static readonly RecordDef CountOfSexWithOthers = DefDatabase<RecordDef>.GetNamed("CountOfSexWithOthers");
		public static readonly RecordDef CountOfSexWithCorpse = DefDatabase<RecordDef>.GetNamed("CountOfSexWithCorpse");
		public static readonly RecordDef CountOfRapedHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfRapedHumanlikes");
		public static readonly RecordDef CountOfBeenRapedByHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByHumanlikes");
		public static readonly RecordDef CountOfRapedAnimals = DefDatabase<RecordDef>.GetNamed("CountOfRapedAnimals");
		public static readonly RecordDef CountOfBeenRapedByAnimals = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByAnimals");
		public static readonly RecordDef CountOfRapedInsects = DefDatabase<RecordDef>.GetNamed("CountOfRapedInsects");
		public static readonly RecordDef CountOfBeenRapedByInsects = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByInsects");
		public static readonly RecordDef CountOfRapedOthers = DefDatabase<RecordDef>.GetNamed("CountOfRapedOthers");
		public static readonly RecordDef CountOfBeenRapedByOthers = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByOthers");
		public static readonly RecordDef CountOfBirthHuman = DefDatabase<RecordDef>.GetNamed("CountOfBirthHuman");
		public static readonly RecordDef CountOfBirthAnimal = DefDatabase<RecordDef>.GetNamed("CountOfBirthAnimal");
		public static readonly RecordDef CountOfBirthEgg = DefDatabase<RecordDef>.GetNamed("CountOfBirthEgg");

		public static readonly MeditationFocusDef SexMeditationFocus = DefDatabase<MeditationFocusDef>.GetNamed("Sex");

		public enum rjwSextype { None, Vaginal, Anal, Oral, Masturbation, DoublePenetration, Boobjob, Handjob, Footjob, Fingering, Scissoring, MutualMasturbation, Fisting, MechImplant, Rimming, Fellatio, Cunnilingus, Sixtynine }
		public static readonly ReadOnlyCollection <rjwSextype> rjwSextypeCollection = Array.AsReadOnly((rjwSextype[])Enum.GetValues(typeof(rjwSextype)));

		public static void bootstrap(Map m)
		{
			if (m.GetComponent<MapCom_Injector>() == null)
				m.components.Add(new MapCom_Injector(m));
		}

		//<Summary>Simple method that quickly checks for match from a list.</Summary>
		public static bool ContainsAny(this string haystack, params string[] needles) { return needles.Any(haystack.Contains); }

		public static bool has_traits(Pawn pawn)
		{
			return pawn?.story?.traits != null;
		}

		public static bool has_quirk(Pawn pawn, string quirk)
		{
			return pawn != null && is_human(pawn) && CompRJW.Comp(pawn).quirks.ToString().Contains(quirk);
		}

		[SyncMethod]
		public static string random_pick_a_trait(this Pawn pawn)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return has_traits(pawn) ? pawn.story.traits.allTraits.RandomElement().def.defName : null;
		}

		public static bool is_psychopath(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Psychopath);
		}

		public static bool is_ascetic(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Ascetic);
		}

		public static bool is_bloodlust(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Bloodlust);
		}

		public static bool is_brawler(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Brawler);
		}

		public static bool is_kind(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Kind);
		}

		public static bool is_rapist(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(rapist);
		}

		public static bool is_necrophiliac(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(necrophiliac);
		}

		public static bool is_zoophile(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(zoophile);
		}

		public static bool is_masochist(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDef.Named("Masochist"));
		}

		/// <summary>
		/// Returns true if the given pawn has the nymphomaniac trait.
		/// This may or may not be a nymph pawnKind.
		/// </summary>
		public static bool is_nympho(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(nymphomaniac);
		}

		public static bool is_slime(Pawn pawn)
		{
			string racename = pawn.kindDef.race.defName.ToLower();
			//if (Prefs.DevMode) ModLog.Message(" is_slime " + xxx.get_pawnname(pawn) + " " + racename.Contains("slime"));

			return racename.Contains("slime");
		}

		public static bool is_demon(Pawn pawn)
		{
			string racename = pawn.kindDef.race.defName.ToLower();
			//if (Prefs.DevMode) ModLog.Message(" is_demon " + xxx.get_pawnname(pawn) + " " + racename.Contains("demon"));

			return racename.Contains("demon");
		}

		public static bool is_asexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Asexual;
		public static bool is_bisexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Bisexual;
		public static bool is_homosexual(Pawn pawn) => (CompRJW.Comp(pawn).orientation == Orientation.Homosexual || CompRJW.Comp(pawn).orientation == Orientation.MostlyHomosexual);
		public static bool is_heterosexual(Pawn pawn) => (CompRJW.Comp(pawn).orientation == Orientation.Heterosexual || CompRJW.Comp(pawn).orientation == Orientation.MostlyHeterosexual);
		public static bool is_pansexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Pansexual;

		public static bool is_slave(Pawn pawn)
		{
			if (is_vanillaslave(pawn) || is_modslave(pawn))
				return true;
			else
				return false;
		}
		public static bool is_vanillaslave(Pawn pawn)
		{
			if (pawn.IsSlave)//1.3
				return true;
			else
				return false;
		}
		public static bool is_modslave(Pawn pawn)
		{
			if (SimpleSlaveryIsActive)
				return pawn?.health.hediffSet.HasHediff(xxx.Enslaved) ?? false;
			else
				return false;
		}

		public static bool is_nympho_or_rapist_or_zoophile(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return (is_rapist(pawn) || is_nympho(pawn) || is_zoophile(pawn));
		}

		//Humanoid Alien Framework traits
		public static bool is_xenophile(Pawn pawn)
		{
			if (!has_traits(pawn) || !AlienFrameworkIsActive) { return false; }
			return pawn.story.traits.DegreeOfTrait(xenophobia) == -1;
		}

		public static bool is_xenophobe(Pawn pawn)
		{
			if (!has_traits(pawn) || !AlienFrameworkIsActive) { return false; }
			return pawn.story.traits.DegreeOfTrait(xenophobia) == 1;
		}

		public static bool is_whore(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return pawn != null && pawn.IsDesignatedService() && !pawn.story.traits.HasTrait(TraitDefOf.Asexual);
			//return (pawn != null && pawn.ownership != null && pawn.ownership.OwnedBed is Building_WhoreBed && (!xxx.RomanceDiversifiedIsActive || !pawn.story.traits.HasTrait(xxx.asexual)));
		}

		public static bool is_lecher(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(philanderer) || PsychologyIsActive && pawn.story.traits.HasTrait(lecher);
		}

		public static bool is_prude(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			if (is_nympho(pawn)) { return false; }
			if (pawn.IsIdeologicallyPrudish()) { return true; }
			return RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(faithful) || PsychologyIsActive && pawn.story.traits.HasTrait(prude);
		}

		public static bool is_animal(Pawn pawn)
		{
			return pawn?.RaceProps?.Animal ?? false;
		}

		public static bool is_insect(Pawn pawn)
		{
			if (pawn == null) return false;
			bool isit = pawn.RaceProps.FleshType == FleshTypeDefOf.Insectoid
						|| pawn.RaceProps.FleshType.corpseCategory.ToString().Contains("CorpsesInsect")
						//genetic rim
						|| pawn.RaceProps.FleshType.defName.Contains("GR_Insectoid");
			//Log.Message("is_insect " + get_pawnname(pawn) + " - " + isit);
			return isit;
		}

		public static bool is_mechanoid(Pawn pawn)
		{
			if (pawn == null) return false;
			if (AndroidsCompatibility.IsAndroid(pawn)) return false;

			bool isit = pawn.RaceProps.IsMechanoid
						|| pawn.RaceProps.FleshType == FleshTypeDefOf.Mechanoid
						|| pawn.RaceProps.FleshType.corpseCategory.ToString().Contains("CorpsesMechanoid")
						//genetic rim
						|| pawn.RaceProps.FleshType.defName.Contains("GR_Mechanoid")
						//android tiers
						|| pawn.RaceProps.FleshType.defName.Contains("MechanisedInfantry")
						|| pawn.RaceProps.FleshType.defName.Contains("Android")
						;
			//Log.Message("is_mechanoid " + get_pawnname(pawn) + " - " + isit);
			return isit;
		}

		public static bool is_tooluser(Pawn pawn)
		{
			return pawn.RaceProps.ToolUser;
		}

		public static bool is_human(Pawn pawn)
		{
			if (pawn == null) return false;
			return pawn.RaceProps.Humanlike;
		}

		public static bool is_female(Pawn pawn)
		{
			return pawn.gender == Gender.Female;
		}
		public static bool is_male(Pawn pawn)
		{
			return pawn.gender == Gender.Male;
		}

		public static bool is_healthy(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.capacities.CanBeAwake &&
				pawn.health.hediffSet.BleedRateTotal <= 0.0f &&
				pawn.health.hediffSet.PainTotal < config.significant_pain_threshold;
		}

		/// <summary>
		/// not going to die soon
		/// </summary>
		/// <param name="pawn"></param>
		/// <returns></returns>
		public static bool is_healthy_enough(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.capacities.CanBeAwake &&
				pawn.health.hediffSet.BleedRateTotal <= 0.1f;
		}

		/// <summary>
		/// pawn can initiate action or respond - whoring, etc
		/// </summary>
		public static bool IsTargetPawnOkay(Pawn pawn)
		{
			return xxx.is_healthy(pawn) && !pawn.Downed && !pawn.Suspended;
		}

		public static bool is_not_dying(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.hediffSet.BleedRateTotal <= 0.3f;
		}

		public static bool is_starved(Pawn pawn)
		{
			return pawn?.needs?.food != null &&
				pawn.needs.food.Starving;
		}
		public static float bleedingRate(Pawn pawn)
		{
			return pawn?.health?.hediffSet?.BleedRateTotal ?? 0f;
		}

		public static string get_pawnname(Pawn who)
		{
			//ModLog.Message("xxx::get_pawnname is "+ who.KindLabelDefinite());
			//ModLog.Message("xxx::get_pawnname is "+ who.KindLabelIndefinite());
			if (who == null) return "null";

			string name = who.Label;
			if (name != null)
			{
				if (who.Name?.ToStringShort != null)
					name = who.Name.ToStringShort;
			}
			else
				name = "noname";

			return name;
		}

		public static bool is_gettin_rapedNow(Pawn pawn)
		{
			if (pawn?.jobs?.curDriver != null)
			{
				return pawn.jobs.curDriver.GetType() == typeof(JobDriver_SexBaseRecieverRaped);
			}
			return false;
		}

		//cells checks are cheap, pathing is expensive. Do pathing check last.)

		public static float need_some_sex(Pawn pawn)
		{
			// 3=> always horny for non humanlikes
			float horniness_degree = 3f;
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex == null) return horniness_degree;
			if (need_sex.CurLevel < need_sex.thresh_frustrated()) horniness_degree = 3f;
			else if (need_sex.CurLevel < need_sex.thresh_horny()) horniness_degree = 2f;
			else if (need_sex.CurLevel < need_sex.thresh_satisfied()) horniness_degree = 1f;
			else horniness_degree = 0f;
			return horniness_degree;
		}
		public enum SexNeed
		{
			Frustrated,
			Horny,
			Neutral,
			Satisfied
		};

		public static SexNeed need_sex(Pawn pawn)
		{
			// 3=> always horny for non humanlikes, since they dont have need
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex == null) return SexNeed.Frustrated;

			if (RJWSettings.sexneed_fix)
				return need_sex_fixed(pawn);
			else
				return need_sex_broken(pawn);
		}

		/// <summary>
		/// Original rjw threshholds for sex
		/// </summary>
		/// <param name="pawn"></param>
		/// <returns></returns>
		private static SexNeed need_sex_broken(Pawn pawn)
		{
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex.CurLevel >= need_sex.thresh_satisfied())
				return SexNeed.Satisfied;
			else if (need_sex.CurLevel >= need_sex.thresh_neutral())
				return SexNeed.Neutral;
			else if (need_sex.CurLevel >= need_sex.thresh_horny())
				return SexNeed.Horny;
			else
				return SexNeed.Frustrated;
		}

		private static SexNeed need_sex_fixed(Pawn pawn)
		{
			// 3=> always horny for non humanlikes
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex.CurLevel <= need_sex.thresh_frustrated())
				return SexNeed.Frustrated;
			else if (need_sex.CurLevel <= need_sex.thresh_horny())
				return SexNeed.Horny;
			else if (need_sex.CurLevel <= need_sex.thresh_neutral())
				return SexNeed.Neutral;
			else
				return SexNeed.Satisfied;
		}

		public static bool is_frustrated(Pawn pawn)
		{
			return need_sex(pawn) == SexNeed.Frustrated;
		}

		public static bool is_horny(Pawn pawn)
		{
			return need_sex(pawn) == SexNeed.Horny;
		}

		public static bool is_hornyorfrustrated(Pawn pawn)
		{
			return (need_sex(pawn) == SexNeed.Horny || need_sex(pawn) == SexNeed.Frustrated);
		}

		public static bool is_neutral(Pawn pawn)
		{
			return need_sex(pawn) == SexNeed.Neutral;
		}
		public static bool is_satisfied(Pawn pawn)
		{
			return need_sex(pawn) == SexNeed.Satisfied;
		}

		/// <summary> Checks to see if the pawn has any partners who don't have a Polyamorous/Polygamous trait or ideo; aka someone who'd get mad about sleeping around. </summary>
		/// <returns> True if the pawn has at least one romantic partner who does not have a poly trait or ideo. False if no partners or all partners are poly. </returns>
		public static bool HasNonPolyPartner(Pawn pawn, bool onCurrentMap = false)
		{
			if (pawn.relations == null) // probably droids or who knows what modded abomination
				return false;

			var partners = LovePartnerRelationUtility.ExistingLovePartners(pawn, allowDead: false).Select(rel => rel.otherPawn);

			// If they don't have a partner at all we can bail right away.
			if (!partners.Any())
				return false;

			foreach (var partner in partners)
			{
				// Stasised partners will never find out!
				if (partner.Suspended)
					continue;

				// Neither does anyone on another map because cheating away from home is obviously never ever discovered
				if (onCurrentMap) // check only on Current Map
					if (pawn.Map == null || partner.Map == null || partner.Map != pawn.Map)
						continue;

				if (partner.IsPoly())
				{
					// We have a partner who has a poly trait or ideo!  But they could have multiple partners so keep checking.
					continue;
				}

				// We found a partner who isn't poly.
				return true;
			}

			// If we got here then we checked every partner and all of them had a poly trait, so they don't have a non-poly partner.
			return false;
		}

		public static Gender opposite_gender(Gender g)
		{
			switch (g)
			{
				case Gender.Male:
					return Gender.Female;
				case Gender.Female:
					return Gender.Male;
				default:
					return Gender.None;
			}
		}

		public static float get_sex_satisfaction(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(xxx.sex_satisfaction, false);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			//this and below should probably be rewritten to do calculations here
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static float get_vulnerability(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(vulnerability_stat, false);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static float get_sex_drive(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(sex_drive_stat, false) * (pawn.GetRJWPawnData().raceSexDrive);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static bool IsSingleOrPartnersNotHere(Pawn pawn)
		{
			var relations = LovePartnerRelationUtility.ExistingLovePartners(pawn, allowDead: false);

			if (!relations.Any())
			{
				return true;
			}
			return relations.All(rel => rel.otherPawn.Map != pawn.Map);
		}

		//base check
		public static bool can_do_loving(Pawn pawn)
		{
			if (is_mechanoid(pawn))
				return false;

			if (is_human(pawn))
			{
				int age = pawn.ageTracker.AgeBiologicalYears;
				var t = 13;
				if (pawn.GetRJWPawnData().RaceSupportDef?.teenAge != null && pawn.GetRJWPawnData().RaceSupportDef?.teenAge != 0)
				{
					t = pawn.GetRJWPawnData().RaceSupportDef.teenAge;
				}
				else if (!pawn.ageTracker.CurLifeStage.reproductive)
					if (pawn.ageTracker.Growth < 1)
						return false;

				if (age < t)
					return false;

				if (!pawn.apparel.WornApparel.NullOrEmpty())
					if (pawn.apparel.WornApparel.Where(x => x.def.defName.ToLower().Contains("warcasket")).Any())
						return false;

				return true;
			}
			if (is_animal(pawn))
			{
				if (pawn.GetRJWPawnData().RaceSupportDef?.teenAge != null && pawn.GetRJWPawnData().RaceSupportDef?.teenAge != 0)
				{
					int age = pawn.ageTracker.AgeBiologicalYears;
					int t = pawn.GetRJWPawnData().RaceSupportDef.teenAge;
					if (age < t)
						return false;
				}
				//CurLifeStageIndex/Growth for insects since they are not reproductive
				else if (!pawn.ageTracker.CurLifeStage.reproductive)
					if (pawn.ageTracker.Growth < 1)
						return false;

				return true;
			}
			return false;
		}

		public static bool can_do_animalsex(Pawn pawn1, Pawn pawn2)
		{
			bool v = false;
			if (xxx.is_animal(pawn1) && xxx.is_animal(pawn2))
				if (RJWSettings.animal_on_animal_enabled)
					v = true;
			else if (RJWSettings.bestiality_enabled)
					v = true;

			return v;
		}

		public static bool can_masturbate(Pawn pawn)
		{
			if (!RJWPreferenceSettings.FapInArmbinders)
				if (Genital_Helper.hands_blocked(pawn))
					return false;
			if (!RJWPreferenceSettings.FapInBelts)
				if (Genital_Helper.genitals_blocked(pawn))
					return false;

			if (!xxx.can_be_fucked(pawn) && !xxx.can_fuck(pawn)) //TODO: should improve this someday
				return false;

			return true;
		}

		// Penetrative organ check.
		public static bool can_fuck(Pawn pawn)
		{
			//this may cause problems with human mechanoids, like misc. bots or other custom race mechanoids
			if (is_mechanoid(pawn))
				return true;

			if (!can_do_loving(pawn))
				return false;

			var parts = pawn.GetGenitalsList();

			if (Genital_Helper.penis_blocked(pawn) || (!Genital_Helper.has_penis_fertile(pawn, parts) && !Genital_Helper.has_penis_infertile(pawn, parts) && !Genital_Helper.has_ovipositorF(pawn, parts))) return false;

			return true;
		}

		// Orifice check.
		public static bool can_be_fucked(Pawn pawn)
		{
			if (is_mechanoid(pawn))
				return false;

			if (!can_do_loving(pawn))
				return false;

			if (Genital_Helper.has_anus(pawn) && !Genital_Helper.anus_blocked(pawn))
				return true;
			if (Genital_Helper.has_vagina(pawn) && !Genital_Helper.vagina_blocked(pawn))
				return true;
			if (Genital_Helper.has_mouth(pawn) && !Genital_Helper.oral_blocked(pawn))
				return true;
			//not sure about below, when female rape male, need to check all code so meh
			//if ((Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || Genital_Helper.has_ovipositorF(pawn)) && !Genital_Helper.penis_blocked(pawn))
			//	return true;
			//if (Genital_Helper.has_breasts(pawn) && !Genital_Helper.breasts_blocked(pawn))
			//	return true;
			//if (pawn.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand)) && !Genital_Helper.hands_blocked(pawn))
			//	return true;

			return false;
		}

		public static bool can_rape(Pawn pawn, bool forced = false)
		{
			if (!RJWSettings.rape_enabled)
				return false;

			if (is_mechanoid(pawn))
				return true;

			if (!(can_fuck(pawn) ||
				(!is_male(pawn) && get_vulnerability(pawn) < RJWSettings.nonFutaWomenRaping_MaxVulnerability && can_be_fucked(pawn))))
				return false;

			if (is_human(pawn))
			{
				if (pawn.ageTracker.Growth < 1 && !pawn.ageTracker.CurLifeStage.reproductive)
					return false;

				if (RJWSettings.WildMode || forced)
					return true;

				return need_some_sex(pawn) > 0;
			}

			return true;
		}

		public static bool can_get_raped(Pawn pawn)
		{
			if (!RJWSettings.rape_enabled)
				return false;

			if (!can_be_fucked(pawn))
				return false;

			if (is_human(pawn))
			{
				if (pawn.ageTracker.Growth < 1 && !pawn.ageTracker.CurLifeStage.reproductive)
					return false;

				if (RJWSettings.WildMode)
					return true;

				if (!(RJWSettings.rapee_MinVulnerability_human >= 0 && get_vulnerability(pawn) > RJWSettings.rapee_MinVulnerability_human))
					return false;
			}

			return true;
		}

		public static bool is_Virgin(Pawn pawn)
		{
			//if (RJWSettings.DevMode) ModLog.Message("xxx::is_Virgin check:" +get_pawnname(pawn));
			if (pawn.relations != null)
				if (pawn.relations.ChildrenCount > 0)
				{
					//if (RJWSettings.DevMode) ModLog.Message("xxx::is_Virgin " + " ChildrenCount " + pawn.relations.ChildrenCount);
					return false;
				}

			if (!(
				pawn.records.GetValue(GetRapedAsComfortPawn) == 0 &&
				pawn.records.GetValue(CountOfSex) == 0 &&
				pawn.records.GetValue(CountOfSexWithHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfSexWithAnimals) == 0 &&
				pawn.records.GetValue(CountOfSexWithInsects) == 0 &&
				pawn.records.GetValue(CountOfSexWithOthers) == 0 &&
				pawn.records.GetValue(CountOfSexWithCorpse) == 0 &&
				//pawn.records.GetValue(CountOfWhore) == 0 &&
				pawn.records.GetValue(CountOfRapedHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfRapedAnimals) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByAnimals) == 0 &&
				pawn.records.GetValue(CountOfRapedInsects) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByInsects) == 0 &&
				pawn.records.GetValue(CountOfRapedOthers) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByOthers) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthHuman) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthAnimal) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthEgg) == 0
				))
			{
				//if (RJWSettings.DevMode) ModLog.Message("xxx::is_Virgin " + "records check fail");
				return false;
			}
			return true;
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	public static class AddComp
	{
		static AddComp()
		{
			AddRJWComp();
		}

		/// <summary>
		/// This automatically adds the comp to all races on startup.
		/// </summary>
		public static void AddRJWComp()
		{
			foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs.Where(thingDef =>
					thingDef.race != null))
			{
				thingDef.comps.Add(new CompProperties_RJW());
				//Log.Message("AddRJWComp to race " + thingDef.label);
			}

			foreach (PawnKindDef pawnKindDef in DefDatabase<PawnKindDef>.AllDefs.Where(pawnKindDef => pawnKindDef.race.race != null))
			{
				RaceGroupDef raceGroupDef = null;
				if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawnKindDef, out raceGroupDef))
				{
					//Log.Message("RaceGroupDef_Helper " + raceGroupDef.defName + " for " + pawnKindDef.race.defName);
					if (raceGroupDef.oviPregnancy)
					{
						if (pawnKindDef.race.comps.Any(x => x is CompProperties_EggLayer))
						{
							//Log.Message(pawnKindDef.race.defName + " was already egglayer");
						}
						else
						{
							CompProperties_EggLayer eggProps = OviHelper.GenerateEggLayerProperties(pawnKindDef, raceGroupDef);
							pawnKindDef.race.comps.Add(eggProps);
							//Log.Message(pawnKindDef.race.defName + " is now egglayer and lays " + eggProps.eggFertilizedDef.defName + " eggs");
						}
					}
				}
			}

			// For some reason eggs only grow if a pawn has a lifestage that is "milkable"
			// This might not be ideal...
			foreach (LifeStageDef lifeStage in DefDatabase<LifeStageDef>.AllDefs)
			{
				if (lifeStage.reproductive)
				{
					lifeStage.milkable = true;
				}
			}
		}
	}
}

repo_name: aelina/rjw

license: mit


code:
using Verse;

namespace rjw
{
	public class CompProperties_RJW : CompProperties
	{
		public CompProperties_RJW()
		{
			compClass = typeof(CompRJW);
		}
	}

	public class CompProperties_HediffBodyPart : HediffCompProperties
	{
		public CompProperties_HediffBodyPart()
		{
			compClass = typeof(CompHediffBodyPart);
		}
	}

	public class CompProperties_ThingBodyPart : CompProperties
	{
		public CompProperties_ThingBodyPart()
		{
			compClass = typeof(CompThingBodyPart);
		}
	}
}


code:
#nullable enable

using Psychology;
using SyrTraits;
using System.Text;
using Verse;
using RimWorld;
using Multiplayer.API;

namespace rjw
{
	public class CompRJW : ThingComp
	{
		/// <summary>
		/// Core comp for genitalia and sexuality tracking.
		/// </summary>
		public CompRJW() { }

		public CompProperties_RJW Props => (CompProperties_RJW)props;
		public Orientation orientation = Orientation.None;
		public StringBuilder quirks = new();
		public string quirksave = ""; // Not the most elegant way to do this, but it minimizes the save bloat.
		public int NextHookupTick;
		private bool BootStrapTriggered = false;
		public Need_Sex? sexNeed;

		/// <summary>
		/// <para>Gets the pawn for this comp.</para>
		/// <para>May return null if the comp's parent is somehow not a pawn.</para>
		/// </summary>
		private Pawn? pawn;
		private Pawn? Pawn => parent is Pawn pawn ? pawn : null;

		// This automatically checks that genitalia has been added to all freshly spawned pawns.
		public override void PostSpawnSetup(bool respawningAfterLoad)
		{
			base.PostSpawnSetup(respawningAfterLoad);

			if (pawn == null)
			{
				pawn = Pawn;
				if (pawn == null) return;
			}

			if (pawn.kindDef.race.defName.Contains("AIRobot") // No genitalia/sexuality for roombas.
				|| pawn.kindDef.race.defName.Contains("AIPawn") // ...nor MAI.
				|| pawn.kindDef.race.defName.Contains("RPP_Bot")
				|| pawn.kindDef.race.defName.Contains("PRFDrone") // Project RimFactory Revived drones
				) return;

			// No genitalia
			//if (!pawn.RaceProps.body.AllParts.Exists(x => x.def == DefDatabase<BodyPartDef>.GetNamed("Genitals")))
			//	return;

			if (Comp(pawn).orientation == Orientation.None)
			{
				Sexualize(pawn);
				Sexualize(pawn, true);
			}

			//Log.Message("PostSpawnSetup for " + pawn?.Name);
		}

		public override void CompTick()
		{
			base.CompTick();

			if (pawn == null)
			{
				pawn = Pawn;
				if (pawn == null) return;
			}

			if (pawn.IsHashIntervalTick(1500) && !pawn.health.Dead)
			// The `NeedInterval` function is called every 150 ticks but was formerly
			// rate limited by a factor of 10.  This will make the upkeep execute at
			// the same interval.
			{
				if (sexNeed == null)
				{
					sexNeed = pawn.needs?.TryGetNeed<Need_Sex>();
				}
				if (sexNeed != null)
				{
					DoUpkeep(pawn, sexNeed);
				}
			}
		}

		/// <summary>
		/// Performs some upkeep tasks originally handled in `Need_Sex.NeedInterval`.
		/// </summary>
		private void DoUpkeep(Pawn pawn, Need_Sex sexNeed)
		{
			if (pawn.Map == null) return;
			if (xxx.is_asexual(pawn)) return;

			var curLevel = sexNeed.CurLevel;

			// Update psyfocus if the pawn is awake.
			if (!RJWSettings.Disable_MeditationFocusDrain && pawn.Awake() && curLevel < sexNeed.thresh_frustrated())
				SexUtility.OffsetPsyfocus(pawn, -0.01f);
			//if (curLevel < sexNeed.thresh_horny())
			//	SexUtility.OffsetPsyfocus(pawn, -0.01f);
			//if (curLevel < sexNeed.thresh_frustrated() || curLevel > sexNeed.thresh_ahegao())
			//	SexUtility.OffsetPsyfocus(pawn, -0.05f);

			if (curLevel < sexNeed.thresh_horny() && (pawn.mindState.canLovinTick - Find.TickManager.TicksGame > 300))
				pawn.mindState.canLovinTick = Find.TickManager.TicksGame + 300;

			// This can probably all just go, since it's now a noop.
			// But I don't know if some mod is patching `xxx.bootstrap` or something.
			if (!BootStrapTriggered)
			{
				//--ModLog.Message("CompRJW::DoUpkeep::calling boostrap - pawn is " + xxx.get_pawnname(pawn));
				xxx.bootstrap(pawn.Map);
				BootStrapTriggered = true;
			}
		}

		public override void PostExposeData()
		{
			base.PostExposeData();
			if (Pawn == null) return;

			// Saves the data.
			Scribe_Values.Look(ref orientation, "RJW_Orientation");
			Scribe_Values.Look(ref quirksave, "RJW_Quirks", "");
			Scribe_Values.Look(ref NextHookupTick, "RJW_NextHookupTick");

			//Log.Message("PostExposeData for " + pawn?.Name);

			// Restore quirk data from the truncated save version.
			quirks = new StringBuilder(quirksave);
		}

		public static CompRJW Comp(Pawn pawn)
		{
			// Call CompRJW.Comp(pawn).<method> to check sexuality, etc.
			return pawn.GetComp<CompRJW>();
		}

		public static void CopyPsychologySexuality(Pawn pawn)
		{
			try
			{
				int kinsey = pawn.TryGetComp<CompPsychology>().Sexuality.kinseyRating;
				//Orientation originalOrientation = Comp(pawn).orientation;

				if (!Genital_Helper.has_genitals(pawn) && (pawn.kindDef.race.defName.ToLower().Contains("droid") || pawn.kindDef.race.defName.ToLower().Contains("drone")))
					Comp(pawn).orientation = Orientation.Asexual;
				else if (kinsey == 0)
					Comp(pawn).orientation = Orientation.Heterosexual;
				else if (kinsey == 1)
					Comp(pawn).orientation = Orientation.MostlyHeterosexual;
				else if (kinsey == 2)
					Comp(pawn).orientation = Orientation.LeaningHeterosexual;
				else if (kinsey == 3)
					Comp(pawn).orientation = Orientation.Bisexual;
				else if (kinsey == 4)
					Comp(pawn).orientation = Orientation.LeaningHomosexual;
				else if (kinsey == 5)
					Comp(pawn).orientation = Orientation.MostlyHomosexual;
				else if (kinsey == 6)
					Comp(pawn).orientation = Orientation.Homosexual;
				else
					Comp(pawn).orientation = Orientation.Asexual;
				/*else
					Log.Error("RJW::ERRROR - unknown kinsey scale value: " + kinsey);/*

				/*if (Comp(pawn).orientation != originalOrientation)
					Log.Message("RJW + Psychology: Inherited pawn " + xxx.get_pawnname(pawn) + " sexuality from Psychology - " + Comp(pawn).orientation);*/
			}
			catch
			{
				if (!pawn.IsAnimal())
					ModLog.Warning("CopyPsychologySexuality " + pawn?.Name + ", def: " + pawn?.def?.defName + ", kindDef: " + pawn?.kindDef?.race.defName);
			}
		}

		public static void CopyIndividualitySexuality(Pawn pawn)
		{
			try
			{
				CompIndividuality.Sexuality individualitySexuality = pawn.TryGetComp<CompIndividuality>().sexuality;
				//Orientation originalOrientation = Comp(pawn).orientation;

				if (individualitySexuality == CompIndividuality.Sexuality.Asexual)
					Comp(pawn).orientation = Orientation.Asexual;
				else if (!Genital_Helper.has_genitals(pawn) && (pawn.kindDef.race.defName.ToLower().Contains("droid") || pawn.kindDef.race.defName.ToLower().Contains("drone")))
					Comp(pawn).orientation = Orientation.Asexual;
				else if (individualitySexuality == CompIndividuality.Sexuality.Straight)
					Comp(pawn).orientation = Orientation.Heterosexual;
				else if (individualitySexuality == CompIndividuality.Sexuality.Bisexual)
					Comp(pawn).orientation = Orientation.Bisexual;
				else if (individualitySexuality == CompIndividuality.Sexuality.Gay)
					Comp(pawn).orientation = Orientation.Homosexual;
				else
					Comp(pawn).orientation = Orientation.Asexual;

				/*if (Comp(pawn).orientation != originalOrientation)
					Log.Message("RJW + [SYR]Individuality: Inherited pawn " + xxx.get_pawnname(pawn) + " sexuality from Individuality - " + Comp(pawn).orientation);*/
			}
			catch
			{
				if (!pawn.IsAnimal())
					ModLog.Warning("CopyIndividualitySexuality " + pawn?.Name + ", def: " + pawn?.def?.defName + ", kindDef: " + pawn?.kindDef?.race.defName);
			}
		}

		public static void VanillaTraitCheck(Pawn pawn)
		{
			//Orientation originalOrientation = Comp(pawn).orientation;
			if (pawn.story.traits.HasTrait(TraitDefOf.Asexual))
				Comp(pawn).orientation = Orientation.Asexual;
			else if (!Genital_Helper.has_genitals(pawn) && (pawn.kindDef.race.defName.ToLower().Contains("droid") || pawn.kindDef.race.defName.ToLower().Contains("drone")))
				Comp(pawn).orientation = Orientation.Asexual;
			else if (pawn.story.traits.HasTrait(TraitDefOf.Gay))
				Comp(pawn).orientation = Orientation.Homosexual;
			else if (pawn.story.traits.HasTrait(TraitDefOf.Bisexual))
				Comp(pawn).orientation = Orientation.Bisexual;
			else
				Comp(pawn).orientation = Orientation.Heterosexual;
		}

		// The main method for adding genitalia and orientation.
		public void Sexualize(Pawn pawn, bool reroll = false)
		{
			if (reroll)
			{
				Comp(pawn).orientation = Orientation.None;

				if (xxx.has_quirk(pawn, "Fertile"))
				{
					Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("IncreasedFertility"));
					if (fertility != null)
						pawn.health.RemoveHediff(fertility);
				}
				if (xxx.has_quirk(pawn, "Infertile"))
				{
					Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("DecreasedFertility"));
					if (fertility != null)
						pawn.health.RemoveHediff(fertility);
				}
				quirks = new StringBuilder();
			}
			else if (Comp(pawn).orientation != Orientation.None)
				return;

			//roll random RJW orientation
			Comp(pawn).orientation = xxx.is_animal(pawn) ? RollAnimalOrientation(pawn) : RollOrientation(pawn);

			//Asexual nymp re-roll
			//if (xxx.is_nympho(pawn))
			//	while (Comp(pawn).orientation == Orientation.Asexual)
			//	{
			//		Comp(pawn).orientation = RollOrientation();
			//	}

			//Log.Message("Sexualizing pawn " + pawn?.Name + ", def: " + pawn?.def?.defName);

			if (!reroll)
				Sexualizer.sexualize_pawn(pawn);
			//Log.Message("Orientation for pawn " + pawn?.Name + " is " + orientation);

			if (xxx.has_traits(pawn) && Genital_Helper.has_genitals(pawn) && !(pawn.kindDef.race.defName.ToLower().Contains("droid") && !AndroidsCompatibility.IsAndroid(pawn)))
			{
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Vanilla)
					VanillaTraitCheck(pawn);
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
					CopyPsychologySexuality(pawn);
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.SYRIndividuality)
					CopyIndividualitySexuality(pawn);
			}
			else if (((pawn.kindDef.race.defName.ToLower().Contains("droid")) && !AndroidsCompatibility.IsAndroid(pawn)) || !Genital_Helper.has_genitals(pawn))
			{
				// Droids with no genitalia are set as asexual.
				// If player later adds genitalia to the droid, the droid 'sexuality' gets rerolled.
				Comp(pawn).orientation = Orientation.Asexual;
			}

			QuirkAdder.Generate(pawn);

			if (quirks.Length == 0)
			{
				quirks.Append("None");
				quirksave = quirks.ToString();
			}
		}

		/// <summary>
		/// check try vanilla traits/mods, check rjw genitals, futa check, some rng rolls
		/// </summary>
		/// <param name="pawn"></param>
		/// <param name="partner"></param>
		/// <returns></returns>
		public static bool CheckPreference(Pawn pawn, Pawn partner)
		{
			if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Vanilla)
			{
				if (xxx.has_traits(pawn))
					VanillaTraitCheck(pawn);
				if (xxx.has_traits(partner))
					VanillaTraitCheck(partner);
			}
			if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.SYRIndividuality)
			{
				CopyIndividualitySexuality(pawn);
				CopyIndividualitySexuality(partner);
			}
			if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
			{
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
					CopyPsychologySexuality(pawn);
				CopyPsychologySexuality(partner);
			}

			//if (xxx.is_mechanoid(pawn))
			//	return false;

			if (Comp(pawn) is CompRJW rjwComp)
			{
				var ori = rjwComp.orientation;

				if (ori == Orientation.Pansexual || ori == Orientation.Bisexual)
					return true;

				if (ori == Orientation.Asexual)
					return false;

				var pawnSex = GenderHelper.GetSex(pawn);
				var partnerSex = GenderHelper.GetSex(partner);

				var isHetero = GenderHelper.CanBeHetero(pawnSex, partnerSex);
				var isHomo = GenderHelper.CanBeHomo(pawnSex, partnerSex);

				// Oh you crazy futas. fuck all cuz horny!
				if (isHetero && isHomo)
					return true;

				//RMB ui lag fixes in mp intead of [SyncMethod]
				if (MP.IsInMultiplayer)
					return RollOriMP(ori, isHetero, isHomo);
				else
					return RollOriSP(ori, isHetero, isHomo);
			}
			else
			{
				//ModLog.Message("Error, pawn:" + pawn + " doesn't have orientation comp, modded race?");
				return false;
			}
		}

		//no rng no lag
		public static bool RollOriMP(Orientation ori, bool isHetero, bool isHomo)
		{
			switch (ori)
			{
				case Orientation.Heterosexual:
					return !isHomo;
				case Orientation.MostlyHeterosexual:
					return (!isHomo);
				case Orientation.LeaningHeterosexual:
					return (!isHomo);
				case Orientation.LeaningHomosexual:
					return (!isHetero);
				case Orientation.MostlyHomosexual:
					return (!isHetero);
				case Orientation.Homosexual:
					return !isHetero;
				default:
					ModLog.Error("ERROR - tried to check preference for undetermined sexuality.");
					return false;
			}
		}

		//slight rng, but heavy ddos lag in MP with [SyncMethod]
		public static bool RollOriSP(Orientation ori, bool isHetero, bool isHomo)
		{
			switch (ori)
			{
				case Orientation.Heterosexual:
					return !isHomo;
				case Orientation.MostlyHeterosexual:
					return (!isHomo || Rand.Chance(0.2f));
				case Orientation.LeaningHeterosexual:
					return (!isHomo || Rand.Chance(0.6f));
				case Orientation.LeaningHomosexual:
					return (!isHetero || Rand.Chance(0.6f));
				case Orientation.MostlyHomosexual:
					return (!isHetero || Rand.Chance(0.2f));
				case Orientation.Homosexual:
					return !isHetero;
				default:
					ModLog.Error("ERROR - tried to check preference for undetermined sexuality.");
					return false;
			}
		}

		[SyncMethod]
		public Orientation RollOrientation(Pawn pawn)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float random = Rand.Range(0f, 1f);
			float checkpoint = RJWPreferenceSettings.asexual_ratio / RJWPreferenceSettings.GetTotal();

			float checkpoint_pan = checkpoint + (RJWPreferenceSettings.pansexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_het = checkpoint_pan + (RJWPreferenceSettings.heterosexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_bi = checkpoint_het + (RJWPreferenceSettings.bisexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_gay = checkpoint_bi + (RJWPreferenceSettings.homosexual_ratio / RJWPreferenceSettings.GetTotal());

			if (random < checkpoint || !Genital_Helper.has_genitals(pawn))
				return Orientation.Asexual;
			else if (random < checkpoint_pan)
				return Orientation.Pansexual;
			else if (random < checkpoint_het)
				return Orientation.Heterosexual;
			else if (random < checkpoint_het + ((checkpoint_bi - checkpoint_het) * 0.33f))
				return Orientation.MostlyHeterosexual;
			else if (random < checkpoint_het + ((checkpoint_bi - checkpoint_het) * 0.66f))
				return Orientation.LeaningHeterosexual;
			else if (random < checkpoint_bi)
				return Orientation.Bisexual;
			else if (random < checkpoint_bi + ((checkpoint_gay - checkpoint_bi) * 0.33f))
				return Orientation.LeaningHomosexual;
			else if (random < checkpoint_bi + ((checkpoint_gay - checkpoint_bi) * 0.66f))
				return Orientation.MostlyHomosexual;
			else
				return Orientation.Homosexual;
		}

		// Simpler system for animals, with most of them being heterosexual.
		// Don't want to disturb player breeding projects by adding too many gay animals.
		[SyncMethod]
		public Orientation RollAnimalOrientation(Pawn pawn)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float random = Rand.Range(0f, 1f);

			if (random < 0.03f || !Genital_Helper.has_genitals(pawn))
				return Orientation.Asexual;
			else if (random < 0.85f)
				return Orientation.Heterosexual;
			else if (random < 0.96f)
				return Orientation.Bisexual;
			else
				return Orientation.Homosexual;
		}
	}
}


code:
using RimWorld;

namespace rjw
{
	public class CompRJWHatcher : CompHatcher
	{
	}
}


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

namespace rjw
{
	/// <summary>
	/// Comp for rjw hediff parts
	/// </summary>
	public class CompHediffBodyPart : HediffComp
	{
		public string Size => parent.CurStage?.label ?? "";
		public string RaceOwner = "Unknows species";		//base race when part created
		public string PreviousOwner = "Unknows";			//base race when part created
		public float SizeBase;								//base size/severity when part created, someday alter by operation
		public float SizeOwner = 0;							//modifier of 1st owner race body size
		public float EffSize;								//SizeBase x SizeOwner = current size | hediff.severity
		public string FluidType = "";						//cummies/milk - insectjelly/honey etc
		public float FluidAmmount;							//amount of Milk/Ejaculation/Wetness
		public float FluidModifier = 1f;					//
		public string Eggs;                                 //for ovi eggs, maybe
		public bool Transplanted = false;					//transplaned from someone else

		/// <summary>
		/// part size in labels
		/// </summary>

		//public override string CompLabelInBracketsExtra
		//{
		//	get
		//	{
		//		if (Size != "")
		//			return Size;

		//		return null;
		//	}
		//}

		/// <summary>
		/// save data
		/// </summary>
		public override void CompExposeData()
		{
			base.CompExposeData();
			Scribe_Values.Look(ref SizeBase, "SizeBase");
			Scribe_Values.Look(ref SizeOwner, "SizeOwner");
			Scribe_Values.Look(ref RaceOwner, "RaceOwner");
			Scribe_Values.Look(ref PreviousOwner, "PreviousOwner");
			Scribe_Values.Look(ref FluidType, "FluidType");
			Scribe_Values.Look(ref FluidAmmount, "FluidAmmount");
			Scribe_Values.Look(ref FluidModifier, "FluidModifier");
			Scribe_Values.Look(ref Eggs, "Eggs");
			Scribe_Values.Look(ref Transplanted, "Transplanted");
		}

		public override void CompPostPostAdd(DamageInfo? dinfo)
		{
			base.CompPostPostAdd(dinfo);
			try//error at world gen
			{
				Pawn.GetRJWPawnData().genitals = new List<Hediff>();
				Pawn.GetRJWPawnData().breasts = new List<Hediff>();
				Pawn.GetRJWPawnData().udders = new List<Hediff>();
				Pawn.GetRJWPawnData().anus = new List<Hediff>();
			}
			catch
			{
			}
		}
		public override void CompPostPostRemoved()
		{
			base.CompPostPostRemoved();
			try
			{
				Pawn.GetRJWPawnData().genitals = new List<Hediff>();
				Pawn.GetRJWPawnData().breasts = new List<Hediff>();
				Pawn.GetRJWPawnData().udders = new List<Hediff>();
				Pawn.GetRJWPawnData().anus = new List<Hediff>();
			}
			catch
			{
			}
		}

		/// <summary>
		/// show part info in healthab
		/// </summary>
		public override string CompTipStringExtra
		{
			get
			{
				if (SizeOwner == 0)
				{
					ModLog.Warning(" " + xxx.get_pawnname(Pawn) + " part " + parent.def.defName + " is broken, resetting");
					initComp();
					updatesize();
					updatepartposition();
				}
				// In theory the tip should be a condensed form and the longer form should be elsewhere, but where?
				return GetTipString(RJWSettings.ShowRjwParts == RJWSettings.ShowParts.Extended).Join("\n");
			}
		}

		IEnumerable<string> GetTipString(bool extended)
		{
			//ModLog.Message(" CompTipStringExtra " + xxx.get_pawnname(Pawn) + " " + parent.def.defName);
			// TODO: Part type should be a property in the extension.
			//StringBuilder defstringBuilder = new StringBuilder();
			if (parent.Part != null)
				if (parent.def.defName.ToLower().Contains("breasts"))
				{
					return GetBreastTip(extended);
				}
				else if (parent.def.defName.ToLower().Contains("penis"))
				{
					return GetPenisTip(extended);
				}
				else if (parent.def.defName.ToLower().Contains("anus") || parent.def.defName.ToLower().Contains("vagina"))
				{
					return GetOrificeTip(extended);
				}
				else if (parent.def.defName.ToLower().Contains("ovi"))
				{
					return GetEggTip(extended);
				}

			return Enumerable.Empty<string>();
		}

		IEnumerable<string> GetEggTip(bool extended)
		{
			if (Eggs != "")
			{
				yield return "RJW_hediff_eggs".Translate(Eggs);
			}
		}

		IEnumerable<string> GetPenisTip(bool extended)
		{
			if (PartSizeExtension.TryGetLength(parent, out float length))
			{
				yield return "RJW_hediff_length".Translate(length.ToString("F1"));
			}

			if (!extended)
			{
				yield break;
			}

			if (PartSizeExtension.TryGetGirth(parent, out float girth))
			{
				yield return "RJW_hediff_girth".Translate(girth.ToString("F1"));
			}

			if (PartSizeExtension.TryGetPenisWeight(parent, out float weight))
			{
				yield return "RJW_hediff_weight".Translate(weight.ToString("F1"));
			}

			foreach (var line in GetFluidTip())
			{
				yield return line;
			}

			if (PartProps.TryGetProps(parent, out List<string> propslist))
			{
				yield return "Properties: " + propslist.ToCommaList();
			}
		}

		IEnumerable<string> GetOrificeTip(bool extended)
		{
			if (!extended)
			{
				yield break;
			}
				//defstringBuilder.Append(this.Def.description);
				//defstringBuilder.AppendLine("Cum: " + FluidType);
				//defstringBuilder.AppendLine("Ejaculation: " + (FluidAmmount * FluidModifier).ToString("F2") + "ml");

			// TODO: Should be scaled bigger than penis size either in xml or here.
			if (PartSizeExtension.TryGetLength(parent, out float length))
			{
				yield return "RJW_hediff_maxLength".Translate(length.ToString("F1"));
			}

			if (PartSizeExtension.TryGetGirth(parent, out float girth))
			{
				yield return "RJW_hediff_maxGirth".Translate(girth.ToString("F1"));
			}

			foreach (var line in GetFluidTip())
			{
				yield return line;
			}

			if (PartProps.TryGetProps(parent, out List<string> propslist))
			{
				yield return "Properties: " + propslist.ToCommaList();
			}
		}

		IEnumerable<string> GetBreastTip(bool extended)
		{
			if (parent.Part.def == xxx.breastsDef)
			{
				foreach (var line in GetBraCupTip())
				{
					yield return line;
				}

				if (!extended)
				{
					yield break;
				}

				if (PartSizeExtension.TryGetCupSize(parent, out float size))
				{
					var cupSize = (int)size;

					if (cupSize > 0)
					{
						var bandSize = PartSizeExtension.GetBandSize(parent);
						yield return "RJW_hediff_braSize".Translate(
							bandSize,
							cupSize);

						if (PartSizeExtension.TryGetOverbustSize(parent, out float overbust))
						{
							var underbust = PartSizeExtension.GetUnderbustSize(parent);
							yield return "RJW_hediff_underbust".Translate(underbust.ToString("F1"));
							yield return "RJW_hediff_overbust".Translate(overbust.ToString("F1"));
						}

						if (PartSizeExtension.TryGetBreastWeight(parent, out float weight))
						{
							yield return "RJW_hediff_weight".Translate(weight.ToString("F1"));
						}
					}
					else
					{
						yield return "RJW_hediff_braSizeNone".Translate();
					}
				}
			}

			foreach (var line in GetFluidTip())
			{
				yield return line;
			}

			if (PartProps.TryGetProps(parent, out List<string> propslist))
			{
				yield return "Properties: " + propslist.ToCommaList();
			}
		}

		IEnumerable<string> GetBraCupTip()
		{
			if (PartSizeExtension.TryGetCupSize(parent, out float size))
			{
				var cupSize = (int)size;

				if (cupSize > 0)
				{
					yield return "RJW_hediff_braCup".Translate(
						PartStagesDef.GetCupSizeLabel(cupSize));
				}
			}
		}

		IEnumerable<string> GetFluidTip()
		{
			if (FluidAmmount != 0 && FluidType != "")
			{
				yield return "RJW_hediff_fluidTypeFluidAmount".Translate(
					FluidType,
					(FluidAmmount * FluidModifier).ToString("F1"));
			}
		}

		//TODO: someday part(SizeOwner) change operations
		public void updatesize(float value = 0)
		{
			if (value == 0) // refresh size
			{
				//Log.Message("CompHediffBodyPart::updatesize original owner part size ( " + SizeOwner + " )");
				//Log.Message("CompHediffBodyPart::updatesize current owner part size ( " + parent.pawn.BodySize + " )");
				value = (parent.pawn.RaceProps.baseBodySize - SizeOwner) / SizeOwner;
				value = SizeBase / (1 + value);

				if (!Transplanted) // update non transplanted part size (kids growth?)
					value /= parent.pawn.RaceProps.baseBodySize / parent.pawn.BodySize;
				//else
				//	value *= parent.pawn.RaceProps.baseBodySize / parent.pawn.BodySize;

				parent.Severity = value;
			}
			else // change size by %
			{
				if (value > 0)
					incsize(value);
				else
					decsize(value);
			}
		}

		//scale parts to bodysize
		public void incsize(float value = 0)
		{
			if (value < 0)
				return;
			else if(value == 0)
				return;
			else if(value != 0)
			{
				//Log.Message(xxx.get_pawnname(pawn) + " updatesize " + parent.def.defName);
				//Log.Message("CompHediffBodyPart::updatesize - increase");
				//Log.Message("CompHediffBodyPart::updatesize( " + SizeOwner + " )");
				//Log.Message("CompHediffBodyPart::updatesize( " + parent.pawn.BodySize + " )");
				//Log.Message("CompHediffBodyPart::updatesize - value");
				//Log.Message("CompHediffBodyPart::updatesize( " + value + " )");
				value = SizeBase * (1 + value);
			}
			parent.Severity = value;
		}

		public void decsize(float value = 0)
		{
			if (value > 0)
				return;
			else if(value == 0)
				return;
			else if (value != 0)
			{
				//Log.Message(xxx.get_pawnname(pawn) + " updatesize " + parent.def.defName);
				//Log.Message("CompHediffBodyPart::updatesize - decrease");
				//Log.Message("CompHediffBodyPart::updatesize( " + SizeOwner + " )");
				//Log.Message("CompHediffBodyPart::updatesize( " + parent.pawn.BodySize + " )");
				//Log.Message("CompHediffBodyPart::updatesize - value");
				//Log.Message("CompHediffBodyPart::updatesize( " + value + " )");
				value = SizeBase * (1 + value);
			}
			if (value <= 0) // aka dont remove 0 severity hediff
			{
				value = 0.01f;
			}
			parent.Severity = value;
		}

		[SyncMethod]
		public void updatepartposition()
		{
			var partBase = parent.def as HediffDef_PartBase;
			if (partBase != null)
			{
				//Log.Message("0 " + (parent.Part == null));
				//Log.Message("1 " +parent.Part);
				//Log.Message("2 " +parent.Part.def);
				//Log.Message("3 " +parent.Part.def.defName);
				//Log.Message("4 " +partBase);
				//Log.Message("5 " +partBase.DefaultBodyPart);
				//ModLog.Message("pawn " + xxx.get_pawnname(Pawn) + ", part: " + parent.def.defName);
				if (parent.Part == null)
				{
					var bp = parent.pawn.RaceProps.body.AllParts.Find(x => x.def.defName.Contains(partBase.DefaultBodyPart));
					if (bp != null)
					{
						ModLog.Warning(" " + xxx.get_pawnname(Pawn) + " part is null/wholebody? is in wrong BodyPart position, resetting to default: " + partBase.DefaultBodyPart);
						parent.Part = bp;
						Pawn.GetRJWPawnData().genitals = new List<Hediff>();
						Pawn.GetRJWPawnData().breasts = new List<Hediff>();
						Pawn.GetRJWPawnData().udders = new List<Hediff>();
						Pawn.GetRJWPawnData().anus = new List<Hediff>();
					}
					else
					{
						ModLog.Message(" " + xxx.get_pawnname(Pawn) + " default bodypart not found: " + partBase.DefaultBodyPart + " -skip.");
					}
				}
				else if (parent.Part != null && partBase.DefaultBodyPart != "" && parent.Part.def.defName != partBase.DefaultBodyPart)
				{
					//Log.Message("f");
					//Log.Message(partBase.DefaultBodyPartList.ToLineList());
					if (partBase.DefaultBodyPartList.Any() && partBase.DefaultBodyPartList.Contains(parent.Part.def.defName))
					{
						return;
					}
					var bp = parent.pawn.RaceProps.body.AllParts.Find(x => x.def.defName.Contains(partBase.DefaultBodyPart));
					if (bp != null)
					{
						ModLog.Warning(" " + xxx.get_pawnname(Pawn) + " part " + parent.def.defName + " is in wrong BodyPart position, resetting to default: " + partBase.DefaultBodyPart);
						parent.Part = bp;
						Pawn.GetRJWPawnData().genitals = new List<Hediff>();
						Pawn.GetRJWPawnData().breasts = new List<Hediff>();
						Pawn.GetRJWPawnData().udders = new List<Hediff>();
						Pawn.GetRJWPawnData().anus = new List<Hediff>();
					}
					else
					{
						ModLog.Message(" " + xxx.get_pawnname(Pawn) + " default bodypart not found: " + partBase.DefaultBodyPart + " -skip.");
					}
					//if (pawn.IsColonist)
					//{
					//	Log.Message(xxx.get_pawnname(pawn) + " has broken hediffs, removing " + this.ToString());
					//	Log.Message(Part.ToString());
					//	Log.Message(bp.def.defName);
					//	Log.Message(partBase.DefaultBodyPart.ToString());
					//}
				}
			}
		}

		/// <summary>
		/// fill comp data
		/// </summary>
		[SyncMethod]
		public void initComp(Pawn pawn = null, bool reroll = false)
		{
			if (pawn == null)
				pawn = parent.pawn;

			double u1 = 1.0 - Rand.Range(0.01f, 1); //uniform(0.01f, 1] random doubles
			double u2 = 1.0 - Rand.Range(0.01f, 1);
			double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
				Math.Sin(2.0 * Math.PI * u2);
			double value =
				(3 /*mostly prevents negatives*/ + .75 /*Normalizing factor*/ * randStdNormal) / 6; /*changes to percent */
			if (value <= 0)
			{
				value = 0.01f;
			}
			else if (value > 1)
			{
				value = 1;
			}
			bool trap = false;
			if (reroll == true)
				value = SizeBase;
			{
				if (parent.def.defName.ToLower().Contains("breast"))
				{
					//FluidType = "Milk";
					FluidAmmount = 0;

					if (pawn != null)
					{
						if (pawn.gender == Gender.Male && RJWSettings.MaleTrap)
							if (pawn.Faction != null && !xxx.is_animal(pawn)) //null faction throws error
							{
								//natives/spacer futa
								float chance = (int)pawn.Faction.def.techLevel < 5 ? RJWSettings.futa_natives_chance : RJWSettings.futa_spacers_chance;
								//nymph futa gender
								chance = xxx.is_nympho(pawn) ? RJWSettings.futa_nymph_chance : chance;

								if (Rand.Chance(chance))
								{
									//make trap
									trap = true;
								}
							}

						if (pawn.gender == Gender.Male && !trap && reroll == false)
							value = 0.01f;

					}
				}
				else if (parent.def.defName.ToLower().Contains("penis") || parent.def.defName.ToLower().Contains("Ovipositorf") || parent.def.defName.ToLower().Contains("Ovipositorm"))
				{
					FluidAmmount = (float)value * pawn.RaceProps.baseBodySize * pawn.RaceProps.baseBodySize * 10 * 2 * Rand.Range(0.75f, 1.25f);
				}
				else if (parent.def.defName.ToLower().Contains("vagina"))
				{
					FluidAmmount = (float)value * pawn.RaceProps.baseBodySize * pawn.RaceProps.baseBodySize * 10 * Rand.Range(0.75f, 1.25f);
				}
				else if (parent.def.defName.ToLower().Contains("anus"))
				{
					FluidAmmount = 0;
				}
				else if (parent.def.defName.ToLower().Contains("tentacle"))
				{
					value *= 2;
					FluidAmmount = (float)value * pawn.RaceProps.baseBodySize * pawn.RaceProps.baseBodySize * 10 * 2 * Rand.Range(0.75f, 1.25f);
				}
				if (parent.def.defName.ToLower().Contains("ovi"))
				{
					Eggs = pawn?.kindDef?.label ?? "";
				}

				FluidType = (parent.def as HediffDef_PartBase).FluidType;
				SizeBase = (float)value;
				SizeOwner = pawn?.RaceProps?.baseBodySize ?? 1.0f;
				RaceOwner = pawn?.kindDef?.race.LabelCap ?? "RJW_hediff_unknownSpecies".Translate();
				PreviousOwner = xxx.get_pawnname(pawn);
				EffSize = SizeOwner*SizeBase;
				FluidModifier = 1f;
			}
		}
	}
}


code:
using RimWorld;

namespace rjw
{
	public class CompRJWProperties_Hatcher : CompProperties_Hatcher
	{
		public CompRJWProperties_Hatcher()
		{
			this.compClass = typeof(CompRJWHatcher);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Comp for things
	/// </summary>
	public class CompThingBodyPart : ThingComp
	{
		/// <summary>
		/// Comp for rjw Thing parts.
		/// </summary>

		public string Size;										//eventually replace with below, maybe
		public float SizeBase;									//base size when part created, someday alter by operation
		public float SizeOwner = 0;								//modifier of 1st owner race body size
		public string RaceOwner = "Unknows species";			//race of 1st owner race
		public string PreviousOwner = "Unknown";				//erm
		public float EffSize;									//SizeBase x SizeOwner
		public string FluidType;								//cummies/milk - insectjelly/honey etc
		public float FluidAmmount;								//amount of Milk/Ejaculation/Wetness
		public float FluidModifier;								//
		public string Eggs;										//for ovi eggs, maybe

		/// <summary>
		/// Thing/part size in labels, init comp on select
		/// </summary>

		//public override string CompInspectStringExtra()
		//{
		//	if (Size != "")
		//		return Translator.Translate("Size") + ": " + Size;
		//	else
		//		return null;

		//	return base.CompInspectStringExtra();
		//}

		/// <summary>
		/// Thing/part size in label
		/// </summary>
		public override string TransformLabel(string label)
		{
			if (SizeOwner == 0)
				InitComp();

			if (Size != "")
				return label + " (" + Size + ")" + " (" + SizeOwner + ")";
			//this.parent.GetStatValue(StatDefOf.Nutrition);

			return label;
		}

		/// <summary>
		/// Thing/part description + comp description
		/// </summary>
		public override string GetDescriptionPart()
		{
			//ModLog.Message(" CompTipStringExtra " + xxx.get_pawnname(Pawn) + " " + parent.def.defName);
			StringBuilder stringBuilder = new StringBuilder();
			if (SizeOwner == 0)
				InitComp();
			stringBuilder.AppendLine("Previous owner: " + PreviousOwner);
			stringBuilder.AppendLine("Original owner race: " + RaceOwner);
			stringBuilder.AppendLine("Original owner race size: " + SizeOwner);
			if (parent.def.defName.ToLower().Contains("breasts"))
			{
				if (FluidType != "")
					stringBuilder.AppendLine("Producing: " + FluidType);
				if (FluidAmmount != 0)
				{
					stringBuilder.AppendLine("Amount: " + FluidAmmount.ToString("F2"));
				}
				if (Size != "")
					stringBuilder.AppendLine("Size: " + Size);
			}
			else if (parent.def.defName.ToLower().Contains("penis") || parent.def.defName.ToLower().Contains("vagina"))
			{
				if (FluidAmmount != 0)
				{
					if (FluidType != "")
						stringBuilder.AppendLine("Cum: " + FluidType);
					if (parent.def.defName.ToLower().Contains("penis"))
						stringBuilder.AppendLine("Ejaculation: " + FluidAmmount.ToString("F2") + "ml");
					if (parent.def.defName.ToLower().Contains("vagina"))
						stringBuilder.AppendLine("Wetness: " + FluidAmmount.ToString("F2"));
				}
				if (Size != "")
					stringBuilder.AppendLine("Size: " + Size);
			}
			else if (parent.def.defName.ToLower().Contains("anus"))
			{
				if (FluidType != "" && FluidAmmount != 0)
				{
					stringBuilder.AppendLine("Lube: " + FluidType);
					if (parent.def.defName.ToLower().Contains("anus"))
						stringBuilder.AppendLine("Wetness: " + FluidAmmount.ToString("F2"));
				}
				if (Size != "")
					stringBuilder.AppendLine("Size: " + Size);
			}
			else if (parent.def.defName.ToLower().Contains("ovi"))
			{
				if (Eggs != "")
					stringBuilder.AppendLine("Eggs: " + Eggs);
			}
			//TODO: someday add properties
			//if (PartProps.TryGetProps(parent, out List<string> propslist))
			//{
			//	yield return "Properties: " + propslist.ToCommaList();
			//	if (Eggs != "")
			//		stringBuilder.AppendLine("Properties: " + propslist.ToCommaList());
			//}

			return stringBuilder.ToString();
		}

		public override void PostExposeData()
		{
			base.PostExposeData();
			Scribe_Values.Look(ref Size, "Size");
			Scribe_Values.Look(ref SizeBase, "SizeBase");
			Scribe_Values.Look(ref SizeOwner, "SizeOwner");
			Scribe_Values.Look(ref RaceOwner, "RaceOwner");
			Scribe_Values.Look(ref PreviousOwner, "PreviousOwner");
			Scribe_Values.Look(ref FluidType, "FluidType");
			Scribe_Values.Look(ref FluidAmmount, "FluidAmmount");
			Scribe_Values.Look(ref FluidModifier, "FluidModifier");
			Scribe_Values.Look(ref Eggs, "Eggs");
		}

		/// <summary>
		/// fill comp data
		/// </summary>
		[SyncMethod]
		public void InitComp(Pawn pawn = null)
		{
			var def = DefDatabase<HediffDef>.GetNamed(parent.def.defName);
			//pick random pawn to create temp hediff
			pawn = PawnsFinder.AllMaps_FreeColonistsAndPrisonersSpawned?.RandomElement();
			if (pawn == null)// SOS2 or other start with no colonists
				pawn = PawnsFinder.All_AliveOrDead?.RandomElement();

			Hediff hd = HediffMaker.MakeHediff(def, pawn);

			CompHediffBodyPart CompHediff = hd.TryGetComp<CompHediffBodyPart>();
			if (CompHediff != null)
			{
				CompHediff.initComp(pawn);
				CompHediff.updatesize();

				FluidType = CompHediff.FluidType;
				FluidAmmount = CompHediff.FluidAmmount;
				FluidModifier = CompHediff.FluidModifier;
				Size = CompHediff.Size;
				SizeBase = CompHediff.SizeBase;
				SizeOwner = CompHediff.SizeOwner;
				RaceOwner = "Unknown";
				PreviousOwner = "Unknown";
				Eggs = CompHediff.Eggs;
			}
		}
	}
}


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

namespace rjw
{
	public enum Orientation
	{
		None,
		Asexual,
		Pansexual,
		Heterosexual,
		MostlyHeterosexual,
		LeaningHeterosexual,
		Bisexual,
		LeaningHomosexual,
		MostlyHomosexual,
		Homosexual
	}
}


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

namespace rjw
{
	public class Quirk
	{
		public static List<Quirk> All = new List<Quirk>();

		public static readonly Quirk Breeder = new Quirk(
			"Breeder",
			"BreederQuirk");
		public static readonly Quirk Endytophile = new Quirk(
			"Endytophile",
			"EndytophileQuirk",
			(pawn, partner) => !partner.apparel.PsychologicallyNude,
			sexProps => !sexProps.pawn.apparel.PsychologicallyNude
			);
		public static readonly Quirk Exhibitionist = new Quirk(
			"Exhibitionist",
			"ExhibitionistQuirk",
			null,
			SatisfiesExhibitionist
			);
		public static readonly Quirk Fertile = new Quirk(
			"Fertile",
			"FertileQuirk");
		public static readonly Quirk Gerontophile = new Quirk(
			"Gerontophile",
			"GerontophileQuirk",
			(pawn, partner) => SexUtility.ScaleToHumanAge(partner) > 55,
			sexProps => sexProps.hasPartner() && SexUtility.ScaleToHumanAge(sexProps.partner) > 55
			);
		public static readonly Quirk ImpregnationFetish = new Quirk(
			"Impregnation fetish",
			"ImpregnationFetishQuirk",
			(pawn, partner) =>
				PregnancyHelper.CanImpregnate(pawn, partner)
				|| PregnancyHelper.CanImpregnate(partner, pawn),
			sexProps =>
				sexProps.hasPartner()
				&& (PregnancyHelper.CanImpregnate(sexProps.pawn, sexProps.partner, sexProps.sexType)
					|| PregnancyHelper.CanImpregnate(sexProps.partner, sexProps.pawn, sexProps.sexType))
			);
		public static readonly Quirk Incubator = new Quirk(
			"Incubator",
			"IncubatorQuirk");
		public static readonly Quirk Infertile = new Quirk(
			"Infertile",
			"InfertileQuirk");
		public static readonly Quirk Messy = new Quirk(
			"Messy",
			"MessyQuirk");
		public static readonly Quirk Podophile = new Quirk(
			"Podophile",
			"PodophileQuirk",
			null,
			sexProps => sexProps.hasPartner() && sexProps.sexType == xxx.rjwSextype.Footjob && sexProps.IsSubmissive()
			);
		public static readonly Quirk Cumslut = new Quirk(
			"Cumslut",
			"CumslutQuirk",
			null,
			sexProps => sexProps.hasPartner() && sexProps.sexType == xxx.rjwSextype.Fellatio && sexProps.IsSubmissive()
			);
		public static readonly Quirk Buttslut = new Quirk(
			"Buttslut",
			"ButtslutQuirk",
			null,
			sexProps => sexProps.hasPartner() && sexProps.sexType == xxx.rjwSextype.Anal && sexProps.IsSubmissive()
			);
		public static readonly Quirk PregnancyFetish = new Quirk(
			"Pregnancy fetish",
			"PregnancyFetishQuirk",
			(pawn, partner) => partner.IsVisiblyPregnant(),
			sexProps => sexProps.hasPartner() && sexProps.partner.IsVisiblyPregnant()
			);
		public static readonly Quirk Sapiosexual = new Quirk(
			"Sapiosexual",
			"SapiosexualQuirk",
			(pawn, partner) => SatisfiesSapiosexual(partner),
			null
			);
		public static readonly Quirk Somnophile = new Quirk(
			"Somnophile",
			"SomnophileQuirk",
			(pawn, partner) => !partner.Awake(),
			sexProps => sexProps.hasPartner() && !sexProps.partner.Awake()
			);
		public static readonly Quirk Teratophile = new Quirk(
			"Teratophile",
			"TeratophileQuirk",
			(pawn, partner) => SatisfiesTeratophile(partner),
			null
			);
		public static readonly Quirk Vigorous = new Quirk(
			"Vigorous",
			"VigorousQuirk");

		// There might be too many of these I dunno.
		// People have expressed "special interest" in some of them so I thought
		// it would be cool to have them in the game but since people are weird you end up with a lot of fetishes.
		public static readonly Quirk ChitinLover = MakeTagBasedQuirk("Chitin lover", "ChitinLoverQuirk", RaceTag.Chitin);
		public static readonly Quirk DemonLover = MakeTagBasedQuirk("Demon lover", "DemonLoverQuirk", RaceTag.Demon);
		public static readonly Quirk FeatherLover = MakeTagBasedQuirk("Feather lover", "FeatherLoverQuirk", RaceTag.Feathers);
		public static readonly Quirk FurLover = MakeTagBasedQuirk("Fur lover", "FurLoverQuirk", RaceTag.Fur);
		public static readonly Quirk PlantLover = MakeTagBasedQuirk("Plant lover", "PlantLoverQuirk", RaceTag.Plant);
		public static readonly Quirk RobotLover = MakeTagBasedQuirk("Robot lover", "RobotLoverQuirk", RaceTag.Robot);
		public static readonly Quirk ScaleLover = MakeTagBasedQuirk("Scale lover", "ScaleLoverQuirk", RaceTag.Scales);
		public static readonly Quirk SkinLover = MakeTagBasedQuirk("Skin lover", "SkinLoverQuirk", RaceTag.Skin);
		public static readonly Quirk SlimeLover = MakeTagBasedQuirk("Slime lover", "SlimeLoverQuirk", RaceTag.Slime);

		public string Key { get; }
		public string LocaliztionKey { get; }
		// Hack, quirk generation logic should be in the quirk not based on flags on the quirk.
		public RaceTag RaceTag { get; }
		readonly Func<Pawn, Pawn, bool> PawnSatisfiesFunc;
		readonly Func<SexProps, bool> SexSatisfiesFunc;
		readonly Action<Pawn> AfterAddFunc;

		public static Quirk MakeTagBasedQuirk(string key, string localizationKey, RaceTag tag)
		{
			return new Quirk(
				key,
				localizationKey,
				(pawn, partner) => partner.Has(tag),
				sexProps => sexProps.hasPartner() && sexProps.partner.Has(tag),
				null,
				tag);
		}

		public Quirk(
			string key,
			string localizationKey,
			Func<Pawn, Pawn, bool> pawnSatisfies = null,
			Func<SexProps, bool> sexSatisfies = null,
			Action<Pawn> afterAdd = null,
			RaceTag raceTag = null)
		{
			Key = key;
			LocaliztionKey = localizationKey;
			PawnSatisfiesFunc = pawnSatisfies;
			SexSatisfiesFunc = sexSatisfies;
			AfterAddFunc = afterAdd;
			RaceTag = raceTag;
			All.Add(this);
		}

		public bool IsSatisfiedBy(Pawn pawn, Pawn partner)
		{
			return pawn != null
				&& partner != null
				&& pawn.Has(this)
				&& PawnSatisfiesFunc != null
				&& PawnSatisfiesFunc(pawn, partner);
		}

		public static int CountSatisfiedQuirks(SexProps props)
		{
			Pawn pawn = props.pawn;
			Pawn partner = props.partner;
			var satisfies = All.Where(quirk =>
				quirk.SexSatisfiesFunc != null
				&& pawn.Has(quirk)
				&& quirk.SexSatisfiesFunc(props));
			return satisfies.Count();
		}

		public void DoAfterAdd(Pawn pawn)
		{
			AfterAddFunc?.Invoke(pawn);
		}

		public static bool SatisfiesExhibitionist(SexProps sexProps)
		{
			var zoo = xxx.is_zoophile(sexProps.pawn);
			return sexProps.pawn.Map.mapPawns.AllPawnsSpawned.Any(x =>
				x != sexProps.pawn
				&& x != sexProps.partner
				&& !x.Dead
				&& (zoo || !xxx.is_animal(x))
				&& x.CanSee(sexProps.pawn));
		}

		public static bool SatisfiesSapiosexual(Pawn partner)
		{
			if (!xxx.has_traits(partner)) return false;
			return partner.story.traits.HasTrait(TraitDefOf.TooSmart)
				|| (xxx.CTIsActive && partner.story.traits.HasTrait(xxx.RCT_Savant))
				|| (xxx.IndividualityIsActive && partner.story.traits.HasTrait(xxx.SYR_CreativeThinker))
				|| (xxx.CTIsActive && partner.story.traits.HasTrait(xxx.RCT_Inventor))
				|| partner.story.traits.HasTrait(TraitDefOf.GreatMemory)
				|| partner.story.traits.HasTrait(TraitDefOf.Transhumanist)
				|| partner.skills.GetSkill(SkillDefOf.Intellectual).levelInt >= 15;
		}

		public static bool SatisfiesTeratophile(Pawn partner)
		{
			if (partner.story == null) return false;
			var story = partner.story;
			return RelationsUtility.IsDisfigured(partner)
				|| story.bodyType == BodyTypeDefOf.Fat
				|| story.traits.HasTrait(TraitDefOf.CreepyBreathing)
				|| (story.traits.HasTrait(TraitDefOf.Beauty) && story.traits.DegreeOfTrait(TraitDefOf.Beauty) < 0)
				|| partner.GetStatValue(StatDefOf.PawnBeauty) < 0;
		}

		public static void AddThought(Pawn pawn)
		{
			var thoughtDef = DefDatabase<ThoughtDef>.GetNamed("ThatsMyFetish");
			pawn.needs.mood.thoughts.memories.TryGainMemory(thoughtDef);
		}
	}
}


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

namespace rjw
{
	public static class QuirkAdder
	{
		public static void Add(Pawn pawn, Quirk quirk, bool warnOnFail = false)
		{
			if (!pawn.Has(quirk))
			{
				var hasFertility = pawn.RaceHasFertility();
				if (quirk == Quirk.Fertile && (!hasFertility || CompRJW.Comp(pawn).quirks.ToString().Contains(Quirk.Infertile.Key)))
				{
					if (warnOnFail)
					{
						ModLog.Warning($"Tried to add quirk {quirk} to {pawn.Name}, but either {pawn.def} does not have fertility or {pawn.Name} has a conflicting quirk");
					}
					return;
				}
				if (quirk == Quirk.Infertile && (!hasFertility || CompRJW.Comp(pawn).quirks.ToString().Contains(Quirk.Fertile.Key)))
				{
					if (warnOnFail)
					{
						ModLog.Warning($"Tried to add quirk {quirk} to {pawn.Name}, but either {pawn.def} does not have fertility or {pawn.Name} has a conflicting quirk");
					}
					return;
				}
				// No fair having a fetish for your own race.
				// But tags don't conflict so having a fetish for robot plant dragons is fine.
				if (quirk.RaceTag != null && pawn.Has(quirk.RaceTag))
				{
					if (warnOnFail)
					{
						ModLog.Warning($"Tried to add quirk {quirk} to {pawn.Name}, but {pawn} already has the associated race tag {quirk.RaceTag}");
					}
					return;
				}
				if (quirk == Quirk.Fertile)
				{
					var fertility = HediffDef.Named("IncreasedFertility");
					if (fertility != null)
						pawn.health.AddHediff(fertility);
				}
				if (quirk == Quirk.Infertile)
				{
					var infertility = HediffDef.Named("DecreasedFertility");
					if (infertility != null)
						pawn.health.AddHediff(infertility);
				}
				if ((quirk == Quirk.Buttslut && !RJWPreferenceSettings.PlayerIsButtSlut)
					|| (quirk == Quirk.Podophile && !RJWPreferenceSettings.PlayerIsFootSlut)
					|| (quirk == Quirk.Cumslut && !RJWPreferenceSettings.PlayerIsCumSlut))
				{
					if (warnOnFail)
					{
						ModLog.Warning($"Tried to add quirk {quirk} to {pawn.Name}, but all relevant sex types are pseudo-disabled (i.e. given minimum weight in settings)");
					}
					return;
				}

				CompRJW.Comp(pawn).quirks.AppendWithComma(quirk.Key);
				CompRJW.Comp(pawn).quirksave = CompRJW.Comp(pawn).quirks.ToString();
				quirk.DoAfterAdd(pawn);
			}
		}
		public static void Remove(Pawn pawn, Quirk quirk)
		{
			if (pawn.Has(quirk))
			{
				if (quirk == Quirk.Fertile)
				{
					var fertility = HediffDef.Named("IncreasedFertility");
					if (fertility != null)
						pawn.health.RemoveHediff(pawn.health.hediffSet.GetFirstHediffOfDef(fertility));
				}
				if (quirk == Quirk.Infertile)
				{
					var infertility = HediffDef.Named("DecreasedFertility");
					if (infertility != null)
						pawn.health.RemoveHediff(pawn.health.hediffSet.GetFirstHediffOfDef(infertility));
				}

				//CompRJW.Comp(pawn).quirks.AppendWithComma(quirk.Key);
				CompRJW.Comp(pawn).quirks.Replace(quirk.Key, null);

				if (CompRJW.Comp(pawn).quirks.Length == 0)
					CompRJW.Comp(pawn).quirks.Append("None");

				CompRJW.Comp(pawn).quirksave = CompRJW.Comp(pawn).quirks.ToString();
			}
		}

		public static void Clear(Pawn pawn)
		{
			Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("IncreasedFertility"));
			if (fertility != null)
				pawn.health.RemoveHediff(fertility);
			Hediff infertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("DecreasedFertility"));
			if (infertility != null)
				pawn.health.RemoveHediff(infertility);

			CompRJW.Comp(pawn).quirks = new StringBuilder();

			if (CompRJW.Comp(pawn).quirks.Length == 0)
				CompRJW.Comp(pawn).quirks.Append("None");

			CompRJW.Comp(pawn).quirksave = CompRJW.Comp(pawn).quirks.ToString();
		}

		public static void Generate(Pawn pawn)
		{
			if (!pawn.RaceHasSexNeed() || (pawn.kindDef.race.defName.ToLower().Contains("droid") && !AndroidsCompatibility.IsAndroid(pawn)))
			{
				return;
			}
			else if (pawn.IsAnimal())
			{
				GenerateForAnimal(pawn);
			}
			else
			{
				GenerateForHumanlike(pawn);
			}
		}

		[SyncMethod]
		static void GenerateForHumanlike(Pawn pawn)
		{
			var count = Rand.RangeInclusive(0, RJWPreferenceSettings.MaxQuirks);
			var list = Quirk.All.ToList();
			list.Shuffle();

			// Some quirks may be hard for a given pawn to indulge in.
			// For example a female homosexual will have a hard time satisfying an impregnation fetish.
			// But rimworld is a weird place and you never know what the pawn will be capable of in the future.
			// We still don't want straight up contradictory results like fertile + infertile.
			var hasFertility = pawn.RaceHasFertility();
			var actual = new List<Quirk>();
			foreach (var quirk in list)
			{
				if (count == 0)
				{
					break;
				}

				// These special cases are sort of hacked in.
				// In theory there should be a general way for the quirk itself to decide when it applies.
				if (quirk == Quirk.Fertile && (!hasFertility || actual.Contains(Quirk.Infertile)))
				{
					continue;
				}
				if (quirk == Quirk.Infertile && (!hasFertility || actual.Contains(Quirk.Fertile)))
				{
					continue;
				}
				// Have to earn these.
				if (quirk == Quirk.Breeder || quirk == Quirk.Incubator)
				{
					continue;
				}
				// No fair having a fetish for your own race.
				// But tags don't conflict so having a fetish for robot plant dragons is fine.
				if (quirk.RaceTag != null && pawn.Has(quirk.RaceTag))
				{
					continue;
				}

				count--;
				actual.Add(quirk);
			}

			foreach (var quirk in actual)
			{
				pawn.Add(quirk);
			}
		}

		[SyncMethod]
		static void GenerateForAnimal(Pawn pawn)
		{
			if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Messy);
			}

			if (!pawn.RaceHasFertility())
			{
				return;
			}

			if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Fertile);
			}
			else if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Infertile);
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// data for sex related stuff/outcome
	/// </summary>
	public class SexProps : IExposable
	{
		public Pawn pawn;
		public Pawn partner;
		public bool hasPartner() => partner != null && partner != pawn;

		public xxx.rjwSextype sexType = xxx.rjwSextype.None;
		public InteractionDef dictionaryKey = null;
		public string rulePack = null;

		public bool usedCondom = false;
		public bool isRape = false;
		public bool isReceiver = false;// as JobDriver_SexBaseReciever
		public bool isRevese = false;// brainfuck with who fucks who, see impregnation for clarity with tags n shit?.     interaction.HasInteractionTag(InteractionTag.Reverse)
		public bool isRapist = false;
		public bool isCoreLovin = false;//vanilla loving, skip some mechanics.  should really clean this up someday
		public bool isWhoring = false;
		public bool canBeGuilty = true;// can initiator pawn be counted guilty for percepts, player initiated/rmb actrions = false

		public int orgasms = 0; // The orgasms had by the pawn
		public SexProps()
		{
		}

		public bool IsInitiator() => !isReceiver;

		public bool IsSubmissive() => (isReceiver && !isRevese) || (!isReceiver && isRevese);

		public SexProps GetForPartner()
		{
			return new SexProps
			{
				pawn = partner,
				partner = pawn,
				sexType = sexType,
				dictionaryKey = dictionaryKey,
				rulePack = rulePack,
				usedCondom = usedCondom,
				isRape = isRape,
				isReceiver = !isReceiver,
				isRevese = isRevese,
				isRapist = isRapist,
				isCoreLovin = isCoreLovin,
				isWhoring = isWhoring,
				canBeGuilty = canBeGuilty,
				orgasms = orgasms
			};
		}

		public void ExposeData()
		{
			Scribe_References.Look(ref pawn, "pawn");
			Scribe_References.Look(ref partner, "partner");

			Scribe_Values.Look(ref sexType, "sexType");
			Scribe_Defs.Look(ref dictionaryKey, "dictionaryKey");
			Scribe_Values.Look(ref rulePack, "rulePack");

			Scribe_Values.Look(ref usedCondom, "usedCondom");
			Scribe_Values.Look(ref isRape, "isRape");
			Scribe_Values.Look(ref isReceiver, "isReceiver");
			Scribe_Values.Look(ref isRapist, "isRapist");
			Scribe_Values.Look(ref isCoreLovin, "isCoreLovin");
			Scribe_Values.Look(ref isWhoring, "isWhoring");
			Scribe_Values.Look(ref canBeGuilty, "canBeGuilty");
			Scribe_Values.Look(ref orgasms, "orgasms");
		}
	}
}


code:
using RimWorld;

namespace rjw
{
    [DefOf]
    public static class IssueDefOf
    {
		public static IssueDef Lovin;

        static IssueDefOf()
        {
            DefOfHelper.EnsureInitializedInCtor(typeof(IssueDefOf));
        }
    }
}


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

namespace rjw
{
    [DefOf]
    public static class JobDefOf
    {
        static JobDefOf()
        {
            DefOfHelper.EnsureInitializedInCtor(typeof(JobDefOf));
        }

        public static JobDef RJW_AdjustParts;
    }
}


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

namespace Rimlaser
{
    public class JobDriver_AdjustParts : JobDriver
    {
        private Thing Gun => job.GetTarget(TargetIndex.A).Thing;

        private IBeamColorThing BeamColorThing => job.GetTarget(TargetIndex.A).Thing as IBeamColorThing;

        private Thing Prism => job.GetTarget(TargetIndex.B).Thing;

        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            if (BeamColorThing == null) return false;

            return pawn.Reserve(Gun, job, 1, -1, null, errorOnFailed) && pawn.Reserve(Prism, job, 1, -1, null, errorOnFailed);
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
            yield return Toils_Goto.GotoThing(TargetIndex.B, PathEndMode.Touch).FailOnDespawnedNullOrForbidden(TargetIndex.B).FailOnSomeonePhysicallyInteracting(TargetIndex.B);
            yield return Toils_Haul.StartCarryThing(TargetIndex.B, false, false, false);
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.Touch).FailOnDespawnedOrNull(TargetIndex.A);
            Toil repair = Toils_General.Wait(75, TargetIndex.None);
            repair.FailOnDespawnedOrNull(TargetIndex.A);
            repair.FailOnCannotTouch(TargetIndex.A, PathEndMode.Touch);
            repair.WithEffect(Gun.def.repairEffect, TargetIndex.A);
            repair.WithProgressBarToilDelay(TargetIndex.A, false, -0.5f);
            yield return repair;
            yield return new Toil
            {
                initAction = delegate ()
                {

                    BeamColorThing.BeamColor = pawn.CurJob.maxNumMeleeAttacks;

                    var targetInfo = new TargetInfo(Gun.Position, Map, false);
                    Effecter effecter = EffecterDefOf.Deflect_Metal.Spawn();
                    effecter.Trigger(targetInfo, targetInfo);
                    effecter.Cleanup();
                    this.Prism.Destroy(DestroyMode.Vanish);
                }
            };
            yield break;
        }
    }
}


code:
using RimWorld;

namespace rjw
{
    [DefOf]
    public static class PreceptDefOf
    {
		[MayRequireIdeology]
		public static PreceptDef Lovin_Prohibited;

		[MayRequireIdeology]
		public static PreceptDef Lovin_Horrible;

		[MayRequireIdeology]
		public static PreceptDef Lovin_SpouseOnly_Strict;

		[MayRequireIdeology]
		public static PreceptDef Lovin_SpouseOnly_Moderate;

		[MayRequireIdeology]
		public static PreceptDef Lovin_SpouseOnly_Mild;

		public static PreceptDef Lovin_Free;

		[MayRequireIdeology]
		public static PreceptDef Lovin_FreeApproved;

        static PreceptDefOf()
        {
            DefOfHelper.EnsureInitializedInCtor(typeof(IssueDefOf));
        }
    }
}


code:
using Verse;
using RimWorld;

namespace rjw
{
    [DefOf]
    public static class RJWHediffDefOf
    {
        public static HediffDef HumpShroomAddiction;

        public static HediffDef HumpShroomEffect;
    }
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	[DefOf]
	public static class RJW_RecipeDefOf
	{
		public static RecipeDef AttachPenis;
		public static RecipeDef InstallPenis;
		public static RecipeDef RemovePenis;
	}
}


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

namespace rjw
{
	public static class PawnDesignations_Breedee
	{
		public static bool UpdateCanDesignateBreeding(this Pawn pawn)
		{
			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return pawn.GetRJWPawnData().CanDesignateBreeding = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return pawn.GetRJWPawnData().CanDesignateBreeding = false;

			//cant have penetrative sex
			if (!xxx.can_be_fucked(pawn))
				return pawn.GetRJWPawnData().CanDesignateBreeding = false;

			if (RJWSettings.bestiality_enabled && xxx.is_human(pawn))
			{
				if (!pawn.IsDesignatedHero())
				{
					if ((xxx.is_zoophile(pawn) || (RJWSettings.override_RJW_designation_checks && !MP.IsInMultiplayer)) && pawn.IsColonist)
						return pawn.GetRJWPawnData().CanDesignateBreeding = true;
				}
				else if (pawn.IsHeroOwner())
					return pawn.GetRJWPawnData().CanDesignateBreeding = true;

				if (pawn.IsPrisonerOfColony || xxx.is_slave(pawn))
					return pawn.GetRJWPawnData().CanDesignateBreeding = true;
			}

			if (RJWSettings.animal_on_animal_enabled && xxx.is_animal(pawn)
				&& pawn.Faction == Faction.OfPlayer)
				return pawn.GetRJWPawnData().CanDesignateBreeding = true;

			return pawn.GetRJWPawnData().CanDesignateBreeding = false;
		}
		public static bool CanDesignateBreeding(this Pawn pawn)
		{
			return pawn.GetRJWPawnData().CanDesignateBreeding;
		}
		public static void ToggleBreeding(this Pawn pawn)
		{
			pawn.UpdateCanDesignateBreeding();
			if (pawn.CanDesignateBreeding())
			{
				if (!pawn.IsDesignatedBreeding())
					DesignateBreeding(pawn);
				else
					UnDesignateBreeding(pawn);
			}
		}
		public static bool IsDesignatedBreeding(this Pawn pawn)
		{
			if (pawn.GetRJWPawnData().Breeding)
			{
				if (!xxx.is_animal(pawn))
				{
					if (!RJWSettings.bestiality_enabled)
						UnDesignateBreeding(pawn);

					else if (!pawn.IsDesignatedHero())
						if (!(xxx.is_zoophile(pawn) || pawn.IsPrisonerOfColony || xxx.is_slave(pawn)))
							if (!(RJWSettings.WildMode || (RJWSettings.override_RJW_designation_checks && !MP.IsInMultiplayer)))
								UnDesignateBreeding(pawn);
				}
				else
				{
					if (!RJWSettings.animal_on_animal_enabled)
						UnDesignateBreeding(pawn);

					else if (!pawn.Faction?.IsPlayer ?? false)
						UnDesignateBreeding(pawn);
				}
				if (pawn.Dead)
					pawn.UnDesignateBreeding();
			}

			return pawn.GetRJWPawnData().Breeding;
		}
		[SyncMethod]
		public static void DesignateBreeding(this Pawn pawn)
		{
			DesignatorsData.rjwBreeding.AddDistinct(pawn);
			pawn.GetRJWPawnData().Breeding = true;
		}
		[SyncMethod]
		public static void UnDesignateBreeding(this Pawn pawn)
		{
			DesignatorsData.rjwBreeding.Remove(pawn);
			pawn.GetRJWPawnData().Breeding = false;
		}
	}
}


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

namespace rjw
{
	public static class PawnDesignations_Breeder
	{
		public static bool UpdateCanDesignateBreedingAnimal(this Pawn pawn)
		{
			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return pawn.GetRJWPawnData().CanDesignateBreedingAnimal = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return pawn.GetRJWPawnData().CanDesignateBreedingAnimal = false;

			//Log.Message("CanDesignateAnimal for " + xxx.get_pawnname(pawn) + " " + SaveStorage.bestiality_enabled);
			//Log.Message("checking animal props " + (pawn.Faction?.IsPlayer.ToString()?? "tynanfag") + xxx.is_animal(pawn) + xxx.can_rape(pawn));
			if ((RJWSettings.bestiality_enabled || RJWSettings.animal_on_animal_enabled)
				&& xxx.is_animal(pawn)
				&& xxx.can_fuck(pawn)
				&& pawn.Faction == Faction.OfPlayer)
				return pawn.GetRJWPawnData().CanDesignateBreedingAnimal = true;

			return pawn.GetRJWPawnData().CanDesignateBreedingAnimal = false;
		}
		public static bool CanDesignateBreedingAnimal(this Pawn pawn)
		{
			return pawn.GetRJWPawnData().CanDesignateBreedingAnimal;
		}
		public static void ToggleBreedingAnimal(this Pawn pawn)
		{
			pawn.UpdateCanDesignateBreedingAnimal();
			if (pawn.CanDesignateBreedingAnimal())
			{
				if (!pawn.IsDesignatedBreedingAnimal())
					DesignateBreedingAnimal(pawn);
				else
					UnDesignateBreedingAnimal(pawn);
			}
		}
		public static bool IsDesignatedBreedingAnimal(this Pawn pawn)
		{
			if (pawn.GetRJWPawnData().BreedingAnimal)
			{
				if (!pawn.Faction?.IsPlayer ?? false)
					UnDesignateBreedingAnimal(pawn);

				if (pawn.Dead)
					pawn.UnDesignateBreedingAnimal();
			}

			return pawn.GetRJWPawnData().BreedingAnimal;
		}
		[SyncMethod]
		public static void DesignateBreedingAnimal(this Pawn pawn)
		{
			DesignatorsData.rjwBreedingAnimal.AddDistinct(pawn);
			pawn.GetRJWPawnData().BreedingAnimal = true;
		}
		[SyncMethod]
		public static void UnDesignateBreedingAnimal(this Pawn pawn)
		{
			DesignatorsData.rjwBreedingAnimal.Remove(pawn);
			pawn.GetRJWPawnData().BreedingAnimal = false;
		}
	}
}