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

namespace rjw
{
	class RaceGroupDef_Helper
	{
		public static readonly IDictionary<SexPartType, BodyPartDef> BodyPartDefBySexPartType = new Dictionary<SexPartType, BodyPartDef>();

		/// <summary>
		/// Cache for TryGetRaceGroupDef.
		/// </summary>
		public static readonly IDictionary<PawnKindDef, RaceGroupDef> RaceGroupByPawnKind = new Dictionary<PawnKindDef, RaceGroupDef>();

		static RaceGroupDef_Helper()
		{
			BodyPartDefBySexPartType.Add(SexPartType.Anus, xxx.anusDef);
			BodyPartDefBySexPartType.Add(SexPartType.FemaleBreast, xxx.breastsDef);
			BodyPartDefBySexPartType.Add(SexPartType.FemaleGenital, xxx.genitalsDef);
			BodyPartDefBySexPartType.Add(SexPartType.MaleBreast, xxx.breastsDef);
			BodyPartDefBySexPartType.Add(SexPartType.MaleGenital, xxx.genitalsDef);
		}

		static bool IsThisMod(Def def)
		{
			var rjwContent = LoadedModManager.RunningMods.Single(pack => pack.Name == "RimJobWorld");
			return rjwContent.AllDefs.Contains(def);
		}

		/// <summary>
		/// Returns the best match RaceGroupDef for the given pawn, or null if none is found.
		/// </summary>
		static RaceGroupDef GetRaceGroupDefInternal(Pawn pawn)
		{
			var kindDef = pawn.kindDef;
			var raceName = kindDef.race.defName;
			var pawnKindName = kindDef.defName;
			var groups = DefDatabase<RaceGroupDef>.AllDefs;

			var kindMatches = groups.Where(group => group.pawnKindNames?.Contains(pawnKindName) ?? false).ToList();
			var raceMatches = groups.Where(group => group.raceNames?.Contains(raceName) ?? false).ToList();
			var count = kindMatches.Count() + raceMatches.Count();
			if (count == 0)
			{
				//Log.Message($"[RJW] Pawn named '{pawn.Name}' matched no RaceGroupDef. If you want to create a matching RaceGroupDef you can use the raceName '{raceName}' or the pawnKindName '{pawnKindName}'.");
				return null;
			}
			else if (count == 1)
			{
				// Log.Message($"[RJW] Pawn named '{pawn.Name}' matched 1 RaceGroupDef.");
				return kindMatches.Concat(raceMatches).Single();
			}
			else
			{
				// Log.Message($"[RJW] Pawn named '{pawn.Name}' matched {count} RaceGroupDefs.");

				// If there are multiple RaceGroupDef matches, choose one of them.
				// First prefer defs NOT defined in rjw.
				// Then prefer a match by kind over a match by race.
				return kindMatches.FirstOrDefault(match => !IsThisMod(match))
					?? raceMatches.FirstOrDefault(match => !IsThisMod(match))
					?? kindMatches.FirstOrDefault()
					?? raceMatches.FirstOrDefault();
			}
		}

		public static bool TryGetRaceGroupDef(Pawn pawn, out RaceGroupDef raceGroupDef)
		{
			if (RaceGroupByPawnKind.TryGetValue(pawn.kindDef, out raceGroupDef))
			{
				return raceGroupDef != null;
			}
			else
			{
				raceGroupDef = GetRaceGroupDefInternal(pawn);
				RaceGroupByPawnKind.Add(pawn.kindDef, raceGroupDef);
				return raceGroupDef != null;
			}
		}

		public static List<string> GetParts(RaceGroupDef raceGroupDef, SexPartType sexPartType)
		{
			switch(sexPartType)
			{
				case SexPartType.Anus:
					return raceGroupDef.anuses;
				case SexPartType.FemaleBreast:
					return raceGroupDef.femaleBreasts;
				case SexPartType.FemaleGenital:
					return raceGroupDef.femaleGenitals;
				case SexPartType.MaleBreast:
					return raceGroupDef.maleBreasts;
				case SexPartType.MaleGenital:
					return raceGroupDef.maleGenitals;
				default:
					throw new ApplicationException($"Unrecognized sexPartType: {sexPartType}");
			}
		}

		public static List<float> GetPartsChances(RaceGroupDef raceGroupDef, SexPartType sexPartType)
		{
			switch(sexPartType)
			{
				case SexPartType.Anus:
					return raceGroupDef.chanceanuses;
				case SexPartType.FemaleBreast:
					return raceGroupDef.chancefemaleBreasts;
				case SexPartType.FemaleGenital:
					return raceGroupDef.chancefemaleGenitals;
				case SexPartType.MaleBreast:
					return raceGroupDef.chancemaleBreasts;
				case SexPartType.MaleGenital:
					return raceGroupDef.chancemaleGenitals;
				default:
					throw new ApplicationException($"Unrecognized sexPartType: {sexPartType}");
			}
		}

		/// <summary>
		/// Returns true if a sex part was chosen (even if that part is "no part").
		/// </summary>
		[SyncMethod]
		public static bool TryAddSexPart(Pawn pawn, SexPartType sexPartType)
		{
			if (!TryGetRaceGroupDef(pawn, out var raceGroupDef))
			{
				// No race, so nothing was chosen.
				return false;
			}

			var parts = GetParts(raceGroupDef, sexPartType);
			if (parts == null)
			{
				// Missing list, so nothing was chosen.
				return false;
			}
			else if (!parts.Any())
			{
				// Empty list, so "no part" was chosen.
				return true;
			}

			var target = BodyPartDefBySexPartType[sexPartType];
			var part = pawn.RaceProps.body.AllParts.Find(bpr => bpr.def == target);
			var chances = GetPartsChances(raceGroupDef, sexPartType);
			var hasChances = chances != null && chances.Count() > 0;

			if (hasChances && chances.Count() != parts.Count())
			{
				Log.Error($"[RJW] RaceGroupDef named {raceGroupDef.defName} in {raceGroupDef.defPackage} has {parts.Count()} parts but {chances.Count()} chances for {sexPartType}.");
				return false;
			}

			string name;

			if (hasChances)
			{
				var indexes = parts.Select((x, i) => i);
				name = parts[indexes.RandomElementByWeight(i => chances[i])];
			}
			else
			{
				name = parts.RandomElement();
			}

			if (string.IsNullOrEmpty(name))
			{
				// Randomly chose "no part".
				return true;
			}

			var hediffDef = DefDatabase<HediffDef>.GetNamedSilentFail(name);
			if (hediffDef == null)
			{
				Log.Error($"[RJW] Could not find a HediffDef named {name} referenced by RaceGroupDef named {raceGroupDef.defName} in {raceGroupDef.defPackage}.");
				return false;
			}

			pawn.health.AddHediff(hediffDef, part);
			// A part was chosen and added.
			return true;
		}
	}
}


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

namespace rjw
{
	class SexPartAdder
	{
		// return True if going to set penis
		// return False for vagina
		public static bool privates_gender(Pawn pawn, Gender gender)
		{
			return (pawn.gender == Gender.Male) ? (gender != Gender.Female) : (gender == Gender.Male);
		}

		[SyncMethod]
		static double GenderTechLevelCheck(Pawn pawn)
		{
			if (pawn == null)
				return 0;
			//--Log.Message("[RJW] check pawn tech level for bionics ( " + xxx.get_pawnname(pawn) + " )");

			bool lowtechlevel = true;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			double value = Rand.Value;

			if (pawn.Faction != null)
				lowtechlevel = (int)pawn.Faction.def.techLevel < 5;

			//--save savages from inventing hydraulic and bionic genitals
			while (lowtechlevel && value >= 0.90)
			{
				value = Rand.Value;
			}
			return value;
		}

		[SyncMethod]
		public static void add_genitals(Pawn pawn, Pawn parent = null, Gender gender = Gender.None)
		{
			//--Log.Message("Genital_Helper::add_genitals( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = Genital_Helper.get_genitals(pawn);
			//--Log.Message("Genital_Helper::add_genitals( " + xxx.get_pawnname(pawn) + " ) - checking genitals");
			if (Part == null)
			{
				//--Log.Message("[RJW] add_genitals( " + xxx.get_pawnname(pawn) + " ) doesn't have a genitals");
				return;
			}
			else if (pawn.health.hediffSet.PartIsMissing(Part))
			{
				//--Log.Message("[RJW] add_genitals( " + xxx.get_pawnname(pawn) + " ) had a genital but was removed.");
				return;
			}
			if (Genital_Helper.has_genitals(pawn) && gender == Gender.None)//allow to add gender specific genitals(futa)
			{
				//--Log.Message("[RJW] add_genitals( " + xxx.get_pawnname(pawn) + " ) already has genitals");
				return;
			}
			var temppawn = pawn;
			if (parent != null)
				temppawn = parent;

			HediffDef privates;
			double value = GenderTechLevelCheck(pawn);
			string racename = temppawn.kindDef.race.defName.ToLower();

			// maybe add some check based on bodysize of pawn for genitals size limit
			//Log.Message("Genital_Helper::add_genitals( " + pawn.RaceProps.baseBodySize + " ) - 1");
			//Log.Message("Genital_Helper::add_genitals( " + pawn.kindDef.race.size. + " ) - 2");

			privates = (privates_gender(pawn, gender)) ? Genital_Helper.generic_penis : Genital_Helper.generic_vagina;

			if (Genital_Helper.has_vagina(pawn) && privates == Genital_Helper.generic_vagina)
			{
				//--Log.Message("[RJW] add_genitals( " + xxx.get_pawnname(pawn) + " ) already has vagina");
				return;
			}
			if ((Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)) && privates == Genital_Helper.generic_penis)
			{
				//--Log.Message("[RJW] add_genitals( " + xxx.get_pawnname(pawn) + " ) already has penis");
				return;
			}

			//override race genitals
			if (privates == Genital_Helper.generic_vagina && pawn.TryAddSexPart(SexPartType.FemaleGenital))
			{
				return;
			}
			if (privates == Genital_Helper.generic_penis && pawn.TryAddSexPart(SexPartType.MaleGenital))
			{
				return;
			}

			//Log.Message("Genital_Helper::add_genitals( " + xxx.get_pawnname(pawn));
			//Log.Message("Genital_Helper::add_genitals( " + pawn.kindDef.race.defName);
			//Log.Message("Genital_Helper::is male( " + privates_gender(pawn, gender));
			//Log.Message("Genital_Helper::is male1( " + pawn.gender);
			//Log.Message("Genital_Helper::is male2( " + gender);
			if (xxx.is_mechanoid(pawn))
			{
				return;
			}
			//insects
			else if (xxx.is_insect(temppawn)
				 || racename.Contains("apini")
				 || racename.Contains("mantodean")
				 || racename.Contains("insect")
				 || racename.Contains("bug"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.ovipositorM : Genital_Helper.ovipositorF;
				//override for Better infestations, since queen is male at creation
				if (racename.Contains("Queen"))
					privates = Genital_Helper.ovipositorF;
			}
			//space cats pawns
			else if ((racename.Contains("orassan") || racename.Contains("neko")) && !racename.ContainsAny("akaneko"))
			{
				if ((value < 0.70) || (pawn.ageTracker.AgeBiologicalYears < 2) || !pawn.RaceProps.Humanlike)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.feline_penis : Genital_Helper.feline_vagina;
				else if (value < 0.90)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
				else
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
			}
			//space dog pawns
			else if (racename.Contains("fennex")
				 || racename.Contains("xenn")
				 || racename.Contains("leeani")
				 || racename.Contains("ferian")
				 || racename.Contains("callistan"))
			{
				if ((value < 0.70) || (pawn.ageTracker.AgeBiologicalYears < 2) || !pawn.RaceProps.Humanlike)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.canine_penis : Genital_Helper.canine_vagina;
				else if (value < 0.90)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
				else
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
			}
			//space horse pawns
			else if (racename.Contains("equium"))
			{
				if ((value < 0.70) || (pawn.ageTracker.AgeBiologicalYears < 2) || !pawn.RaceProps.Humanlike)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.equine_penis : Genital_Helper.equine_vagina;
				else if (value < 0.90)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
				else
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
			}
			//space raccoon pawns
			else if (racename.Contains("racc") && !racename.Contains("raccoon"))
			{
				if ((value < 0.70) || (pawn.ageTracker.AgeBiologicalYears < 2) || !pawn.RaceProps.Humanlike)
				{
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.raccoon_penis : Genital_Helper.generic_vagina;
				}
				else if (value < 0.90)
				{
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
				}
				else
				{
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
				}
			}
			//alien races - ChjDroid, ChjAndroid
			else if (racename.Contains("droid"))
			{
				if (pawn.story.GetBackstory(BackstorySlot.Childhood) != null)
				{
					if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("bishojo"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("pleasure"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("idol"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("social"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("substitute"))
					{
						if (value < 0.05)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.micro_penis : Genital_Helper.micro_vagina;
						else if (value < 0.20)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.small_penis : Genital_Helper.tight_vagina;
						else if (value < 0.70)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.average_penis : Genital_Helper.average_vagina;
						else if (value < 0.80)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.big_penis : Genital_Helper.loose_vagina;
						else if (value < 0.90)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.huge_penis : Genital_Helper.gaping_vagina;
						else if (value < 0.95)
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
						else
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
					}
					else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
					{
						if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
						else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
							privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
					}
					else
						return;
				}
				else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
				{
					if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
					else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
				}
				if (privates == Genital_Helper.generic_penis || privates == Genital_Helper.generic_vagina)
					return;
			}
			//animal cats
			else if (racename.ContainsAny("cat", "cougar", "lion", "leopard", "cheetah", "panther", "tiger", "lynx", "smilodon", "akaneko"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.feline_penis : Genital_Helper.feline_vagina;
			}
			//animal canine/dogs
			else if (racename.ContainsAny("husky", "warg", "terrier", "collie", "hound", "retriever", "mastiff", "wolf", "fox",
				"vulptex", "dachshund", "schnauzer", "corgi", "pug", "doberman", "chowchow", "borzoi", "saintbernard", "newfoundland",
				"poodle", "dog", "coyote") && !racename.Contains("lf_foxia"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.canine_penis : Genital_Helper.canine_vagina;
			}
			//animal horse - MoreMonstergirls
			else if (racename.ContainsAny("horse", "centaur", "zebra", "donkey", "dryad"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.equine_penis : Genital_Helper.equine_vagina;
			}
			//animal raccoon
			else if (racename.Contains("racc"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.raccoon_penis : Genital_Helper.generic_vagina;
			}
			//animal crocodilian (alligator, crocodile, etc)
			else if (racename.ContainsAny("alligator", "crocodile", "caiman", "totodile", "croconaw", "feraligatr", "quinkana", "purussaurus", "kaprosuchus", "sarcosuchus"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.crocodilian_penis : Genital_Helper.generic_vagina;
			}
			//hemipenes - mostly reptiles and snakes
			else if (racename.ContainsAny("guana", "cobra", "gecko", "snake", "boa", "quinkana", "megalania", "gila", "gigantophis", "komodo", "basilisk", "thorny", "onix", "lizard", "slither") && !racename.ContainsAny("boar"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.hemipenis : Genital_Helper.generic_vagina;
			}
			//animal dragon - MoreMonstergirls
			else if (racename.ContainsAny("dragon", "thrumbo", "drake", "charizard", "saurus") && !racename.Contains("lf_dragonia"))
			{
				privates = (privates_gender(pawn, gender)) ? Genital_Helper.dragon_penis : Genital_Helper.dragon_vagina;
			}
			//animal slime - MoreMonstergirls
			else if (racename.Contains("slime"))
			{
				//privates = (privates_gender(pawn, gender)) ? slime_penis : slime_vagina;
				pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.slime_penis : Genital_Helper.slime_vagina, Part);
				pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.slime_vagina : Genital_Helper.slime_penis, Part);
				return;
			}
			//animal demons - MoreMonstergirls
			else if (racename.Contains("impmother") || racename.Contains("demon"))
			{
				pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.demon_penis : Genital_Helper.demon_vagina, Part);
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				if (Rand.Value < 0.25f)
					pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.demon_penis : Genital_Helper.demonT_penis, Part);
				return;
			}
			//animal demons - MoreMonstergirls
			else if (racename.Contains("baphomet"))
			{
				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				if (Rand.Value < 0.50f)
					pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.demon_penis : Genital_Helper.demon_vagina, Part);
				else
					pawn.health.AddHediff((privates_gender(pawn, gender)) ? Genital_Helper.equine_penis : Genital_Helper.demon_vagina, Part);
				return;
			}
			//humanoid monstergirls - LostForest
			else if (racename.ContainsAny("lf_dragonia", "lf_flammelia", "lf_foxia", "lf_glacia", "lf_lefca", "lf_wolvern"))
			{
				if (value < 0.05)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.micro_penis : Genital_Helper.micro_vagina;
				else if (value < 0.20)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.small_penis : Genital_Helper.tight_vagina;
				else if (value < 0.70)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.average_penis : Genital_Helper.average_vagina;
				else if (value < 0.90)
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.big_penis : Genital_Helper.loose_vagina;
				else
					privates = (privates_gender(pawn, gender)) ? Genital_Helper.huge_penis : Genital_Helper.loose_vagina;
			}
			else if (pawn.RaceProps.Humanlike)
			{
				//--Log.Message("Genital_Helper::add_genitals( " + xxx.get_pawnname(pawn) + " ) - race is humanlike");
				if (pawn.ageTracker.AgeBiologicalYears < 5)
				{
					if (value < 0.05)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.micro_penis : Genital_Helper.micro_vagina;
					else if (value < 0.20)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.small_penis : Genital_Helper.tight_vagina;
					else if (value < 0.70)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.average_penis : Genital_Helper.average_vagina;
					else if (value < 0.90)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.big_penis : Genital_Helper.loose_vagina;
					else
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.huge_penis : Genital_Helper.loose_vagina;
				}
				else
				{
					if (value < 0.02)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.peg_penis : Genital_Helper.micro_vagina;
					else if (value < 0.05)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.micro_penis : Genital_Helper.micro_vagina;
					else if (value < 0.20)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.small_penis : Genital_Helper.tight_vagina;
					else if (value < 0.70)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.average_penis : Genital_Helper.average_vagina;
					else if (value < 0.80)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.big_penis : Genital_Helper.loose_vagina;
					else if (value < 0.90)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.huge_penis : Genital_Helper.gaping_vagina;
					else if (value < 0.95)
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.hydraulic_penis : Genital_Helper.hydraulic_vagina;
					else
						privates = (privates_gender(pawn, gender)) ? Genital_Helper.bionic_penis : Genital_Helper.bionic_vagina;
				}
			}
			//--Log.Message("Genital_Helper::add_genitals final ( " + xxx.get_pawnname(pawn) + " ) " + privates.defName);
			pawn.health.AddHediff(privates, Part);
		}

		public static void add_breasts(Pawn pawn, Pawn parent = null, Gender gender = Gender.None)
		{
			//--Log.Message("[RJW] add_breasts( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = Genital_Helper.get_breasts(pawn);

			if (Part == null)
			{
				//--Log.Message("[RJW] add_breasts( " + xxx.get_pawnname(pawn) + " ) - pawn doesn't have a breasts");
				return;
			}
			else if (pawn.health.hediffSet.PartIsMissing(Part))
			{
				//--Log.Message("[RJW] add_breasts( " + xxx.get_pawnname(pawn) + " ) had breasts but were removed.");
				return;
			}
			if (Genital_Helper.has_breasts(pawn))
			{
				//--Log.Message("[RJW] add_breasts( " + xxx.get_pawnname(pawn) + " ) - pawn already has breasts");
				return;
			}

			var temppawn = pawn;
			if (parent != null)
				temppawn = parent;

			HediffDef bewbs;
			double value = GenderTechLevelCheck(pawn);
			string racename = temppawn.kindDef.race.defName.ToLower();

			bewbs = Genital_Helper.generic_breasts;

			//TODO: figure out how to add (flat) breasts to males
			var sexPartType = (pawn.gender == Gender.Female || gender == Gender.Female)
				? SexPartType.FemaleBreast
				: SexPartType.MaleBreast;
			if (pawn.TryAddSexPart(sexPartType))
			{
				return;
			}
			if (xxx.is_mechanoid(pawn))
			{
				return;
			}
			if (xxx.is_insect(temppawn))
			{
				// this will probably need override in case there are humanoid insect race
				//--Log.Message("[RJW] add_breasts( " + xxx.get_pawnname(pawn) + " ) - is insect,doesnt need breasts");
				return;
			}
			//alien races - MoreMonstergirls
			else if (racename.Contains("slime"))
			{
				//slimes are always females, and idc what anyone else say!
				pawn.health.AddHediff(Genital_Helper.slime_breasts, Part);
				return;
			}
			else if (pawn.gender == Gender.Female || gender == Gender.Female)
			{
				if (pawn.RaceProps.Humanlike)
				{
					//alien races - ChjDroid, ChjAndroid
					if (racename.ContainsAny("mantis", "rockman", "slug", "zoltan", "engie", "sergal", "cutebold", "dodo", "owl", "parrot",
						"penguin", "cassowary", "chicken", "vulture"))
					{
						pawn.health.AddHediff(Genital_Helper.featureless_chest, Part);
						return;
					}
					else if (racename.ContainsAny("avali", "khess"))
					{
						return;
					}
					else if (racename.Contains("droid"))
					{
						if (pawn.story.GetBackstory(BackstorySlot.Childhood) != null)
						{
							if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("bishojo"))
								bewbs = Genital_Helper.bionic_breasts;
							else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("pleasure"))
								bewbs = Genital_Helper.bionic_breasts;
							else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("idol"))
								bewbs = Genital_Helper.bionic_breasts;
							else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("social"))
								bewbs = Genital_Helper.hydraulic_breasts;
							else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("substitute"))
							{
								if (value < 0.05)
									bewbs = Genital_Helper.flat_breasts;
								else if (value < 0.25)
									bewbs = Genital_Helper.small_breasts;
								else if (value < 0.70)
									bewbs = Genital_Helper.average_breasts;
								else if (value < 0.80)
									bewbs = Genital_Helper.large_breasts;
								else if (value < 0.90)
									bewbs = Genital_Helper.huge_breasts;
								else if (value < 0.95)
									bewbs = Genital_Helper.hydraulic_breasts;
								else
									bewbs = Genital_Helper.bionic_breasts;
							}
							else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
							{
								if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
									bewbs = Genital_Helper.bionic_breasts;
								else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
									bewbs = Genital_Helper.hydraulic_breasts;
							}
							else
								return;
						}
						else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
						{
							if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
								bewbs = Genital_Helper.bionic_breasts;
							else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
								bewbs = Genital_Helper.hydraulic_breasts;
						}
						if (bewbs == Genital_Helper.generic_breasts)
							return;
					}
					//alien races - MoreMonstergirls
					//alien races - Kijin
					else if (racename.Contains("cowgirl") || racename.Contains("kijin"))
					{
						if (value < 0.05)
							bewbs = Genital_Helper.flat_breasts;
						else if (value < 0.10)
							bewbs = Genital_Helper.small_breasts;
						else if (value < 0.20)
							bewbs = Genital_Helper.average_breasts;
						else if (value < 0.50)
							bewbs = Genital_Helper.large_breasts;
						else if (value < 0.75 && racename.Contains("cowgirl"))
							bewbs = Genital_Helper.udder;
						else
							bewbs = Genital_Helper.huge_breasts;
					}
					else if (pawn.ageTracker.AgeBiologicalYears < 5)
					{
						if (value < 0.05)
							bewbs = Genital_Helper.flat_breasts;
						else if (value < 0.15)
							bewbs = Genital_Helper.small_breasts;
						else if (value < 0.70)
							bewbs = Genital_Helper.average_breasts;
						else if (value < 0.80)
							bewbs = Genital_Helper.large_breasts;
						else
							bewbs = Genital_Helper.huge_breasts;
					}
					else
					{
						if (value < 0.05)
							bewbs = Genital_Helper.flat_breasts;
						else if (value < 0.25)
							bewbs = Genital_Helper.small_breasts;
						else if (value < 0.70)
							bewbs = Genital_Helper.average_breasts;
						else if (value < 0.80)
							bewbs = Genital_Helper.large_breasts;
						else if (value < 0.90)
							bewbs = Genital_Helper.huge_breasts;
						else if (value < 0.95)
							bewbs = Genital_Helper.hydraulic_breasts;
						else
							bewbs = Genital_Helper.bionic_breasts;
					}
				}
				//humanoid monstergirls - LostForest
				else if (racename.ContainsAny("lf_dragonia", "lf_flammelia", "lf_foxia", "lf_glacia", "lf_lefca", "lf_wolvern"))
				{
					if (value < 0.10)
						bewbs = Genital_Helper.flat_breasts;
					else if (value < 0.30)
						bewbs = Genital_Helper.small_breasts;
					else if (value < 0.70)
						bewbs = Genital_Helper.average_breasts;
					else if (value < 0.90)
						bewbs = Genital_Helper.large_breasts;
					else
						bewbs = Genital_Helper.huge_breasts;
				}
				else if (racename.ContainsAny("mammoth", "elasmotherium", "chalicotherium", "megaloceros", "sivatherium", "deinotherium",
					"aurochs", "zygolophodon", "uintatherium", "gazelle", "ffalo", "boomalope", "cow", "miltank", "elk", "reek", "nerf",
					"bantha", "tauntaun", "caribou", "deer", "ibex", "dromedary", "alpaca", "llama", "goat", "moose"))
				{
					pawn.health.AddHediff(Genital_Helper.udder, Part);
					return;
				}
				else if (racename.ContainsAny("cassowary", "emu", "dinornis", "ostrich", "turkey", "chicken", "duck", "murkroW", "bustard", "palaeeudyptes",
					"goose", "tukiri", "porg", "yi", "kiwi", "penguin", "quail", "ptarmigan", "doduo", "flamingo", "plup", "empoleon", "meadow ave") && !racename.ContainsAny("duck-billed"))
				{
					return;  // Separate list for birds, to make it easier to add cloaca at some later date.
				}   // Other breastless creatures.
				else if (racename.ContainsAny("titanis", "titanoboa", "guan", "tortoise", "turt", "aerofleet", "quinkana", "megalochelys",
					"purussaurus", "cobra", "dewback", "rancor", "frog", "onyx", "flommel", "lapras", "aron", "chinchou",
					"squirtle", "wartortle", "blastoise", "totodile", "croconaw", "feraligatr", "litwick", "pumpkaboo", "shuppet", "haunter",
					"gastly", "oddish", "hoppip", "tropius", "budew", "roselia", "bellsprout", "drifloon", "chikorita", "bayleef", "meganium",
					"char", "drago", "dratini", "saur", "tyrannus", "carnotaurus", "baryonyx", "minmi", "diplodocus", "phodon", "indominus",
					"raptor", "caihong", "coelophysis", "cephale", "compsognathus", "mimus", "troodon", "dactyl", "tanystropheus", "geosternbergia",
					"deino", "suchus", "dracorex", "cephalus", "trodon", "quetzalcoatlus", "pteranodon", "antarctopelta", "stygimoloch", "rhabdodon",
					"rhamphorhynchus", "ceratops", "ceratus", "zalmoxes", "mochlodon", "gigantophis", "crab", "pulmonoscorpius", "manipulator",
					"meganeura", "euphoberia", "holcorobeus", "protosolpuga", "barbslinger", "blizzarisk", "frostmite", "devourer", "hyperweaver",
					"macrelcana", "acklay", "elemental", "megalania", "gecko", "gator", "komodo", "scolipede", "shuckle", "combee", "shedinja",
					"caterpie", "wurmple", "lockjaw", "needlepost", "needleroll", "squid", "slug", "gila", "pleura"))
				{
					return;
				}
				pawn.health.AddHediff(bewbs, Part);
			}
		}

		public static void add_anus(Pawn pawn, Pawn parent = null)
		{
			BodyPartRecord Part = Genital_Helper.get_anus(pawn);

			if (Part == null)
			{
				//--Log.Message("[RJW] add_anus( " + xxx.get_pawnname(pawn) + " ) doesn't have an anus");
				return;
			}
			else if (pawn.health.hediffSet.PartIsMissing(Part))
			{
				//--Log.Message("[RJW] add_anus( " + xxx.get_pawnname(pawn) + " ) had an anus but was removed.");
				return;
			}
			if (Genital_Helper.has_anus(pawn))
			{
				//--Log.Message("[RJW] add_anus( " + xxx.get_pawnname(pawn) + " ) already has an anus");
				return;
			}

			var temppawn = pawn;
			if (parent != null)
				temppawn = parent;

			HediffDef asshole;
			double value = GenderTechLevelCheck(pawn);
			string racename = temppawn.kindDef.race.defName.ToLower();

			asshole = Genital_Helper.generic_anus;

			if (pawn.TryAddSexPart(SexPartType.Anus))
			{
				return;
			}
			else if (xxx.is_mechanoid(pawn))
			{
				return;
			}
			else if (xxx.is_insect(temppawn))
			{
				asshole = Genital_Helper.insect_anus;
			}
			//alien races - ChjDroid, ChjAndroid
			else if (racename.Contains("droid"))
			{
				if (pawn.story.GetBackstory(BackstorySlot.Childhood) != null)
				{
					if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("bishojo"))
						asshole = Genital_Helper.bionic_anus;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("pleasure"))
						asshole = Genital_Helper.bionic_anus;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("idol"))
						asshole = Genital_Helper.bionic_anus;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("social"))
						asshole = Genital_Helper.hydraulic_anus;
					else if (pawn.story.childhood.untranslatedTitleShort.ToLower().Contains("substitute"))
					{
						if (value < 0.05)
							asshole = Genital_Helper.micro_anus;
						else if (value < 0.20)
							asshole = Genital_Helper.tight_anus;
						else if (value < 0.70)
							asshole = Genital_Helper.average_anus;
						else if (value < 0.80)
							asshole = Genital_Helper.loose_anus;
						else if (value < 0.90)
							asshole = Genital_Helper.gaping_anus;
						else if (value < 0.95)
							asshole = Genital_Helper.hydraulic_anus;
						else
							asshole = Genital_Helper.bionic_anus;
					}
					else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
					{
						if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
							asshole = Genital_Helper.bionic_anus;
						else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
							asshole = Genital_Helper.hydraulic_anus;
					}
				}
				else if (pawn.story.GetBackstory(BackstorySlot.Adulthood) != null)
				{
					if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("courtesan"))
						asshole = Genital_Helper.bionic_anus;
					else if (pawn.story.adulthood.untranslatedTitleShort.ToLower().Contains("social"))
						asshole = Genital_Helper.hydraulic_anus;
				}
				if (asshole == Genital_Helper.generic_anus)
					return;
			}
			else if (racename.Contains("slime"))
			{
				asshole = Genital_Helper.slime_anus;
			}
			//animal demons - MoreMonstergirls
			else if (racename.Contains("impmother") || racename.Contains("baphomet") || racename.Contains("demon"))
			{
				asshole = Genital_Helper.demon_anus;
			}
			//humanoid monstergirls - LostForest
			else if (racename.ContainsAny("lf_dragonia", "lf_flammelia", "lf_foxia", "lf_glacia", "lf_lefca", "lf_wolvern"))
			{
				if (value < 0.05)
					asshole = Genital_Helper.micro_anus;
				else if (value < 0.20)
					asshole = Genital_Helper.tight_anus;
				else if (value < 0.90)
					asshole = Genital_Helper.average_anus;
				else
					asshole = Genital_Helper.loose_anus;
			}
			else if (pawn.RaceProps.Humanlike)
			{
				if (pawn.ageTracker.AgeBiologicalYears < 5)
				{
					if (value < 0.05)
						asshole = Genital_Helper.micro_anus;
					else if (value < 0.20)
						asshole = Genital_Helper.tight_anus;
					else if (value < 0.90)
						asshole = Genital_Helper.average_anus;
					else
						asshole = Genital_Helper.loose_anus;
				}
				else
				{
					if (value < 0.05)
						asshole = Genital_Helper.micro_anus;
					else if (value < 0.20)
						asshole = Genital_Helper.tight_anus;
					else if (value < 0.70)
						asshole = Genital_Helper.average_anus;
					else if (value < 0.80)
						asshole = Genital_Helper.loose_anus;
					else if (value < 0.90)
						asshole = Genital_Helper.gaping_anus;
					else if (value < 0.95)
						asshole = Genital_Helper.hydraulic_anus;
					else
						asshole = Genital_Helper.bionic_anus;
				}
			}
			pawn.health.AddHediff(asshole, Part);
		}
	}
}

license: mit


code:
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.Sound;
using JobDriver_Lovin = rjw_CORE_EXPOSED.JobDriver_Lovin;
using Harmony;
using Multiplayer.API;

namespace rjw
{
	public class SexUtility
	{
		private const float no_partner_ability = 0.8f;
		private const float base_sat_per_fuck = 0.40f;
		private const float base_sat_per_quirk = 0.20f;

		//Anal sex
		private static readonly InteractionDef analSex = DefDatabase<InteractionDef>.GetNamed("AnalSex");
		//Vaginal sex
		private static readonly InteractionDef vaginalSex = DefDatabase<InteractionDef>.GetNamed("VaginalSex");
		//Oral sex
		private static readonly InteractionDef rimming = DefDatabase<InteractionDef>.GetNamed("Rimming");
		private static readonly InteractionDef cunnilingus = DefDatabase<InteractionDef>.GetNamed("Cunnilingus");
		private static readonly InteractionDef fellatio = DefDatabase<InteractionDef>.GetNamed("Fellatio");
		private static readonly InteractionDef sixtynine = DefDatabase<InteractionDef>.GetNamed("Sixtynine");
		//Other sex types
		private static readonly InteractionDef doublePenetration = DefDatabase<InteractionDef>.GetNamed("DoublePenetration");
		private static readonly InteractionDef breastjob = DefDatabase<InteractionDef>.GetNamed("Breastjob");
		private static readonly InteractionDef handjob = DefDatabase<InteractionDef>.GetNamed("Handjob");
		private static readonly InteractionDef footjob = DefDatabase<InteractionDef>.GetNamed("Footjob");
		private static readonly InteractionDef beakjob = DefDatabase<InteractionDef>.GetNamed("Beakjob");
		private static readonly InteractionDef fingering = DefDatabase<InteractionDef>.GetNamed("Fingering");
		private static readonly InteractionDef scissoring = DefDatabase<InteractionDef>.GetNamed("Scissoring");
		private static readonly InteractionDef mutualMasturbation = DefDatabase<InteractionDef>.GetNamed("MutualMasturbation");
		private static readonly InteractionDef fisting = DefDatabase<InteractionDef>.GetNamed("Fisting");
		//Rape types
		private static readonly InteractionDef analRape = DefDatabase<InteractionDef>.GetNamed("AnalRape");
		private static readonly InteractionDef vaginalRape = DefDatabase<InteractionDef>.GetNamed("VaginalRape");
		private static readonly InteractionDef otherRape = DefDatabase<InteractionDef>.GetNamed("OtherRape");
		private static readonly InteractionDef handRape = DefDatabase<InteractionDef>.GetNamed("HandjobRape");
		private static readonly InteractionDef fingeringRape = DefDatabase<InteractionDef>.GetNamed("FingeringRape");
		private static readonly InteractionDef violateCorpse = DefDatabase<InteractionDef>.GetNamed("ViolateCorpse");
		private static readonly InteractionDef mechImplant = DefDatabase<InteractionDef>.GetNamed("MechImplant");
		//Breeding
		private static readonly InteractionDef vaginalBreeding = DefDatabase<InteractionDef>.GetNamed("VaginalBreeding");
		private static readonly InteractionDef analBreeding = DefDatabase<InteractionDef>.GetNamed("AnalBreeding");
		private static readonly InteractionDef oralBreeding = DefDatabase<InteractionDef>.GetNamed("OralBreeding");
		private static readonly InteractionDef forcedOralBreeding = DefDatabase<InteractionDef>.GetNamed("ForcedOralBreeding");
		private static readonly InteractionDef forcedFellatioBreeding = DefDatabase<InteractionDef>.GetNamed("ForcedFellatioBreeding");
		private static readonly InteractionDef fingeringBreeding = DefDatabase<InteractionDef>.GetNamed("FingeringBreeding");
		private static readonly InteractionDef requestBreeding = DefDatabase<InteractionDef>.GetNamed("RequestBreeding");
		private static readonly InteractionDef requestAnalBreeding = DefDatabase<InteractionDef>.GetNamed("RequestAnalBreeding");

		public static readonly InteractionDef AnimalSexChat = DefDatabase<InteractionDef>.GetNamed("AnimalSexChat");

		private static readonly ThingDef cum = ThingDef.Named("FilthCum");
		private static readonly ThingDef girlcum = ThingDef.Named("FilthGirlCum");

		private static readonly Dictionary<InteractionDef, xxx.rjwSextype> sexActs = new Dictionary<InteractionDef, xxx.rjwSextype>
		{
			{analSex, xxx.rjwSextype.Anal },
			{analRape, xxx.rjwSextype.Anal },
			{vaginalSex, xxx.rjwSextype.Vaginal },
			{vaginalRape, xxx.rjwSextype.Vaginal },
			{fellatio, xxx.rjwSextype.Oral },
			{rimming, xxx.rjwSextype.Oral },
			{cunnilingus, xxx.rjwSextype.Oral },
			{sixtynine, xxx.rjwSextype.Oral },
			{handjob, xxx.rjwSextype.Handjob },
			{handRape, xxx.rjwSextype.Handjob },
			{breastjob, xxx.rjwSextype.Boobjob },
			{doublePenetration, xxx.rjwSextype.DoublePenetration },
			{footjob, xxx.rjwSextype.Footjob },
			{fingering, xxx.rjwSextype.Fingering },
			{fingeringRape, xxx.rjwSextype.Fingering },
			{fingeringBreeding, xxx.rjwSextype.Fingering },
			{scissoring, xxx.rjwSextype.Scissoring },
			{mutualMasturbation, xxx.rjwSextype.MutualMasturbation },
			{fisting, xxx.rjwSextype.Fisting },
			{vaginalBreeding, xxx.rjwSextype.Vaginal },
			{analBreeding, xxx.rjwSextype.Anal },
			{oralBreeding, xxx.rjwSextype.Oral },
			{forcedOralBreeding, xxx.rjwSextype.Oral },
			{forcedFellatioBreeding, xxx.rjwSextype.Oral },
			{requestBreeding, xxx.rjwSextype.Vaginal },
			{requestAnalBreeding, xxx.rjwSextype.Anal },
			{otherRape, xxx.rjwSextype.Oral },
			{mechImplant, xxx.rjwSextype.MechImplant },
			{violateCorpse, xxx.rjwSextype.None } // Sextype as none, since this cannot result in pregnancy etc.
		};

		// This and the following array are used to generate text from interactions.
		private static readonly InteractionDef[,] dictionarykeys =
		{
			// 1: Interactions_Sex (consensual), 2: Interactions_Rape (forced), 3: Interactions_Breed (animals)
			{vaginalSex, vaginalRape, vaginalBreeding },			// Vaginal
			{vaginalSex, vaginalRape, requestBreeding },			// Vaginal - reseiving
			{analSex, analRape, analBreeding },						// Anal
			{analSex, analRape, requestAnalBreeding },				// Anal - reseiving
			{cunnilingus, otherRape, oralBreeding },				// Cunnilingus
			{cunnilingus, otherRape, forcedOralBreeding },			// Cunnilingus - receiving
			{rimming, otherRape, oralBreeding },					// Rimming
			{rimming, otherRape, forcedOralBreeding },				// Rimming - receiving
			{fellatio, otherRape, oralBreeding },					// Fellatio
			{fellatio, otherRape, forcedOralBreeding },				// Fellatio - receiving
			{doublePenetration, vaginalRape, vaginalBreeding },		// Double penetration
			{doublePenetration, vaginalRape, vaginalBreeding },		// Double penetration - receiving
			{breastjob, otherRape, breastjob },						// Breastjob
			{breastjob, otherRape, breastjob },						// Breastjob - receiving
			{handjob, handRape, handjob },							// Handjob
			{handjob, handRape, handjob },							// Handjob - receiving
			{footjob, otherRape, footjob },							// Footjob
			{footjob, otherRape, footjob },							// Footjob - receiving
			{fingering, fingeringRape, fingeringBreeding },			// Fingering
			{fingering, otherRape, fingeringBreeding },				// Fingering - receiving
			{scissoring, otherRape, scissoring },					// Scissoring
			{scissoring, otherRape, scissoring },					// Scissoring - receiving
			{mutualMasturbation, otherRape, mutualMasturbation },	// Mutual Masturbation
			{mutualMasturbation, otherRape, mutualMasturbation },	// Mutual Masturbation - receiving
			{fisting, analRape, fisting },							// Fisting
			{fisting, analRape, fisting },							// Fisting - receiving
			{sixtynine, otherRape, sixtynine },						// 69
			{sixtynine, otherRape, sixtynine },						// 69 - receiving
		};

		private static readonly string[,] rulepacks =
		{
			// Rulepacks for interaction text output.
			// 1: RulePacks_Sex (consensual), 2: RulePacks_Rape (forced), 3: RulePacks_Breeding (animals)
			{"VaginalSexRP", "VaginalRapeRP", "VaginalBreedingRP"},			// Vaginal
			{"VaginalSexRP", "VaginalDomRP", "VaginalBreedingRP"},			// Vaginal - reseiving
			{"AnalSexRP", "AnalRapeRP", "AnalBreedingRP"},					// Anal
			{"AnalSexRP", "AnalDomRP", "AnalBreedingRP"},					// Anal - reseiving
			{"CunnilingusRP", "VaginalRapeRP", "CunnilingusRP"},			// Cunnilingus
			{"CunnilingusRP", "ForcedCunnilingusRP", "CunnilingusRP"},		// Cunnilingus - receiving
			{"RimmingRP", "VaginalRapeRP", "RimmingRP"},					// Rimming
			{"RimmingRP", "ForcedRimmingRP", "RimmingRP"},					// Rimming - receiving
			{"FellatioRP", "FellatioRapeRP", "FellatioRP"},					// Fellatio
			{"FellatioRP", "ForcedFellatioRP", "FellatioRP"},				// Fellatio - receiving
			{"DoublePenetrationRP", "DoubleRapeRP", "VaginalBreedingRP"},	// Double penetration
			{"DoublePenetrationRP", "OtherRapeRP", "VaginalBreedingRP"},	// Double penetration - receiving
			{"BreastjobRP", "MaleRapeRP", "BreastjobRP"},					// Breastjob
			{"BreastjobRP", "ForcedBreastjobRP", "BreastjobRP"},			// Breastjob - receiving
			{"HandjobRP", "HandjobRapeRP", "HandjobRP"},					// Handjob
			{"HandjobRP", "ForcedHandjobRP", "HandjobRP"},					// Handjob - receiving
			{"FootjobRP", "FootjobRapeRP", "FootjobRP"},					// Footjob
			{"FootjobRP", "ForcedFootjobRP", "FootjobRP"},					// Footjob - receiving
			{"FingeringRP", "FingeringRapeRP", "FingeringRP"},				// Fingering
			{"FingeringRP", "ForcedFingeringRP", "FingeringRP"},			// Fingering - receiving
			{"ScissoringRP", "ScissoringRapeRP", "ScissoringRP"},			// Scissoring
			{"ScissoringRP", "ForcedScissoringRP", "ScissoringRP"},			// Scissoring - receiving
			{"MutualMasturbationRP", null, "MutualMasturbationRP"},			// Mutual Masturbation
			{"MutualMasturbationRP", null, "MutualMasturbationRP"},			// Mutual Masturbation - receiving
			{"FistingRP", "FistingRapeRP", "FistingRP"},					// Fisting
			{"FistingRP", null, "FistingRP"},								// Fisting - receiving
			{"SixtynineRP", "ForcedSixtynineRP", "SixtynineRP"},			// 69
			{"SixtynineRP", null, "SixtynineRP"}							// 69 - receiving
		};

		// Alert checker that is called from several jobs. Checks the pawn relation, and whether it should sound alert.
		// notification in top left corner
		// rape attempt
		public static void RapeTargetAlert(Pawn rapist, Pawn target)
		{
			if (target.IsDesignatedComfort() && rapist.jobs.curDriver.GetType() == typeof(JobDriver_PrisonerComfortRapin))
				if (!RJWPreferenceSettings.ShowForCP)
					return;
			if (target.IsDesignatedComfort() && rapist.jobs.curDriver.GetType() == typeof(JobDriver_Breeding))
				if (target.IsDesignatedBreeding())
					if (!RJWPreferenceSettings.ShowForBreeding)
						return;

			bool silent = false;
			PawnRelationDef relation = rapist.GetMostImportantRelation(target);
			string rapeverb = "rape";

			if (xxx.is_mechanoid(rapist)) rapeverb = "assault";
			else if (xxx.is_animal(rapist) || xxx.is_animal(target)) rapeverb = "breed";

			// TODO: Need to write a cherker method for family relations. Would be useful for other things than just this, such as incest settings/quirk.

			string message = (xxx.get_pawnname(rapist) + " is trying to " + rapeverb + " " + xxx.get_pawnname(target));
			message += relation == null ? "." : (", " + rapist.Possessive() + " " + relation.GetGenderSpecificLabel(target) + ".");

			switch (RJWPreferenceSettings.rape_alert_sound)
			{
				case RJWPreferenceSettings.RapeAlert.Enabled:
					break;
				case RJWPreferenceSettings.RapeAlert.Humanlikes:
					if (!xxx.is_human(target))
						silent = true;
					break;
				case RJWPreferenceSettings.RapeAlert.Colonists:
					if (!target.IsColonist)
						silent = true;
					break;
				default:
					silent = true;
					break;
			}

			if (!silent)
			{
				Messages.Message(message, rapist, MessageTypeDefOf.NegativeEvent);
			}
			else
			{
				Messages.Message(message, rapist, MessageTypeDefOf.SilentInput);
			}
		}

		// Alert checker that is called from several jobs.
		// notification in top left corner
		// rape started
		public static void BeeingRapedAlert(Pawn rapist, Pawn target)
		{
			if (target.IsDesignatedComfort() && rapist.jobs.curDriver.GetType() == typeof(JobDriver_PrisonerComfortRapin))
				if (!RJWPreferenceSettings.ShowForCP)
					return;
			if (target.IsDesignatedComfort() && rapist.jobs.curDriver.GetType() == typeof(JobDriver_Breeding))
				if (target.IsDesignatedBreeding())
					if (!RJWPreferenceSettings.ShowForBreeding)
						return;

			bool silent = false;

			switch (RJWPreferenceSettings.rape_alert)
			{
				case RJWPreferenceSettings.RapeAlert.Enabled:
					break;
				case RJWPreferenceSettings.RapeAlert.Humanlikes:
					if (!xxx.is_human(target))
						silent = true;
					break;
				case RJWPreferenceSettings.RapeAlert.Colonists:
					if (!target.IsColonist)
						silent = true;
					break;
				default:
					silent = true;
					break;
			}

			if (!silent)
			{
				Messages.Message(xxx.get_pawnname(target) + " is getting raped.", target, MessageTypeDefOf.NegativeEvent);
			}
			else
			{
				Messages.Message(xxx.get_pawnname(target) + " is getting raped.", target, MessageTypeDefOf.SilentInput);
			}
		}

		// Quick method that return a body part by name. Used for checking if a pawn has a specific body part, etc.
		public static BodyPartRecord GetPawnBodyPart(Pawn pawn, string bodyPart)
		{
			return pawn.RaceProps.body.AllParts.Find(x => x.def == DefDatabase<BodyPartDef>.GetNamed(bodyPart));
		}

		public static void CumFilthGenerator(Pawn pawn)
		{
			if (pawn.Dead) return;
			if (xxx.is_slime(pawn)) return;
			if (!RJWSettings.cum_filth) return;

			// Larger creatures, larger messes.
			float pawn_cum = Math.Min(80 / ScaleToHumanAge(pawn), 2.0f) * pawn.BodySize;

			// Increased output if the pawn has the Messy quirk.
			if (xxx.has_quirk(pawn, "Messy"))
				pawn_cum *= 2.0f;

			if (Genital_Helper.has_vagina(pawn))
				FilthMaker.MakeFilth(pawn.PositionHeld, pawn.MapHeld, girlcum, pawn.LabelIndefinite(), (int)Math.Max(pawn_cum/2, 1.0f));

			if (Genital_Helper.has_penis(pawn))
				FilthMaker.MakeFilth(pawn.PositionHeld, pawn.MapHeld, cum, pawn.LabelIndefinite(), (int)Math.Max(pawn_cum, 1.0f));
		}

		// The pawn may or may not clean up the mess after fapping.
		[SyncMethod]
		public static bool ConsiderCleaning(Pawn fapper)
		{
			if (!RJWSettings.cum_filth) return false;
			if (!xxx.has_traits(fapper) || fapper.story == null) return false;
			if (fapper.story.WorkTagIsDisabled(WorkTags.Cleaning)) return false;

			float do_cleaning = 0.5f; // 50%

			if (!fapper.PositionHeld.Roofed(fapper.Map))
				do_cleaning -= 0.25f; // Less likely to clean if outdoors.

			if (xxx.CTIsActive && fapper.story.traits.HasTrait(TraitDef.Named("RCT_NeatFreak")))
				do_cleaning += 1.00f;

			if (xxx.has_quirk(fapper, "Messy"))
				do_cleaning -= 0.75f;

			switch (fapper.needs.rest.CurCategory)
			{
				case RestCategory.Exhausted:
					do_cleaning -= 0.5f;
					break;
				case RestCategory.VeryTired:
					do_cleaning -= 0.3f;
					break;
				case RestCategory.Tired:
					do_cleaning -= 0.1f;
					break;
				case RestCategory.Rested:
					do_cleaning += 0.3f;
					break;
			}

			if (fapper.story.traits.DegreeOfTrait(TraitDefOf.NaturalMood) == -2) // Depressive
				do_cleaning -= 0.3f;
			if (fapper.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 2) // Industrious
				do_cleaning += 1.0f;
			else if (fapper.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 1) // Hard worker
				do_cleaning += 0.5f;
			else if (fapper.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == -1) // Lazy
				do_cleaning -= 0.5f;
			else if (fapper.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == -2) // Slothful
				do_cleaning -= 1.0f;

			if (xxx.is_ascetic(fapper))
				do_cleaning += 0.2f;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return Rand.Chance(do_cleaning);
		}

		// Handles after-sex trait and thought gain, and fluid creation. Initiator of the act (whore, rapist, female zoophile, etc) should be first.
		[SyncMethod]
		private static void Aftersex(Pawn pawn, Pawn partner, bool usedCondom = false, bool rape = false, bool isCoreLovin = false, xxx.rjwSextype sextype = xxx.rjwSextype.Vaginal)
		{
			bool bothInMap = false;

			if (!partner.Dead)
				bothInMap = pawn.Map != null && partner.Map != null; //Added by Hoge. false when called this function for despawned pawn: using for background rape like a kidnappee

			//Catch-all timer increase, for ensuring that pawns don't get stuck repeating jobs.

			pawn.rotationTracker.Face(partner.DrawPos);
			pawn.rotationTracker.FaceCell(partner.Position);

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (bothInMap)
			{
				if (!partner.Dead)
				{
					partner.rotationTracker.Face(pawn.DrawPos);
					partner.rotationTracker.FaceCell(pawn.Position);

					if (RJWSettings.sounds_enabled)
					{
						if (rape)
						{
							if (Rand.Value > 0.30f)
								LifeStageUtility.PlayNearestLifestageSound(partner, (ls) => ls.soundAngry, 1.2f);
							else
								LifeStageUtility.PlayNearestLifestageSound(partner, (ls) => ls.soundCall, 1.2f);

							pawn.Drawer.Notify_MeleeAttackOn(partner);
							partner.stances.StaggerFor(Rand.Range(10, 300));
						}
						else
							LifeStageUtility.PlayNearestLifestageSound(partner, (ls) => ls.soundCall);
					}
					if (sextype == xxx.rjwSextype.Vaginal || sextype == xxx.rjwSextype.DoublePenetration)
						if (xxx.is_Virgin(partner))
						{
							//TODO: bind virginity to parts of pawn
							/*
							string thingdef_penis_name = Genital_Helper.get_penis_all(pawn)?.def.defName ?? "";
							ThingDef thingdef_penis = null;

							Log.Message("SexUtility::thingdef_penis_name " + thingdef_penis_name);
							Log.Message("SexUtility::thingdef_penis 1 " + thingdef_penis);

							if (thingdef_penis_name != "")
								thingdef_penis = (from x in DefDatabase<ThingDef>.AllDefs where x.defName == thingdef_penis_name select x).RandomElement();
							Log.Message("SexUtility::thingdef_penis 2 " + thingdef_penis);

							partner.TakeDamage(new DamageInfo(DamageDefOf.Stab, 1, 999, -1.0f, null, xxx.genitals, thingdef_penis));
							*/
						}
				}

				if (RJWSettings.sounds_enabled)
					SoundDef.Named("Cum").PlayOneShot(!partner.Dead
						? new TargetInfo(partner.Position, pawn.Map)
						: new TargetInfo(pawn.Position, pawn.Map));

				if (rape)
				{
					if (Rand.Value > 0.30f)
						LifeStageUtility.PlayNearestLifestageSound(pawn, (ls) => ls.soundAngry, 1.2f);
					else
						LifeStageUtility.PlayNearestLifestageSound(pawn, (ls) => ls.soundCall, 1.2f);
				}
				else
					LifeStageUtility.PlayNearestLifestageSound(pawn, (ls) => ls.soundCall);
			}

			if (!usedCondom)
			{
				CumFilthGenerator(pawn);

				if (bothInMap && !isCoreLovin)
					CumFilthGenerator(partner);

				//apply cum to body:
				SemenHelper.calculateAndApplySemen(pawn, partner, sextype);

				//--Log.Message("SexUtility::aftersex( " + pawn_name + ", " + partner_name + " ) - checking satisfaction");

				if (!pawn.Dead && !partner.Dead)
				{
					PregnancyHelper.impregnate(pawn, partner, sextype);
					//The dead have no hediff, so no need to roll_to_catch; TO DO: add a roll_to_catch_from_corpse to std
					//--Log.Message("SexUtility::aftersex( " + pawn_name + ", " + partner_name + " ) - checking disease");
					if (!(xxx.is_animal(pawn) || xxx.is_animal(partner)))
						std_spreader.roll_to_catch(pawn, partner);
				}
			}
			else
			{
				var newCondom = GenSpawn.Spawn(ThingDef.Named("UsedCondom"), pawn.Position, pawn.Map);
				CondomUtility.useCondom(pawn);
				CondomUtility.useCondom(partner);
			}

			//TODO: below is fucked up, unfuck it someday
			xxx.UpdateRecords(pawn, partner, sextype, rape, isCoreLovin);
			Satisfy(pawn, partner, sextype, rape);

			CheckTraitGain(pawn, partner, rape);
			CheckTraitGain(partner, pawn, rape);
		}

		// Solo acts.
		public static void Aftersex(Pawn pawn, xxx.rjwSextype sextype = xxx.rjwSextype.Masturbation)
		{
			IncreaseTicksToNextLovin(pawn);

			//if (Mod_Settings.sounds_enabled)
			//{
			//	SoundDef.Named("Cum").PlayOneShot(new TargetInfo(pawn.Position, pawn.Map, false));
			//}

			CumFilthGenerator(pawn);

			//apply cum to body:
			SemenHelper.calculateAndApplySemen(pawn, null, sextype);

			//--Log.Message("SexUtility::aftersex( " + pawn_name + ", " + partner_name + " ) - checking satisfaction");
			xxx.UpdateRecords(pawn, null, sextype);
			Satisfy(pawn, null, sextype);

			// No traits from solo. Enable if some are edded. (Voyerism?)
			//check_trait_gain(pawn);
		}

		private static void Satisfy(Pawn pawn, Pawn partner, xxx.rjwSextype sextype, bool rape = false)
		{
			//--Log.Message("xxx::satisfy( " + pawn_name + ", " + partner_name + ", " + violent + "," + isCoreLovin + " ) called");

			if (pawn == null && partner == null) return; // Unlikely, but checking this here makes other checks simpler.

			float pawn_ability = pawn != null ? Math.Max(xxx.get_sex_ability(pawn), 0.3f) : no_partner_ability;
			float partner_ability = (partner != null && !partner.Dead) ? Math.Max(xxx.get_sex_ability(partner), 0.3f) : no_partner_ability;
			/*
			if(sextype == xxx.rjwSextype.Anal)
			{
				pawn_ability = pawn != null ? pawn.health.hediffSet.GetFirstHediffOfDef(Genital_Helper.average_anus).CurStage.statOffsets.FindLast(DefDatabase<StatDef>.GetNamed("SexAbility")).value : pawn_ability;
				partner_ability = (partner != null && !partner.Dead) ? Math.Max(xxx.get_sex_ability(partner), 0.3f) : partner_ability;
			}
			*/

			//--Log.Message("xxx::satisfy( " + pawn_name + ", " + partner_name + ", " + violent + "," + isCoreLovin + " ) - calculate base satisfaction");
			// Base satisfaction is based on partner's ability
			float pawn_satisfaction = base_sat_per_fuck * partner_ability;
			float partner_satisfaction = base_sat_per_fuck * pawn_ability;

			// Measure by own ability instead if masturbating.
			// Low pawn_ability usually means things like impaired manipulation, which should make masturbation less satisfactory.
			if (sextype == xxx.rjwSextype.Masturbation)
				pawn_satisfaction = base_sat_per_fuck * pawn_ability;

			if (pawn != null && partner != null)
			{
				if (xxx.is_animal(partner) && xxx.is_zoophile(pawn))
				{
					pawn_satisfaction *= 1.5f;
				}
				if (partner.Dead && xxx.is_necrophiliac(pawn))
				{
					pawn_satisfaction *= 1.5f;
				}

				if (!partner.Dead)
				{
					xxx.TransferNutrition(pawn, partner, sextype);
					if (rape)
					{
						//--Log.Message("SexUtility::aftersex( " + pawn_name + ", " + partner_name + " ) - Broken mind updates for rape");
						xxx.processBrokenPawn(partner);
						xxx.ExtraSatisfyForBrokenPawn(partner);
					}
				}
			}

			SatisfyPersonal(pawn, partner, sextype, rape, pawn_satisfaction);
			SatisfyPersonal(partner, pawn, sextype, rape, partner_satisfaction);
			//--Log.Message("xxx::satisfy( " + pawn_name + ", " + partner_name + ", " + rape + " ) - pawn_satisfaction = " + pawn_satisfaction + ", partner_satisfaction = " + partner_satisfaction);
		}

		// Scales alien lifespan to human age.
		// Some aliens have broken lifespans, that can be manually corrected here.
		public static int ScaleToHumanAge(Pawn pawn, int humanLifespan = 80)
		{
			float pawnAge = pawn.ageTracker.AgeBiologicalYearsFloat;
			float pawnLifespan = pawn.RaceProps.lifeExpectancy;

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

			// Xen races, all broken and need a fix.
			if (pawn.def.defName.ContainsAny("Alien_Sergal", "Alien_SergalNME", "Alien_Xenn", "Alien_Racc", "Alien_Ferrex", "Alien_Wolvx", "Alien_Frijjid", "Alien_Fennex") && pawnLifespan >= 2000f)
			{
				pawnAge = Math.Min(pawnAge, 80f); // Clamp to 80.
				pawnLifespan = 80f;
			}
			if (pawn.def.defName.ContainsAny("Alien_Gnoll", "Alien_StripedGnoll") && pawnLifespan >= 2000f)
			{
				pawnAge = Math.Min(pawnAge, 60f); // Clamp to 60.
				pawnLifespan = 60f; // Mature faster than humans.
			}

			// Immortal races that mature at similar rate to humans.
			if (pawn.def.defName.ContainsAny("LF_Dragonia", "LotRE_ElfStandardRace", "Alien_Crystalloid", "Alien_CrystalValkyrie"))
			{
				pawnAge = Math.Min(pawnAge, 40f); // Clamp to 40 - never grow 'old'.
				pawnLifespan = 80f;
			}

			float age_scaling = humanLifespan / pawnLifespan;
			float scaled_age = pawnAge * age_scaling;

			if (scaled_age < 1)
				scaled_age = 1;

			return (int)scaled_age;
		}

		// Used in complex impregnation calculation. Pawns/animals with similar parts have better compatibility.
		public static float BodySimilarity(Pawn pawn, Pawn partner)
		{
			float size_adjustment = Mathf.Lerp(0.3f, 1.05f, 1.0f - Math.Abs(pawn.BodySize - partner.BodySize));

			//Log.Message("[RJW] Size adjustment: " + size_adjustment);

			List<BodyPartDef> pawn_partlist = new List<BodyPartDef> { };
			List<BodyPartDef> pawn_mismatched = new List<BodyPartDef> { };
			List<BodyPartDef> partner_mismatched = new List<BodyPartDef> { };

			//Log.Message("[RJW]Checking compatibility for " + xxx.get_pawnname(pawn) + " and " + xxx.get_pawnname(partner));
			bool pawnHasHands = pawn.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand));

			foreach (BodyPartRecord part in pawn.RaceProps.body.AllParts)
			{
				pawn_partlist.Add(part.def);
			}
			float pawn_count = pawn_partlist.Count();

			foreach (BodyPartRecord part in partner.RaceProps.body.AllParts)
			{
				partner_mismatched.Add(part.def);
			}
			float partner_count = partner_mismatched.Count();

			foreach (BodyPartDef part in pawn_partlist)
			{
				if (partner_mismatched.Contains(part))
				{
					pawn_mismatched.Add(part);
					partner_mismatched.Remove(part);
				}
			}

			float pawn_mismatch = pawn_mismatched.Count() / pawn_count;
			float partner_mismatch = partner_mismatched.Count() / partner_count;

			//Log.Message("[RJW]Body type similarity for " + xxx.get_pawnname(pawn) + " and " + xxx.get_pawnname(partner) + ": " + Math.Round(((pawn_mismatch + partner_mismatch) * 50) * size_adjustment, 1) + "%");

			return ((pawn_mismatch + partner_mismatch) / 2) * size_adjustment;
		}

		private static void SatisfyPersonal(Pawn pawn, Pawn partner, xxx.rjwSextype sextype, bool violent, float satisfaction)
		{
			//--Log.Message("xxx::satisfy( " + pawn_name + ", " + partner_name + ", " + violent + "," + isCoreLovin + " ) - modifying partner satisfaction");
			if (pawn?.needs?.TryGetNeed<Need_Sex>() == null) return;

			var quirkCount = Quirk.CountSatisfiedQuirks(pawn, partner, sextype, violent);
			satisfaction += quirkCount * base_sat_per_quirk;

			if (xxx.is_rapist(pawn) || xxx.is_bloodlust(pawn))
			{
				// Rapists and Bloodlusts get more satisfaction from violetn encounters, and less from non-violent
				satisfaction *= violent ? 1.20f : 0.8f;
			}
			else
			{
				// non-violent pawns get less satisfaction from violent encounters, and full from non-violent
				satisfaction *= violent ? 0.8f : 1.0f;
			}

			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomAddiction")) && !pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomEffect")))
			{
				//Log.Message("[RJW]xxx::satisfy 0 pawn is " + xxx.get_pawnname(pawn));
				satisfaction = 0.1f;
			}

			//--Log.Message("xxx::satisfy( " + pawn_name + ", " + partner_name + ", " + violent + "," + isCoreLovin + " ) - setting pawn joy");
			pawn.needs.TryGetNeed<Need_Sex>().CurLevel += satisfaction;

			if (pawn.needs.joy != null)
			{
				pawn.needs.joy.CurLevel += satisfaction * 0.50f;   // convert half of satisfaction to joy
			}

			if (quirkCount > 0)
			{
				Quirk.AddThought(pawn);
			}
		}

		[SyncMethod]
		private static void CheckTraitGain(Pawn pawn, Pawn partner, bool rape = false)
		{
			if (!xxx.has_traits(pawn) || pawn.records.GetValue(xxx.CountOfSex) <= 10) return;

			if (RJWSettings.AddTrait_Necrophiliac && !xxx.is_necrophiliac(pawn) && partner.Dead && pawn.records.GetValue(xxx.CountOfSexWithCorpse) > 0.5 * pawn.records.GetValue(xxx.CountOfSex))
			{
				pawn.story.traits.GainTrait(new Trait(xxx.necrophiliac));
				//Log.Message(xxx.get_pawnname(necro) + " aftersex, not necro, adding necro trait");
			}
			if (RJWSettings.AddTrait_Rapist && !xxx.is_rapist(pawn) && !xxx.is_masochist(pawn) && rape && pawn.records.GetValue(xxx.CountOfRapedHumanlikes) > 0.12 * pawn.records.GetValue(xxx.CountOfSex))
			{
				var chance = 0.5f;
				if (xxx.is_kind(pawn)) chance -= 0.25f;
				if (xxx.is_prude(pawn)) chance -= 0.25f;
				if (xxx.is_zoophile(pawn)) chance -= 0.25f; // Less interested in raping humanlikes.
				if (xxx.is_ascetic(pawn)) chance -= 0.2f;
				if (xxx.is_bloodlust(pawn)) chance += 0.2f;
				if (xxx.is_psychopath(pawn)) chance += 0.25f;

				//Rand.PopState();
				//Rand.PushState(RJW_Multiplayer.PredictableSeed());
				if (Rand.Chance(chance))
				{
					pawn.story.traits.GainTrait(new Trait(xxx.rapist));
					//--Log.Message(xxx.get_pawnname(pawn) + " aftersex, not rapist, adding rapist trait");
				}
			}
			if (RJWSettings.AddTrait_Zoophiliac && !xxx.is_zoophile(pawn) && xxx.is_animal(partner) && (pawn.records.GetValue(xxx.CountOfSexWithAnimals) + pawn.records.GetValue(xxx.CountOfSexWithInsects) > 0.5 * pawn.records.GetValue(xxx.CountOfSex)))
			{
				pawn.story.traits.GainTrait(new Trait(xxx.zoophile));
				pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(xxx.got_bred);
				pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(xxx.got_groped);
				pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(xxx.got_licked);
				//--Log.Message(xxx.get_pawnname(pawn) + " aftersex, not zoo, adding zoo trait");
			}
			if (RJWSettings.AddTrait_Nymphomaniac && !xxx.is_nympho(pawn))
			{
				if (pawn.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomAddiction")))
				{
					pawn.story.traits.GainTrait(new Trait(xxx.nymphomaniac));
					//Log.Message(xxx.get_pawnname(pawn) + " is HumpShroomAddicted, not nymphomaniac, adding nymphomaniac trait");
				}

			}
		}

		// Checks if enough time has passed from previous lovin'.
		public static bool ReadyForLovin(Pawn pawn)
		{
			return Find.TickManager.TicksGame > pawn.mindState.canLovinTick;
		}

		// Checks if enough time has passed from previous search for a hookup.
		// Checks if hookups allowed during working hours, exlcuding nymphs
		public static bool ReadyForHookup(Pawn pawn)
		{
			if (!xxx.is_nympho(pawn) && RJWHookupSettings.NoHookupsDuringWorkHours && ((pawn.timetable != null) ? pawn.timetable.CurrentAssignment : TimeAssignmentDefOf.Anything) == TimeAssignmentDefOf.Work) return false;
			return Find.TickManager.TicksGame > CompRJW.Comp(pawn).NextHookupTick;
		}

		private static void IncreaseTicksToNextLovin(Pawn pawn)
		{
			if (pawn == null || pawn.Dead) return;
			int currentTime = Find.TickManager.TicksGame;
			if (pawn.mindState.canLovinTick <= currentTime)
				pawn.mindState.canLovinTick = currentTime + GenerateMinTicksToNextLovin(pawn);
		}

		[SyncMethod]
		public static int GenerateMinTicksToNextLovin(Pawn pawn)
		{
			if (DebugSettings.alwaysDoLovin) return 100;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());

			float interval = JobDriver_Lovin.LovinIntervalHoursFromAgeCurve.Evaluate(ScaleToHumanAge(pawn));
			float rinterval = Math.Max(0.5f, Rand.Gaussian(interval, 0.3f));

			float tick = 1.0f;

			// Nymphs automatically get the tick increase from the trait influence on sex drive.

			if (xxx.is_animal(pawn))
				tick = 0.75f;
			else if (xxx.is_prude(pawn))
				tick = 1.5f;

			if (pawn.Has(Quirk.Vigorous))
				tick *= 0.8f;

			float sex_drive = xxx.get_sex_drive(pawn);
			if (sex_drive <= 0.05f)
				sex_drive = 0.05f;

			return (int)(tick * rinterval * (2500.0f / sex_drive));
		}

		public static void IncreaseTicksToNextHookup(Pawn pawn)
		{
			if (pawn == null || pawn.Dead)
				return;

			// There are 2500 ticks per rimworld hour.  Sleeping an hour between checks seems like a good start.
			// We could get fancier and weight it by sex drive and stuff, but would people even notice?
			const int TicksBetweenHookups = 2500;

			int currentTime = Find.TickManager.TicksGame;
			CompRJW.Comp(pawn).NextHookupTick = currentTime + TicksBetweenHookups;
		}

		//
		// Determines the sex type and handles the log output.
		// "Pawn" should be initiator of the act (rapist, whore, etc), "Partner" should be the target.
		//
		public static void ProcessSex(Pawn pawn, Pawn partner, bool usedCondom = false, bool rape = false, bool isCoreLovin = false, bool whoring = false)
		{
			Log.Message("usedCondom=" + usedCondom);
			if (pawn == null || partner == null)
			{
				if (pawn == null)
					Log.Error("[RJW][SexUtility] ERROR: pawn is null.");
				if (partner == null)
					Log.Error("[RJW][SexUtility] ERROR: partner is null.");
				return;
			}

			// Re-draw clothes.
			if (xxx.is_human(pawn))
				pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
			if (xxx.is_human(partner))
				partner.Drawer.renderer.graphics.ResolveApparelGraphics();

			IncreaseTicksToNextLovin(pawn);
			IncreaseTicksToNextLovin(partner);

			rape = rape || pawn?.CurJob.def == xxx.gettin_raped || (!partner.Dead && partner.CurJob.def == xxx.gettin_raped); // Double-checking.

			Pawn receiving = partner;
			xxx.rjwSextype sextype = DetermineSextype(pawn, partner, rape, whoring, ref receiving);
			Aftersex(pawn, partner, usedCondom, rape, isCoreLovin, sextype);

			//--Log.Message("SexUtility::processsex( " + pawn_name + ", " + partner_name + " ) - checking thoughts");
			xxx.think_about_sex(pawn, partner, receiving == pawn, rape, sextype, isCoreLovin, whoring);
			xxx.think_about_sex(partner, pawn, receiving == partner, rape, sextype, isCoreLovin, false);

			pawn.Drawer.Notify_MeleeAttackOn(partner);
		}

		[SyncMethod]
		private static xxx.rjwSextype DetermineSextype(Pawn pawn, Pawn partner, bool rape, bool whoring, ref Pawn receiving)
		{
			//--Log.Message("[RJW]SexUtility::processSex is called for pawn" + xxx.get_pawnname(pawn) + " and partner " + xxx.get_pawnname(partner));
			bool pawnHasMouth = Genital_Helper.has_mouth(pawn) && !Genital_Helper.oral_blocked(pawn);
			bool pawnHasAnus = Genital_Helper.has_anus(pawn) && !Genital_Helper.anus_blocked(pawn);
			bool pawnHasBreasts = Genital_Helper.has_breasts(pawn) && !Genital_Helper.breasts_blocked(pawn);
			bool pawnHasVagina = Genital_Helper.has_vagina(pawn) && !Genital_Helper.vagina_blocked(pawn);
			bool pawnHasPenis = (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || Genital_Helper.has_ovipositorF(pawn)) && !Genital_Helper.penis_blocked(pawn);
			bool pawnHasMultiPenis = Genital_Helper.has_multipenis(pawn) && !Genital_Helper.penis_blocked(pawn);
			//--Log.Message("[RJW]SexUtility::processSex is pawnHasPenis " + pawnHasPenis);
			//--Log.Message("[RJW]SexUtility::processSex is is_female(Partner) " + is_female(Partner));
			bool partnerHasMouth = Genital_Helper.has_mouth(partner) && !Genital_Helper.oral_blocked(partner);
			bool partnerHasAnus = Genital_Helper.has_anus(partner) && !Genital_Helper.anus_blocked(partner);
			bool partnerHasBreasts = Genital_Helper.has_breasts(partner) && !Genital_Helper.breasts_blocked(partner);
			bool partnerHasVagina = Genital_Helper.has_vagina(partner) && !Genital_Helper.vagina_blocked(partner);
			bool partnerHasPenis = (Genital_Helper.has_penis(partner) || Genital_Helper.has_penis_infertile(pawn)) && !Genital_Helper.penis_blocked(partner);
			bool partnerHasMultiPenis = Genital_Helper.has_multipenis(partner) && !Genital_Helper.penis_blocked(partner);

			bool pawnHasHands = pawn.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand)) && !Genital_Helper.hands_blocked(pawn);
			bool partnerHasHands = partner.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand)) && !Genital_Helper.hands_blocked(partner);

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());

			/*Things to keep in mind:
			 - Both the initiator and the partner can be female, male, or futa.
			 - Can be rape or consensual.
			 - Includes pawns with blocked or no genitalia.

			 Need to add support here when new types get added.
			 Types to be added: 69, spooning...?

			 This would be much 'better' code as arrays, but that'd hurt readability and make it harder to modify.
			 If this weren't 3.5, I'd use tuples.*/

			// Range 1.0 to 0.0 [100% to 0%].
			float vagiIni = RJWPreferenceSettings.vaginal;				// Vaginal
			float VagiRec = RJWPreferenceSettings.vaginal;				// Vaginal - receiving
			float analIni = RJWPreferenceSettings.anal;					// Anal
			float analRec = RJWPreferenceSettings.anal;					// Anal - receiving
			float cunnIni = RJWPreferenceSettings.cunnilingus;		 	// Cunnilingus
			float cunnRec = RJWPreferenceSettings.cunnilingus;		 	// Cunnilingus - receiving
			float rimmIni = RJWPreferenceSettings.rimming;				// Rimming
			float rimmRec = RJWPreferenceSettings.rimming;				// Rimming - receiving
			float fellIni = RJWPreferenceSettings.fellatio;				// Fellatio
			float fellRec = RJWPreferenceSettings.fellatio;				// Fellatio - receiving
			float doubIni = RJWPreferenceSettings.double_penetration;	// DoublePenetration
			float doubRec = RJWPreferenceSettings.double_penetration;	// DoublePenetration - receiving
			float bresIni = RJWPreferenceSettings.breastjob;			// Breastjob
			float bresRec = RJWPreferenceSettings.breastjob;			// Breastjob - receiving
			float handIni = RJWPreferenceSettings.handjob;				// Handjob
			float handRec = RJWPreferenceSettings.handjob;				// Handjob - receiving
			float footIni = RJWPreferenceSettings.footjob;				// Footjob
			float footRec = RJWPreferenceSettings.footjob;				// Footjob - receiving
			float fingIni = RJWPreferenceSettings.fingering;			// Fingering
			float fingRec = RJWPreferenceSettings.fingering;			// Fingering - receiving
			float scisIni = RJWPreferenceSettings.scissoring;			// Scissoring
			float scisRec = RJWPreferenceSettings.scissoring;			// Scissoring - receiving
			float mutuIni = RJWPreferenceSettings.mutual_masturbation;	// MutualMasturbation
			float mutuRec = RJWPreferenceSettings.mutual_masturbation;	// MutualMasturbation - receiving
			float fistIni = RJWPreferenceSettings.fisting;				// Fisting
			float fistRec = RJWPreferenceSettings.fisting;				// Fisting - receiving
			float sixtIni = RJWPreferenceSettings.sixtynine;			// 69
			float sixtRec = RJWPreferenceSettings.sixtynine;			// 69 - receiving

			string pawn_quirks = CompRJW.Comp(pawn).quirks.ToString();
			string partner_quirks = CompRJW.Comp(partner).quirks.ToString();

			// Modifiers > 1.0f = higher chance of being picked
			// Modifiers < 1.0f = lower chance of being picked
			// 0 = disables types.

			// Pawn does not need sex, or is not horny. Mostly whores, sexbots, etc.
			if (xxx.need_some_sex(pawn) < 1.0f)
			{
				vagiIni *= 0.6f;
				analIni *= 0.6f;
				cunnRec *= 0.6f;
				rimmRec *= 0.6f;
				fellRec *= 0.6f;
				doubIni *= 0.6f;
				bresRec *= 0.6f;
				handRec *= 0.6f;
				footRec *= 0.6f;
				fingRec *= 0.6f;
				sixtIni *= 0.6f;
				sixtRec *= 0.6f;
			}

			// Adjusts initial chances
			if (pawnHasPenis)
			{
				vagiIni *= 1.5f;
				analIni *= 1.5f;
				fellRec *= 1.5f;
				doubIni *= 1.5f;
				if (partnerHasVagina)
				{
					fistRec *= 0.5f;
					rimmIni *= 0.8f;
					rimmRec *= 0.5f;
				}
			}
			else if (pawnHasVagina)
			{
				VagiRec *= 1.2f;
				scisRec *= 1.2f;
			}

			//Size adjustments. Makes pawns reluctant to have penetrative sex if there's large size difference.
			if (partner.BodySize > pawn.BodySize * 2 && !rape && !xxx.is_animal(pawn))
			{
				VagiRec *= 0.6f;
				analRec *= 0.6f;
				fistRec *= 0.2f;
				sixtIni *= 0.2f;
				sixtRec *= 0.2f;
			}
			else if (pawn.BodySize > partner.BodySize * 2 && !rape && !xxx.is_animal(pawn) && !xxx.is_psychopath(pawn))
			{
				vagiIni *= 0.6f;
				analIni *= 0.6f;
				fistIni *= 0.3f;
				sixtIni *= 0.2f;
				sixtRec *= 0.2f;
			}

			if (partner.Dead || partner.Downed || !partner.health.capacities.CanBeAwake) // This limits options a lot, for obvious reason.
			{
				VagiRec = 0f;
				analRec = 0f;
				cunnIni *= 0.3f;
				cunnRec = 0f;
				rimmIni *= 0.1f;
				rimmRec = 0f;
				fellRec *= 0.2f;
				doubRec = 0f;
				bresRec = 0f;
				handRec = 0f;
				footRec = 0f;
				fingRec = 0f;
				fingIni *= 0.5f;
				scisIni *= 0.2f;
				scisRec = 0f;
				mutuIni = 0f;
				mutuRec = 0f;
				fistRec = 0f;
				sixtRec = 0f;
				if (pawnHasPenis)
				{
					sixtIni *= 0.5f; // Can facefuck the unconscious (or corpse). :/
				}
				else
				{
					sixtIni = 0f;
				}
				if (partner.Dead)
				{
					fellIni = 0f;
					handIni = 0f;
					footIni = 0f;
					bresIni = 0f;
					fingIni = 0f;
					fistIni *= 0.2f; // Fisting a corpse? Whatever floats your boat, I guess.
				}
				else
				{
					fellIni *= 0.4f;
					handIni *= 0.5f;
					footIni *= 0.2f;
					bresIni *= 0.2f;
					fistIni *= 0.6f;
				}
			}

			if (rape)
			{
				// This makes most types less likely to happen during rape, but doesn't disable them.
				// Things like forced blowjob can happen, so it shouldn't be impossible in rjw.
				VagiRec *= 0.5f; //Forcing vaginal on male.
				analRec *= 0.3f; //Forcing anal on male.
				cunnIni *= 0.3f; //Forced cunnilingus.
				cunnRec *= 0.6f;
				rimmIni *= 0.1f;
				fellIni *= 0.4f;
				doubRec *= 0.2f; //Rapist forcing the target to double-penetrate her - unlikely.
				bresIni *= 0.2f;
				bresRec *= 0.2f;
				handIni *= 0.6f;
				handRec *= 0.2f;
				footIni *= 0.2f;
				footRec *= 0.1f;
				fingIni *= 0.8f;
				fingRec *= 0.1f;
				scisIni *= 0.6f;
				scisRec *= 0.1f;
				mutuIni = 0f;
				mutuRec = 0f;
				fistIni *= 1.2f;
				fistRec = 0f;
				sixtIni *= 0.5f;
				sixtRec = 0f;
			}

			if (xxx.is_animal(pawn))
			{
				if (pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, partner))
				{   //Bond animals
					VagiRec *= 1.8f; //Presenting
					analRec *= 1.2f;
					fellIni *= 1.2f;
					cunnIni *= 1.2f;
				}
				else
				{
					VagiRec *= 0.3f;
					analRec *= 0.3f;
				}
				vagiIni *= 1.8f;
				analIni *= 0.9f;
				cunnRec *= 0.2f;
				rimmRec *= 0.1f;
				fellRec *= 0.1f;
				doubIni *= 0.6f;
				doubRec *= 0.1f;
				bresIni = 0f;
				bresRec *= 0.1f;
				handIni *= 0.4f; //Enabled for primates.
				handRec *= 0.1f;
				footIni = 0f;
				footRec *= 0.1f;
				fingIni *= 0.3f; //Enabled for primates.
				fingRec *= 0.2f;
				scisIni *= 0.2f;
				scisRec *= 0.1f;
				mutuIni *= 0.1f;
				mutuRec *= 0.1f;
				fistIni *= 0.2f; //Enabled for primates...
				fistRec *= 0.6f;
				sixtIni *= 0.2f;
				sixtRec *= 0.2f;
			}

			if (xxx.is_animal(partner)) // Zoophilia and animal-on-animal
			{
				if (pawn.Faction != partner.Faction && rape) // Wild animals && animals from other factions
				{
					cunnRec *= 0.1f; // Wild animals bite, colonists should be smart enough to not try to force oral from them.
					rimmRec *= 0.1f;
					fellRec *= 0.1f;
				}
				else
				{
					cunnRec *= 0.5f;
					rimmRec *= 0.4f;
					fellRec *= 0.4f;
				}
				cunnIni *= 0.7f;
				rimmIni *= 0.1f;
				fellIni *= 1.2f;
				doubIni *= 0.6f;
				doubRec *= 0.1f;
				bresIni *= 0.3f; //Giving a breastjob to animals - unlikely.
				bresRec = 0f;
				handIni *= 1.2f;
				handRec *= 0.4f; //Animals are not known for giving handjobs, but enabled for primates and such.
				footIni *= 0.3f;
				footRec = 0f;
				fingIni *= 0.8f;
				fingRec *= 0.2f; //Enabled for primates.
				scisIni *= 0.1f;
				scisRec = 0f;
				mutuIni *= 0.6f;
				mutuRec *= 0.1f;
				fistIni *= 0.6f;
				fistRec *= 0.1f;
				sixtIni *= 0.2f;
				sixtRec *= 0.2f;
			}

			//Quirks
			if (pawn_quirks.Contains("Podophile")) // Foot fetish
			{
				footIni *= 2.0f;
				footRec *= 2.5f;
			}
			if (partner_quirks.Contains("Podophile"))
			{
				footIni *= 2.5f;
				footRec *= 2.0f;
			}
			if (pawn_quirks.Contains("Impregnation fetish") && (PregnancyHelper.CanImpregnate(pawn, partner) || PregnancyHelper.CanImpregnate(partner, pawn)))
			{
				vagiIni *= 2.5f;
				VagiRec *= 2.5f;
			}

			if (whoring) // Paid sex
			{
				VagiRec *= 1.5f;
				analIni *= 0.7f; //Some customers may pay for this.
				analRec *= 1.2f;
				cunnIni *= 1.2f;
				cunnRec *= 0.3f; //Customer paying to lick the whore - uncommon.
				rimmRec *= 0.2f;
				fellIni *= 1.5f; //Classic.
				fellRec *= 0.2f;
				doubIni *= 0.8f;
				doubRec *= 1.2f;
				bresIni *= 1.2f;
				bresRec *= 0.1f;
				handIni *= 1.5f;
				handRec *= 0.1f;
				footIni *= 0.6f;
				footRec *= 0.1f;
				fingIni *= 0.6f;
				fingRec *= 0.2f;
				scisRec *= 0.2f;
				mutuIni *= 0.2f;
				mutuRec *= 0.2f;
				fistIni *= 0.6f;
				fistRec *= 0.7f;
				sixtIni *= 0.7f;
				sixtRec *= 0.7f;
			}

			// Pawn lacks vagina, disable related types.
			if (!pawnHasVagina)
			{
				VagiRec = 0f;
				cunnRec = 0f;
				doubRec = 0f;
				fingRec = 0f;
				scisIni = 0f;
				scisRec = 0f;
			}
			if (!partnerHasVagina)
			{
				vagiIni = 0f;
				cunnIni = 0f;
				doubIni = 0f;
				fingIni = 0f;
				scisIni = 0f;
				scisRec = 0f;
			}

			// Pawn lacks penis, disable related types.
			if (!pawnHasPenis)
			{
				vagiIni = 0f;
				analIni = 0f;
				fellRec = 0f;
				doubIni = 0f;
				bresRec = 0f;
				handRec = 0f;
				footRec = 0f;
			}
			else if (pawnHasMultiPenis && partnerHasVagina && partnerHasAnus)
			{
				// Pawn has multi-penis and can use it. Single-penetration chance down.
				vagiIni *= 0.8f;
				analIni *= 0.8f;
				doubIni *= 1.5f;
			}
			else
			{
				doubIni = 0f;
			}

			if (!partnerHasPenis)
			{
				VagiRec = 0f;
				analRec = 0f;
				fellIni = 0f;
				doubRec = 0f;
				bresIni = 0f;
				handIni = 0f;
				footIni = 0f;
			}
			else if (partnerHasMultiPenis && pawnHasVagina && pawnHasAnus)
			{
				// Pawn has multi-penis and can use it. Single-penetration chance down.
				VagiRec *= 0.8f;
				analRec *= 0.8f;
				doubRec *= 1.5f;
			}
			else
			{
				doubRec = 0f;
			}

			// One pawn lacks genitalia: no mutual masturbation or 69.
			if (!(pawnHasPenis || pawnHasVagina) || !(partnerHasPenis || partnerHasVagina))
			{
				mutuIni = 0f;
				mutuRec = 0f;
				sixtIni = 0f;
				sixtRec = 0f;
			}

			// Pawn lacks anus...
			if (!pawnHasAnus)
			{
				analRec = 0f;
				rimmRec = 0f;
				doubRec = 0f;
				fistRec = 0f;
			}
			if (!partnerHasAnus)
			{
				analIni = 0f;
				rimmIni = 0f;
				doubIni = 0f;
				fistIni = 0f;
			}

			// Pawn lacks boobs
			if (!pawnHasBreasts)
			{
				bresIni = 0f;
			}
			if (!partnerHasBreasts)
			{
				bresRec = 0f;
			}

			// Pawn lacks hands
			if (!pawnHasHands)
			{
				handIni = 0f;
				fingIni = 0f;
				mutuIni = 0f;
				fistIni = 0f;
			}
			if (!partnerHasHands)
			{
				handRec = 0f;
				fingRec = 0f;
				mutuRec = 0f;
				fistRec = 0f;
			}

			// Pawn lacks mouth
			if (!pawnHasMouth)
			{
				cunnIni = 0f;
				rimmIni = 0f;
				fellIni = 0f;
				sixtIni = 0f;
			}
			if (!partnerHasMouth)
			{
				cunnIni = 0f;
				rimmIni = 0f;
				fellIni = 0f;
				sixtIni = 0f;
			}

			List<float> sexTypes = new List<float> {
				vagiIni, VagiRec,	//  0,  1
				analIni, analRec,	//  2,  3
				cunnIni, cunnRec,	//  4,  5
				rimmIni, rimmRec,	//  6,  7
				fellIni, fellRec,	//  8,  9
				doubIni, doubRec,	// 10, 11
				bresIni, bresRec,	// 12, 13
				handIni, handRec,	// 14, 15
				footIni, footRec,	// 16, 17
				fingIni, fingRec,	// 18, 19
				scisIni, scisRec,	// 20, 21
				mutuIni, mutuRec,	// 22, 23
				fistIni, fistRec,	// 24, 25
				sixtIni, sixtRec	// 26, 27
			};

			// Bit of randomization..
			for (int i = 0; i < sexTypes.Count; i++)
			{
				sexTypes[i] = Rand.Range(0f, sexTypes[i]);
			}

			float maxValue = sexTypes.Max();

			if (!(maxValue > 0f))
			{
				Log.Error("[RJW] ERROR: No available sex types for " + xxx.get_pawnname(pawn) + " and " + xxx.get_pawnname(partner));
				return xxx.rjwSextype.None;
			}

			List<RulePackDef> extraSentencePacks = new List<RulePackDef>();

			string rulepack; //defName of the rulePackDef (see RulePacks_Sex.xml, etc.)
			InteractionDef dictionaryKey;

			Pawn giving = pawn;

			if (sexTypes.IndexOf(maxValue) % 2 != 0 && !rape)
			{
				giving = partner;
				receiving = pawn;
			}

			int typeIndex = sexTypes.IndexOf(maxValue);

			if (xxx.is_animal(pawn))
			{
				rulepack = rulepacks[typeIndex, 2];
				dictionaryKey = dictionarykeys[typeIndex, 2];
			}
			else if (!rape)
			{
				rulepack = rulepacks[typeIndex, 0];
				dictionaryKey = dictionarykeys[typeIndex, 0];
			}
			else // is rape
			{
				rulepack = rulepacks[typeIndex, 1];
				dictionaryKey = dictionarykeys[typeIndex, 1];
			}

			// Override for mechanoid implanting. Add the defNames for species that should be allowed to do it.
			// Currently only includes the core mechanoids, plus some from Orion's More Mechanoids mod.
			if (pawn.kindDef.race.defName.ContainsAny("Mech_Centipede", "Mech_Lancer", "Mech_Scyther", "Mech_Crawler", "Mech_Skullywag", "Mech_Flamebot", "Mech_Mammoth", "Mech_Assaulter"))
			{
				dictionaryKey = mechImplant;
				rulepack = "MechImplantingRP";
			}

			if (rulepack == "FellatioRP")
			{
				if (GetPawnBodyPart(pawn, "Beak") != null && pawn == giving || GetPawnBodyPart(partner, "Beak") != null && pawn == receiving)
				{
					rulepack = "BeakjobRP";
					dictionaryKey = beakjob;
				}
			}

			// Text override for corpse violation.
			if (pawn.CurJob.def == xxx.violate_corpse)
				dictionaryKey = violateCorpse;

			xxx.rjwSextype sextype = sexActs[dictionaryKey];
			extraSentencePacks.Add(RulePackDef.Named(rulepack));
			PlayLogEntry_Interaction playLogEntry = new PlayLogEntry_Interaction(dictionaryKey, giving, receiving, extraSentencePacks);
			Find.PlayLog.Add(playLogEntry);

			return sextype;
		}

		[SyncMethod]
		public static void Sex_Beatings(Pawn pawn, Pawn partner, bool isRape = false)
		{
			if (isRape && !RJWSettings.rape_beating)
				return;

			//dont remember what it does, probably manhunter stuff or not? disable and wait reports
			//if (!xxx.is_human(pawn))
			//	return;

			//If a pawn is incapable of violence/has low melee, they most likely won't beat their partner
			if (pawn.skills?.GetSkill(SkillDefOf.Melee).Level < 1)
				return;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float rand_value = Rand.Value;
			//float rand_value = RJW_Multiplayer.RJW_MP_RAND();
			float victim_pain = partner.health.hediffSet.PainTotal;
			// bloodlust makes the aggressor more likely to hit the prisoner
			float beating_chance = xxx.config.base_chance_to_hit_prisoner * (xxx.is_bloodlust(pawn) ? 1.5f : 1.0f);
			// psychopath makes the aggressor more likely to hit the prisoner past the significant_pain_threshold
			float beating_threshold = xxx.is_psychopath(pawn) ? xxx.config.extreme_pain_threshold : pawn.HostileTo(partner) ? xxx.config.significant_pain_threshold : xxx.config.minor_pain_threshold;

			//--Log.Message("roll_to_hit:  rand = " + rand_value + ", beating_chance = " + beating_chance + ", victim_pain = " + victim_pain + ", beating_threshold = " + beating_threshold);
			if ((victim_pain < beating_threshold && rand_value < beating_chance) || (rand_value < (beating_chance / 2) && xxx.is_bloodlust(pawn)))
			{
				//--Log.Message("   done told her twice already...");
				if (InteractionUtility.TryGetRandomVerbForSocialFight(pawn, out Verb v))
				{
					//Log.Message("   v. : " + v);
					//Log.Message("   v.GetDamageDef : " + v.GetDamageDef());
					//Log.Message("   v.v.tool - " + v.tool.label);
					//Log.Message("   v.v.tool.power base - " + v.tool.power);
					var orgpower = v.tool.power;
					//in case something goes wrong
					try
					{
						//Log.Message("   v.v.tool.power base - " + v.tool.power);
						if (RJWSettings.gentle_rape_beating)
						{
							v.tool.power = 0;
							//partner.stances.stunner.StunFor(600, pawn);
						}
						//Log.Message("   v.v.tool.power mod - " + v.tool.power);
						pawn.meleeVerbs.TryMeleeAttack(partner, v);
					}
					catch
					{ }
					v.tool.power = orgpower;
					//Log.Message("   v.v.tool.power reset - " + v.tool.power);
				}
			}
		}
	}
}


code:
using Multiplayer.API;
using Verse;

namespace rjw
{
	public static class Sexualizer
	{
		static void SexualizeSingleGenderPawn(Pawn pawn)
		{
			// Single gender is futa without the female gender change.
			SexPartAdder.add_genitals(pawn, null, Gender.Male);
			SexPartAdder.add_genitals(pawn, null, Gender.Female);
			SexPartAdder.add_breasts(pawn, null, Gender.Female);
			SexPartAdder.add_anus(pawn, null);
		}

		static void SexulaizeGenderlessPawn(Pawn pawn)
		{
			if (RJWSettings.GenderlessAsFuta && !xxx.is_mechanoid(pawn))
			{
				Log.Message("[RJW] SexulaizeGenderlessPawn() - genderless pawn, treating Genderless pawn As Futa" + xxx.get_pawnname(pawn));
				//set gender to female for futas
				pawn.gender = Gender.Female;
				SexPartAdder.add_genitals(pawn, null, Gender.Male);
				SexPartAdder.add_genitals(pawn, null, Gender.Female);
				SexPartAdder.add_breasts(pawn, null, Gender.Female);
				SexPartAdder.add_anus(pawn, null);
			}
			else
			{
				Log.Message("[RJW] SexulaizeGenderlessPawn() - unable to sexualize genderless pawn " + xxx.get_pawnname(pawn) + " gender: " + pawn.gender);
			}
		}

		[SyncMethod]
		static void SexualizeGenderedPawn(Pawn pawn)
		{
			//apply normal gender
			//Log.Message("[RJW] SexualizeGenderedPawn( " + xxx.get_pawnname(pawn) + " ) add genitals");
			SexPartAdder.add_genitals(pawn, null, pawn.gender);
			//Log.Message("[RJW] SexualizeGenderedPawn( " + xxx.get_pawnname(pawn) + " ) add breasts");
			SexPartAdder.add_breasts(pawn, null, pawn.gender);
			//Log.Message("[RJW] SexualizeGenderedPawn( " + xxx.get_pawnname(pawn) + " ) add anus");
			SexPartAdder.add_anus(pawn, null);

			//apply futa gender
			//if (pawn.gender == Gender.Female) // changing male to futa will break pawn generation due to relations

			if (pawn.Faction != null && !xxx.is_animal(pawn)) //null faction throws error
			{
				//Log.Message("[RJW] SexualizeGenderedPawn( " + xxx.get_pawnname(pawn) + " ) techLevel: " + (int)pawn.Faction.def.techLevel);
				//Log.Message("[RJW] SexualizeGenderedPawn( " + xxx.get_pawnname(pawn) + " ) techLevel: " + pawn.Faction.Name);

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

				// Log.Message($"[RJW] SexualizeGenderedPawn {chance} from {RJWSettings.futa_nymph_chance} {RJWSettings.futa_natives_chance} {RJWSettings.futa_spacers_chance}");
				if (Rand.Chance(chance))
				{
					//make futa
					if (pawn.gender == Gender.Female && RJWSettings.FemaleFuta)
						SexPartAdder.add_genitals(pawn, null, Gender.Male);
					//make trap
					else if (pawn.gender == Gender.Male && RJWSettings.MaleTrap)
						SexPartAdder.add_breasts(pawn, null, Gender.Female);
				}
			}
		}

		[SyncMethod]
		public static void sexualize_pawn(Pawn pawn)
		{
			//Log.Message("[RJW] sexualize_pawn( " + xxx.get_pawnname(pawn) + " ) called");
			if (pawn == null)
			{
				return;
			}

			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef) &&
				raceGroupDef.hasSingleGender)
			{
				Log.Message($"[RJW] sexualize_pawn() - sexualizing single gender pawn {xxx.get_pawnname(pawn)}  race: {raceGroupDef.defName}");
				SexualizeSingleGenderPawn(pawn);
			}
			else if (pawn.RaceProps.hasGenders)
			{
				SexualizeGenderedPawn(pawn);
			}
			else
			{
				if (Current.ProgramState == ProgramState.Playing) // DO NOT run at world generation, throws error
				{
					SexulaizeGenderlessPawn(pawn);
					return;
				}
			}

			if (!pawn.Dead)
			{
				//Add ticks at generation, so pawns don't instantly start lovin' when generated (esp. on scenario start).
				//if (xxx.RoMIsActive && pawn.health.hediffSet.HasHediff(HediffDef.Named("TM_ShapeshiftHD"))) // Rimworld of Magic's polymorph/shapeshift
				//	pawn.mindState.canLovinTick = Find.TickManager.TicksGame + (int)(SexUtility.GenerateMinTicksToNextLovin(pawn) * Rand.Range(0.01f, 0.05f));
				if (!xxx.is_insect(pawn))
					pawn.mindState.canLovinTick = Find.TickManager.TicksGame + (int)(SexUtility.GenerateMinTicksToNextLovin(pawn) * Rand.Range(0.1f, 1.0f));
				else
					pawn.mindState.canLovinTick = Find.TickManager.TicksGame + (int)(SexUtility.GenerateMinTicksToNextLovin(pawn) * Rand.Range(0.01f, 0.2f));

				//Log.Message("[RJW] sexualize_pawn( " + xxx.get_pawnname(pawn) + " ) add sexneed");
				if (pawn.RaceProps.Humanlike)
				{
					var sex_need = pawn.needs.TryGetNeed<Need_Sex>();
					if (pawn.Faction != null && !(pawn.Faction?.IsPlayer ?? false) && sex_need != null)
					{
						sex_need.CurLevel = Rand.Range(0.01f, 0.75f);
					}
				}
			}
		}
	}
}


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

namespace rjw
{
	public static class Logger
	{
		private static readonly LogMessageQueue messageQueueRJW = new LogMessageQueue();
		public static void Message(string text)
		{
			bool DevModeEnabled = RJWSettings.DevMode;
			if (!DevModeEnabled) return;
			UnityEngine.Debug.Log(text);
			messageQueueRJW.Enqueue(new LogMessage(LogMessageType.Message, text, StackTraceUtility.ExtractStackTrace()));
		}
		public static void Warning(string text)
		{
			bool DevModeEnabled = RJWSettings.DevMode;
			if (!DevModeEnabled) return;
			UnityEngine.Debug.Log(text);
			messageQueueRJW.Enqueue(new LogMessage(LogMessageType.Warning, text, StackTraceUtility.ExtractStackTrace()));
		}
		public static void Error(string text)
		{
			bool DevModeEnabled = RJWSettings.DevMode;
			if (!DevModeEnabled) return;
			UnityEngine.Debug.Log(text);
			messageQueueRJW.Enqueue(new LogMessage(LogMessageType.Error, text, StackTraceUtility.ExtractStackTrace()));
		}

		public static TimeSpan Time(Action action)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			action();
			stopwatch.Stop();
			return stopwatch.Elapsed;
		}
	}
}


code:
using Verse;

namespace rjw
{
	public class MapCom_Injector : MapComponent
	{
		public bool injected_designator = false;

		public bool triggered_after_load = false;

		public MapCom_Injector(Map m) : base(m)
		{
		}

		public override void MapComponentUpdate()
		{
		}

		public override void MapComponentTick()
		{
		}

		public override void MapComponentOnGUI()
		{
			var currently_visible = Find.CurrentMap == map;

			if ((!injected_designator) && currently_visible)
			{
				//Find.ReverseDesignatorDatabase.AllDesignators.Add(new Designator_ComfortPrisoner());
				//Find.ReverseDesignatorDatabase.AllDesignators.Add(new Designator_Breed());
				injected_designator = true;
			}
			else if (injected_designator && (!currently_visible))
				injected_designator = false;
		}

		public override void ExposeData()
		{
		}
	}
}


code:
using System;
using Verse;

namespace rjw
{
	public sealed class MiscTranslationDef : Def
	{
		public Type targetClass;
		public string stringA = null;
		public string stringB = null;
		public string stringC = null;

		private void Assert(bool check, string errorMessage)
		{
			if (!check)
			{
				Log.Error($"[RJW] Invalid data in MiscTranslationDef {defName}: {errorMessage}");
			}
		}

		public override void PostLoad()
		{
			Assert(targetClass != null, "targetClass field must be set");
		}

		public override void ResolveReferences()
		{
			base.ResolveReferences();
		}
	}
}


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

namespace rjw
{
	public static class PawnExtensions
	{
		public static bool RaceHasFertility(this Pawn pawn)
		{
			// True by default.
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
				return raceGroupDef.hasFertility;
			return true;
		}

		public static bool RaceHasPregnancy(this Pawn pawn)
		{
			// True by default.
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
				return raceGroupDef.hasPregnancy;
			return true;
		}

		public static bool RaceHasOviPregnancy(this Pawn pawn)
		{
			// False by default.
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
				return raceGroupDef.oviPregnancy;
			return false;
		}

		public static bool RaceImplantEggs(this Pawn pawn)
		{
			// False by default.
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
				return raceGroupDef.ImplantEggs;
			return false;
		}

		public static bool RaceHasSexNeed(this Pawn pawn)
		{
			// True by default.
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
				return raceGroupDef.hasSexNeed;
			return true;
		}

		public static bool TryAddSexPart(this Pawn pawn, SexPartType sexPartType)
		{
			return RaceGroupDef_Helper.TryAddSexPart(pawn, sexPartType);
		}

		public static bool Has(this Pawn pawn, Quirk quirk)
		{
			return xxx.has_quirk(pawn, quirk.Key);
		}

		public static bool Has(this Pawn pawn, RaceTag tag)
		{
			if (RaceGroupDef_Helper.TryGetRaceGroupDef(pawn, out var raceGroupDef))
			{
				return raceGroupDef.tags != null && raceGroupDef.tags.Contains(tag.Key);
			}
			else
			{
				return tag.DefaultWhenNoRaceGroupDef(pawn);
			}
		}

		public static void Add(this Pawn pawn, Quirk quirk)
		{
			QuirkAdder.Add(pawn, quirk);
		}

		public static bool IsSexyRobot(this Pawn pawn)
		{
			return AndroidsCompatibility.IsAndroid(pawn);
		}

		// In theory I think this should involve RaceGroupDef.
		public static bool IsUnsexyRobot(this Pawn pawn)
		{
			return !IsSexyRobot(pawn)
				&& (xxx.is_mechanoid(pawn) || pawn.kindDef.race.defName.ToLower().Contains("droid"));
		}

		public static bool IsAnimal(this Pawn pawn)
		{
			return xxx.is_animal(pawn);
		}

		public static bool IsVisiblyPregnant(this Pawn pawn)
		{
			return pawn.IsPregnant(true);
		}

		public static bool IsPregnant(this Pawn pawn, bool mustBeVisible = false)
		{
			var set = pawn.health.hediffSet;
			return set.HasHediff(HediffDefOf.Pregnant, mustBeVisible) ||
				Hediff_BasePregnancy.KnownPregnancies().Any(x => set.HasHediff(HediffDef.Named(x), mustBeVisible));
		}
	}
}