code:
///Better not leave it be, this mod is not intended to reimpelement base functionality and break other mods by that.
//using Harmony;
//using RimWorld;
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Reflection;
//using System.Text;
//using UnityEngine;
//using Verse;
//using Verse.Sound;

//namespace rjw.Source.Common
//{
//	public static class ExpandedPawnCharacterCard
//	{
//		/// <summary>
//		/// Better to leave it be, since it doesn't affect anything much.
//		/// </summary>
//		public static float cardX = 570f;  // vanilla is 570

//		/// <summary>
//		/// Basically, you want to increase this value if you want character info card be taller (thus allowing to have more
//		/// traits and skills shown). Height is in pixels, increase by ~25 pixels for each additional row of skill/trait.
//		/// </summary>
//		public static float cardY = 470f;  // vanilla is 470

//		[HarmonyPatch(typeof(CharacterCardUtility), "DrawCharacterCard", new[] { typeof(Rect), typeof(Pawn), typeof(Action), typeof(Rect), })]
//		public static class CharacterCardUtilityPatch
//		{
//			private static IEnumerable<WorkTags> WorkTagsFrom(WorkTags tags)
//			{
//				foreach (WorkTags workTag in tags.GetAllSelectedItems<WorkTags>())
//				{
//					if (workTag != WorkTags.None)
//					{
//						yield return workTag;
//					}
//				}
//			}

//			[HarmonyPrefix]
//			public static bool DrawCharacterCardP(ref Rect rect, ref Pawn pawn, ref Action randomizeCallback, ref Rect creationRect)
//			{
//				rect.position = new Vector2(17f, 17f);
//				rect.size = new Vector2(cardX, cardY);
//				bool flag = randomizeCallback != null;
//				GUI.BeginGroup((!flag) ? rect : creationRect);
//				Rect rectLabel = new Rect(0f, 0f, 300f, 30f);
//				NameTriple nameTriple = pawn.Name as NameTriple;
//				if (flag && nameTriple != null)
//				{
//					Rect rect3 = new Rect(rectLabel);
//					rect3.width *= 0.333f;
//					Rect rect4 = new Rect(rectLabel);
//					rect4.width *= 0.333f;
//					rect4.x += rect4.width;
//					Rect rect5 = new Rect(rectLabel);
//					rect5.width *= 0.333f;
//					rect5.x += rect4.width * 2f;
//					string first = nameTriple.First;
//					string nick = nameTriple.Nick;
//					string last = nameTriple.Last;
//					CharacterCardUtility.DoNameInputRect(rect3, ref first, 12);
//					if (nameTriple.Nick == nameTriple.First || nameTriple.Nick == nameTriple.Last)
//					{
//						GUI.color = new Color(1f, 1f, 1f, 0.5f);
//					}
//					CharacterCardUtility.DoNameInputRect(rect4, ref nick, 9);
//					GUI.color = Color.white;
//					CharacterCardUtility.DoNameInputRect(rect5, ref last, 12);
//					if (nameTriple.First != first || nameTriple.Nick != nick || nameTriple.Last != last)
//					{
//						pawn.Name = new NameTriple(first, nick, last);
//					}
//					TooltipHandler.TipRegion(rect3, "FirstNameDesc".Translate());
//					TooltipHandler.TipRegion(rect4, "ShortIdentifierDesc".Translate());
//					TooltipHandler.TipRegion(rect5, "LastNameDesc".Translate());
//				}
//				else
//				{
//					rectLabel.width = 999f;
//					Text.Font = GameFont.Medium;
//					Widgets.Label(rectLabel, pawn.Name.ToStringFull);
//					Text.Font = GameFont.Small;
//				}
//				if (randomizeCallback != null)
//				{
//					Rect rectRandomise = new Rect(creationRect.width - 24f - 100f, 0f, 100f, rectLabel.height);
//					if (Widgets.ButtonText(rectRandomise, "Randomize".Translate(), true, false, true))
//					{
//						SoundDefOf.TickTiny.PlayOneShotOnCamera(null);
//						randomizeCallback();
//					}
//					UIHighlighter.HighlightOpportunity(rectRandomise, "RandomizePawn");
//				}
//				if (flag)
//				{
//					Widgets.InfoCardButton(creationRect.width - 24f, 0f, pawn);
//				}
//				else if (!pawn.health.Dead)
//				{
//					float num = CharacterCardUtility.PawnCardSize.x - 85f;
//					if ((pawn.Faction == Faction.OfPlayer || pawn.IsPrisonerOfColony) && pawn.Spawned)
//					{
//						Rect rectBanish = new Rect(num, 0f, 30f, 30f);
//						TooltipHandler.TipRegion(rectBanish, PawnBanishUtility.GetBanishButtonTip(pawn));
//						if (Widgets.ButtonImage(rectBanish, ContentFinder<Texture2D>.Get("UI/Buttons/Banish", true)))
//						{
//							if (pawn.Downed)
//							{
//								Messages.Message("MessageCantBanishDownedPawn".Translate(new object[]
//								{
//								pawn.LabelShort
//								}).AdjustedFor(pawn), pawn, MessageTypeDefOf.RejectInput);
//							}
//							else
//							{
//								PawnBanishUtility.ShowBanishPawnConfirmationDialog(pawn);
//							}
//						}
//						num -= 40f;
//					}
//					if (pawn.IsColonist)
//					{
//						Rect rect8 = new Rect(num, 0f, 30f, 30f);
//						TooltipHandler.TipRegion(rect8, "RenameColonist".Translate());
//						if (Widgets.ButtonImage(rect8, ContentFinder<Texture2D>.Get("UI/Buttons/Rename", true)))
//						{
//							Find.WindowStack.Add(new Dialog_ChangeNameTriple(pawn));
//						}
//						num -= 40f;
//					}
//				}
//				string label = pawn.MainDesc(true);
//				Rect rectMainDesc = new Rect(0f, 45f, rect.width, 60f);
//				Widgets.Label(rectMainDesc, label);
//				Pawn p = pawn;
//				TooltipHandler.TipRegion(rectMainDesc, () => p.ageTracker.AgeTooltipString, 6873641);
//				Rect positionLeftHalf = new Rect(0f, 100f, 250f, cardY);
//				Rect positionRightHalf = new Rect(positionLeftHalf.xMax, 100f, 258f, cardY);
//				GUI.BeginGroup(positionLeftHalf);
//				float currentY = 0f;
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, 0f, 200f, 30f), "Backstory".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				foreach (BackstorySlot backstorySlot in Enum.GetValues(typeof(BackstorySlot)))
//				{
//					Backstory backstory = pawn.story.GetBackstory(backstorySlot);
//					if (backstory != null)
//					{
//						Rect rect10 = new Rect(0f, currentY, positionLeftHalf.width, 24f);
//						if (Mouse.IsOver(rect10))
//						{
//							Widgets.DrawHighlight(rect10);
//						}
//						TooltipHandler.TipRegion(rect10, backstory.FullDescriptionFor(pawn));
//						Text.Anchor = TextAnchor.MiddleLeft;
//						string str = (backstorySlot != BackstorySlot.Adulthood) ? "Childhood".Translate() : "Adulthood".Translate();
//						Widgets.Label(rect10, str + ":");
//						Text.Anchor = TextAnchor.UpperLeft;
//						Rect rect11 = new Rect(rect10);
//						rect11.x += 90f;
//						rect11.width -= 90f;
//						string title = backstory.Title;
//						Widgets.Label(rect11, title);
//						currentY += rect10.height + 2f;
//					}
//				}
//				currentY += 25f;
//				Text.Font = GameFont.Small;
//				Widgets.Label(new Rect(0f, currentY, 200f, 30f), "IncapableOf".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				StringBuilder stringBuilder = new StringBuilder();
//				WorkTags combinedDisabledWorkTags = pawn.story.CombinedDisabledWorkTags;
//				if (combinedDisabledWorkTags == WorkTags.None)
//				{
//					stringBuilder.Append("(" + "NoneLower".Translate() + "), ");
//				}
//				else
//				{
//					List<WorkTags> list = WorkTagsFrom(combinedDisabledWorkTags).ToList<WorkTags>();
//					bool flag2 = true;
//					foreach (WorkTags current in list)
//					{
//						if (flag2)
//						{
//							stringBuilder.Append(current.LabelTranslated().CapitalizeFirst());
//						}
//						else
//						{
//							stringBuilder.Append(current.LabelTranslated());
//						}
//						stringBuilder.Append(", ");
//						flag2 = false;
//					}
//				}
//				string text = stringBuilder.ToString();
//				text = text.Substring(0, text.Length - 2);
//				Rect rect12 = new Rect(0f, currentY, positionLeftHalf.width, 999f);
//				Widgets.Label(rect12, text);
//				currentY += 100f;
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, currentY, 200f, 30f), "Traits".Translate());
//				currentY += 30f;
//				Text.Font = GameFont.Small;
//				for (int i = 0; i < pawn.story.traits.allTraits.Count; i++)
//				{
//					Trait trait = pawn.story.traits.allTraits[i];
//					Rect rectCurrentTrait = new Rect(0f, currentY, positionLeftHalf.width, 24f);
//					if (Mouse.IsOver(rectCurrentTrait))
//					{
//						Widgets.DrawHighlight(rectCurrentTrait);
//					}
//					Widgets.Label(rectCurrentTrait, trait.LabelCap);
//					currentY += rectCurrentTrait.height + 2f;
//					Trait trLocal = trait;
//					TipSignal tip = new TipSignal(() => trLocal.TipString(p), (int)currentY * 37);
//					TooltipHandler.TipRegion(rectCurrentTrait, tip);
//				}
//				GUI.EndGroup();
//				GUI.BeginGroup(positionRightHalf);
//				Text.Font = GameFont.Medium;
//				Widgets.Label(new Rect(0f, 0f, 200f, 30f), "Skills".Translate());
//				SkillUI.SkillDrawMode mode;
//				if (Current.ProgramState == ProgramState.Playing)
//				{
//					mode = SkillUI.SkillDrawMode.Gameplay;
//				}
//				else
//				{
//					mode = SkillUI.SkillDrawMode.Menu;
//				}
//				SkillUI.DrawSkillsOf(pawn, new Vector2(0f, 35f), mode);
//				GUI.EndGroup();
//				GUI.EndGroup();

//				return false;
//			}
//		}
//		[HarmonyPatch(typeof(ITab_Pawn_Character), "FillTab")]
//		public static class ITab_Pawn_CharacterPatch
//		{
//			[HarmonyPrefix]
//			public static bool FillTabP(ITab_Pawn_Character __instance)
//			{

//				FieldInfo fi = typeof(ITab_Pawn_Character).GetField("size", Unprivater.flags);
//				fi.SetValue(__instance, new Vector2(cardX+34f, cardY+34f));

//				var p = Unprivater.GetProtectedProperty<Pawn>("PawnToShowInfoAbout", __instance);

//				Rect rect = new Rect(17f, 17f, 570f, cardY);
//				CharacterCardUtility.DrawCharacterCard(rect, p, null, default(Rect));

//				return false;
//			}
//		}

//	}
//}


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

namespace rjw
{
	public static class BreastSize_Helper
	{
		public static int NipplesOnlyBreastSize = -1;

		static HediffDef[] BreastsInOrder = new[] {
			Genital_Helper.flat_breasts,
			Genital_Helper.small_breasts,
			Genital_Helper.average_breasts,
			Genital_Helper.large_breasts,
			Genital_Helper.huge_breasts
		};

		public static int MaxSize = BreastsInOrder.Length - 1;

		static IDictionary<HediffDef, int> SizeByHediffDef = BreastsInOrder
			.Select((hed, i) => new { hed, i })
			.ToDictionary(pair => pair.hed, pair => pair.i);

		/// <summary>
		/// Returns true and sets size if pawn has natural breasts.
		/// Otherwise returns false.
		/// </summary>
		public static bool TryGetBreastSize(Pawn pawn, out int size)
		{
			return TryGetBreastSize(pawn, out size, out var hediff);
		}

		/// <summary>
		/// Returns true and sets size and hediff if pawn has natural breasts.
		/// Hediff will still be null for nipples only.
		/// Otherwise returns false.
		/// </summary>
		public static bool TryGetBreastSize(Pawn pawn, out int size, out Hediff hediff)
		{
			var chest = Genital_Helper.get_breasts(pawn);

			if (pawn.health.hediffSet.PartIsMissing(chest))
			{
				size = 0;
				hediff = null;
				return false;
			}

			foreach(var candidate_hediff in pawn.health.hediffSet.hediffs)
			{
				if (SizeByHediffDef.TryGetValue(candidate_hediff.def, out size))
				{
					hediff = candidate_hediff;
					return true;
				}
			}

			if (HasNipplesOnly(pawn, chest))
			{
				size = NipplesOnlyBreastSize;
				hediff = null;
				return true;
			}

			size = 0;
			hediff = null;
			return false;
		}

		public static int GetSize(HediffDef hediffDef)
		{
			return SizeByHediffDef[hediffDef];
		}

		public static HediffDef GetHediffDef(int size)
		{
			return BreastsInOrder[size];
		}

		public static bool IsMammal(Pawn pawn)
		{
			// In theory should not apply to lizards etc either.
			return !xxx.is_mechanoid(pawn);
		}

		/// <summary>
		/// Returns true if pawn has nipples but no breast development or implants.
		/// </summary>
		public static bool HasNipplesOnly(Pawn pawn, BodyPartRecord chest)
		{
			var alreadyHasBoobs = pawn.health.hediffSet.hediffs.Any(hediff =>
				hediff.Part == chest &&
				(hediff is Hediff_Implant || hediff is Hediff_AddedPart));
			return IsMammal(pawn) && !alreadyHasBoobs;
		}

		/// <summary>
		/// Adds two bruises with amount up to the given max.
		/// </summary>
		[SyncMethod]
		public static void HurtBreasts(Pawn pawn, BodyPartRecord part, int max)
		{
			if (max <= 0)
			{
				return;
			}

			// Two bruises.
			for (var i = 0; i < 2; i++)
			{
				pawn.TakeDamage(new DamageInfo(
					DamageDefOf.Blunt,
					Rand.RangeInclusive(max / 4, max),
					999f,
					-1f,
					null,
					part,
					null,
					DamageInfo.SourceCategory.ThingOrUnknown,
					null));
			}
		}
	}
}

repo_name: Eggs/rjw

license: mit


code:
// #define TESTMODE // Uncomment to enable logging.

using Verse;
using Verse.AI;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using System.Diagnostics;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// Helper for sex with animals
	/// </summary>
	public class BreederHelper
	{
		public const int max_animals_at_once = 1; // lets not forget that the purpose is procreation, not sodomy

		[Conditional("TESTMODE")]
		private static void DebugText(string msg)
		{
			Log.Message(msg);
		}

		//animal try to find best designated pawn to breed
		[SyncMethod]
		public static Pawn find_designated_breeder(Pawn pawn, Map m)
		{
			if (!DesignatorsData.rjwBreeding.Any()) return null;
			DebugText("BreederHelper::find_designated_breeder( " + xxx.get_pawnname(pawn) + " ) called");

			Pawn best_target = null;
			var best_fuckability = 0.10f; // Don't breed pawns with <10% fuckability

			if (pawn.Faction == null)// HostileTo causes error on fresh colony(w/o name)
				return null;

			IEnumerable<Pawn> targets = DesignatorsData.rjwBreeding.Where(x
				=> x != pawn
				&& xxx.is_not_dying(x)
				&& xxx.can_get_raped(x)
				&& !x.IsForbidden(pawn)
				&& !x.Suspended
				&& !x.HostileTo(pawn)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once)
				&& ((RJWSettings.bestiality_enabled && xxx.is_human(x)) || (RJWSettings.animal_on_animal_enabled && xxx.is_animal(x)))
				);

			foreach (Pawn target in targets)
			{
				if (!xxx.can_path_to_target(pawn, target.Position))
					continue;// too far

				var fuc = SexAppraiser.would_fuck(pawn, target, invert_opinion: true, ignore_gender: (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || xxx.is_insect(pawn)));
				DebugText("BreederHelper::find_designated_breeder( " + xxx.get_pawnname(pawn) + " -> " + xxx.get_pawnname(target) + " (" + fuc.ToString() + " / " + best_fuckability.ToString() + ")");

				if ((fuc > best_fuckability) && (Rand.Value < fuc))
				{
					best_target = target;
					best_fuckability = fuc;
				}
			}
			return best_target;
		}

		//animal or human try to find animals to breed (non designation)
		//public static Pawn find_breeder_animal(Pawn pawn, Map m, bool SameRace = true)
		[SyncMethod]
		public static Pawn find_breeder_animal(Pawn pawn, Map m)
		{
			DebugText("BreederHelper::find_breeder_animal( " + xxx.get_pawnname(pawn) + " ) called");

			float base_fuckability = 0.1f; //Determines how picky the pawns are. Could be added as config.
			List<Pawn> valid_targets = new List<Pawn>();

			//Pruning initial pawn list.
			IEnumerable<Pawn> targets = m.mapPawns.AllPawnsSpawned.Where(x
				=> x != pawn
				&& xxx.is_animal(x)
				&& xxx.can_get_raped(x)
				&& !x.IsForbidden(pawn)
				&& !x.Suspended
				&& !x.HostileTo(pawn)
				&& pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once)
				//&& SameRace ? pawn.kindDef.race == x.kindDef.race : true
				);

			if (targets.Any())
			{
				//filter pawns for female, who can fuck her
				//not sure if faction check should be but w/e
				if (!Genital_Helper.has_penis(pawn) && !Genital_Helper.has_penis_infertile(pawn) && (Genital_Helper.has_vagina(pawn) || Genital_Helper.has_anus(pawn)))
				{
					targets = targets.Where(x => xxx.can_fuck(x) && x.Faction == pawn.Faction);
				}

				//for humans, animals dont have need - always = 3f
				//if not horny, seek only targets with safe temp
				if (xxx.need_some_sex(pawn) < 3.0f)
				{
					targets = targets.Where(x => pawn.CanReach(x, PathEndMode.Touch, Danger.None));
				}

				//Used for interspecies animal-on-animal.
				//Animals will only go for targets they can see.
				if (xxx.is_animal(pawn))
				{
					targets = targets.Where(x => pawn.CanSee(x) && pawn.def.defName != x.def.defName);
				}
				else
				{
					// Pickier about the targets if the pawn has no prior experience.
					if (pawn.records.GetValue(xxx.CountOfSexWithAnimals) < 3 && !xxx.is_zoophile(pawn))
					{
						base_fuckability *= 2f;
					}

					if (xxx.need_some_sex(pawn) > 2f)
					{   // Less picky when frustrated...
						base_fuckability *= 0.6f;
					}
					else if (xxx.need_some_sex(pawn) < 2f)
					{   // ...and far more picky when satisfied.
						base_fuckability *= 2.5f;
					}
				}
			}

			DebugText("[RJW]BreederHelper::find_breeder_animal::" + targets.Count() + " targets found on map.");

			if (!targets.Any())
			{
				return null; //None found.
			}

			foreach (Pawn target in targets)
			{
				DebugText("[RJW]BreederHelper::find_breeder_animal::Checking target " + target.kindDef.race.defName.ToLower());

				if (!xxx.can_path_to_target(pawn, target.Position))
					continue;// too far

				float fuc = SexAppraiser.would_fuck_animal(pawn, target); // 0.0 to ~3.0, orientation checks etc.

				if (!(fuc > base_fuckability)) continue;
				DebugText("Adding target" + target.kindDef.race.defName.ToLower());
				valid_targets.Add(target);
			}

			DebugText(valid_targets.Count() + " valid targets found on map.");
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return valid_targets.Any() ? valid_targets.RandomElement() : null;
		}
	}
}


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

//This one is helper lib for handling all the trans surgery.
namespace rjw
{
	public static class GenderHelper
	{
		public enum Sex { male, female, trap, futa, none }//there is Verse,Gender but it is shit
		//These would probably be better packed in some enumerable structure, so that functions below weren't if-trees, but I don't know C#, sry.
		public static HediffDef was_boy = HediffDef.Named("hediff_was_boy");
		public static HediffDef was_girl = HediffDef.Named("hediff_was_girl");
		public static HediffDef was_futa = HediffDef.Named("hediff_was_futa");
		public static HediffDef was_trap = HediffDef.Named("hediff_was_trap");

		static List<HediffDef> old_sex_list = new List<HediffDef> { was_boy, was_girl, was_futa, was_trap };
		static Dictionary<Sex, HediffDef> sex_to_old_sex = new Dictionary<Sex, HediffDef>() {
			{Sex.male, was_boy },{Sex.female, was_girl},{Sex.trap, was_trap},{Sex.futa, was_futa}
		};
		static Dictionary<HediffDef,Sex> old_sex_to_sex = sex_to_old_sex.ToDictionary(x => x.Value, x => x.Key);

		public static HediffDef m2t = HediffDef.Named("hediff_male2trap");
		public static HediffDef m2f = HediffDef.Named("hediff_male2female");
		public static HediffDef m2h = HediffDef.Named("hediff_male2futa");

		public static HediffDef f2t = HediffDef.Named("hediff_female2trap");
		public static HediffDef f2m = HediffDef.Named("hediff_female2male");
		public static HediffDef f2h = HediffDef.Named("hediff_female2futa");

		public static HediffDef h2t = HediffDef.Named("hediff_futa2trap");
		public static HediffDef h2m = HediffDef.Named("hediff_futa2male");
		public static HediffDef h2f = HediffDef.Named("hediff_futa2female");

		public static HediffDef t2h = HediffDef.Named("hediff_trap2futa");
		public static HediffDef t2m = HediffDef.Named("hediff_trap2male");
		public static HediffDef t2f = HediffDef.Named("hediff_trap2female");

		static List<HediffDef> SexChangeThoughts = new List<HediffDef> { m2t, m2f, m2h, f2t, f2m, f2h, h2t, h2m, h2f };

		private static readonly SimpleCurve rigidity_from_age = new SimpleCurve//relative to max age
		{
			new CurvePoint(0f, 0.1f),
			new CurvePoint(0.2f, 0.1f),
			new CurvePoint(0.5f, 1f),
			new CurvePoint(10f, 1f)
		};

		public static Sex GetSex(Pawn pawn)
		{
			bool has_vagina = Genital_Helper.has_vagina(pawn);
			bool has_penis = Genital_Helper.has_penis(pawn);
			bool has_penis_infertile = Genital_Helper.has_penis_infertile(pawn);
			bool has_breasts = Genital_Helper.has_breasts(pawn);
			bool has_male_breasts = Genital_Helper.has_male_breasts(pawn);
			//BodyType? bt = pawn.story?.bodyType;
			//if (bt != null)
			//	bt = BodyType.Undefined;

			Sex res;
			if (has_vagina && !has_penis && !has_penis_infertile)
				res = Sex.female;
			else if (has_vagina && (has_penis || has_penis_infertile))
				res = Sex.futa;
			else if ((has_penis || has_penis_infertile) && has_breasts && !has_male_breasts)
				res = Sex.trap;
			else if (has_penis || has_penis_infertile) //probably should change this later
				res = Sex.male;
			else if (pawn.gender == Gender.Male)
				res = Sex.male;
			else if (pawn.gender == Gender.Female)
				res = Sex.female;
			else
				res = Sex.none;
			return res;
		}
		/*
		public static HediffDef GetReactionHediff(Sex before, Sex after)
		{
			if (before == after)
				return null;

			if (before == Sex.male)
				return (after == Sex.female) ? m2f : m2t;
			else if (before == Sex.female)
			{
				if (after == Sex.male)
					return f2m;
				else if (after == Sex.trap)
					return f2t;
				else if (after == Sex.futa)
					return f2h;
				else
					return null;
			}
			else if (before == Sex.futa && (after == Sex.female || after == Sex.none))
				return h2f;
			else//trap to anything, futa to trap; probably won't even be reached ever
				return null;
		}
		*/

		//TODO: fix reactions
		public static HediffDef GetReactionHediff(Sex before, Sex after)
		{
			if (before == after)
				return null;
			else if (before == Sex.male)
			{
				if (after == Sex.female)
					return m2f;
				else if (after == Sex.trap)
					return m2t;
				else if (after == Sex.futa)
					return m2h;
				else
					return null;
			}
			else if (before == Sex.female)
			{
				if (after == Sex.male)
					return f2m;
				else if (after == Sex.trap)
					return f2t;
				else if (after == Sex.futa)
					return f2h;
				else
					return null;
			}
			else if (before == Sex.futa)
			{
				if (after == Sex.male)
					return h2m;
				else if (after == Sex.female)
					return h2f;
				else if (after == Sex.trap)
					return h2t;
				else
					return null;
			}
			else if (before == Sex.trap)
			{
				if (after == Sex.male)
					return t2m;
				else if (after == Sex.female)
					return t2f;
				else if (after == Sex.futa)
					return t2h;
				else
					return null;
			}
			else//unicorns?
				return null;
		}

		public static bool WasThisBefore(Pawn pawn, Sex after)
		{
			Hediff was = null;

			switch (after)
			{
				case Sex.male:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_boy);
					break;
				case Sex.female:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_girl);
					break;
				case Sex.trap:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_trap);
					break;
				case Sex.futa:
					was = pawn.health.hediffSet.GetFirstHediffOfDef(was_futa);
					break;
			}
			return (was != null) ? true : false;
		}

		//Get one of the sexes that were on this pawn before
		public static Sex GetOriginalSex(Pawn pawn)
		{
			foreach (var os in old_sex_list)
			{
				if (pawn.health.hediffSet.GetFirstHediffOfDef(os) != null)
					return old_sex_to_sex[os];
			}
			return Sex.none;//it shouldnt reach here though
		}

		public static Hediff IsInDenial(Pawn pawn)
		{
			Hediff res = null;
			foreach (var h in SexChangeThoughts)
			{
				res = pawn.health.hediffSet.GetFirstHediffOfDef(h);
				if (res != null)
					break;
			}
			return res;
		}

		//roll how much gender fluid the pawn is.
		//In ideal world this would actually take into account from where to where transition is moving and so on.
		//Same applies to the thought hediffs themselves, but we get what we can get now
		static float RollSexChangeSeverity(Pawn pawn)
		{
			float res = 1;
			if (xxx.is_bisexual(pawn))
				res *= 0.5f;
			if (pawn.story != null && (pawn.story.bodyType == BodyTypeDefOf.Thin || pawn.story.bodyType == BodyTypeDefOf.Fat))
				res *= 0.8f;
			if (!pawn.ageTracker.CurLifeStage.reproductive)
				res *= 0.2f;
			else
				res *= rigidity_from_age.Evaluate(SexUtility.ScaleToHumanAge(pawn));

			return res;
		}

		//Quick hack to check if hediff is adding happiness
		static bool is_happy(this Hediff thought)
		{
			return thought.CurStageIndex == 0;
		}
		static void make_happy(this Hediff thought)
		{
			thought.Severity = 0.24f;//this is currently max severity for hediff, that is associated with positive mood
		}
		static void mix_thoughts(this Hediff newer, Hediff older)
		{
			newer.Severity = (newer.Severity + older.Severity) / 2f  ;
		}

		static void GiveThought(Pawn pawn, HediffDef thought, bool happy = false, Hediff old_thought=null)
		{
			pawn.health.AddHediff(thought);
			var new_thought = pawn.health.hediffSet.GetFirstHediffOfDef(thought);
			if (happy)
			{
				new_thought.make_happy();
				return;
			}
			new_thought.Severity = RollSexChangeSeverity(pawn);
			if (old_thought!=null)
			{
				new_thought.Severity = (new_thought.Severity + old_thought.Severity) / 2f;
			}
		}

		/// <summary>
		/// Executes action and then changes sex if necessary.
		/// </summary>
		public static void ChangeSex(Pawn pawn, Action action)
		{
			var before = GetSex(pawn);
			action();
			var after = GetSex(pawn);
			ChangeSex(pawn, before, after);
		}

		public static void ChangeSex(Pawn pawn, Sex oldsex, Sex newsex)
		{
			// Wakeup pawn sexuality if it has none before

			if (!(CompRJW.Comp(pawn).orientation == Orientation.Asexual || CompRJW.Comp(pawn).orientation == Orientation.None))
				if (oldsex == newsex)
					return;

			//update ingame genders
			if (newsex == Sex.none)
				return;
			else if (newsex == Sex.male || newsex == Sex.trap)
				pawn.gender = Gender.Male;
			else
				pawn.gender = Gender.Female;

			// Sexualize pawn after installation of parts if it was "not interested in".
			if (oldsex == Sex.none || CompRJW.Comp(pawn).orientation == Orientation.Asexual || CompRJW.Comp(pawn).orientation == Orientation.None)
				if (pawn.kindDef.race.defName.ToLower().Contains("droid") && !AndroidsCompatibility.IsAndroid(pawn))
				{
					//basic droids,they dont care
					return;
				}
				else
				{
					CompRJW.Comp(pawn).Sexualize(pawn, true);
				}

			var old_thought = IsInDenial(pawn);
			var react = GetReactionHediff(oldsex, newsex);

			if (old_thought==null || old_thought.is_happy())//pawn either liked it or got used already
			{
				//Log.Message("ChangeSex 1 old_thought" + old_thought);
				//Log.Message("ChangeSex 1 react" + react);
				if (react!=null)
					GiveThought(pawn, react, pawn.IsDesignatedHero());//give unhappy, if not hero
				if (old_thought!=null)
					pawn.health.RemoveHediff(old_thought);

				//add tracking hediff
				pawn.health.AddHediff(sex_to_old_sex[oldsex]);
			}
			else//pawn was unhappy
			{
				if (WasThisBefore(pawn, newsex))//pawn is happy to be previous self
				{
					GiveThought(pawn, react, happy:true);
					pawn.health.RemoveHediff(old_thought);
				}
				else//pawn is still unhappy mix the unhappiness from two
				{
					react = GetReactionHediff(GetOriginalSex(pawn), newsex);//check reaction from original sex
					if (react!=null)
					{
						GiveThought(pawn, react, old_thought: old_thought);
						pawn.health.RemoveHediff(old_thought);
					}
					//else pawn keeps old unhappy thought
				}
			}
		}
	}
}


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

namespace rjw
{
	public static class Genital_Helper
	{
		public static HediffDef generic_anus = HediffDef.Named("GenericAnus");
		public static HediffDef generic_penis = HediffDef.Named("GenericPenis");
		public static HediffDef generic_vagina = HediffDef.Named("GenericVagina");
		public static HediffDef generic_breasts = HediffDef.Named("GenericBreasts");

		public static HediffDef micro_penis = HediffDef.Named("MicroPenis");
		public static HediffDef small_penis = HediffDef.Named("SmallPenis");
		public static HediffDef average_penis = HediffDef.Named("Penis");
		public static HediffDef big_penis = HediffDef.Named("BigPenis");
		public static HediffDef huge_penis = HediffDef.Named("HugePenis");
		public static HediffDef hydraulic_penis = HediffDef.Named("HydraulicPenis");
		public static HediffDef bionic_penis = HediffDef.Named("BionicPenis");
		public static HediffDef archotech_penis = HediffDef.Named("ArchotechPenis");

		public static HediffDef micro_vagina = HediffDef.Named("MicroVagina");
		public static HediffDef tight_vagina = HediffDef.Named("TightVagina");
		public static HediffDef average_vagina = HediffDef.Named("Vagina");
		public static HediffDef loose_vagina = HediffDef.Named("LooseVagina");
		public static HediffDef gaping_vagina = HediffDef.Named("GapingVagina");
		public static HediffDef hydraulic_vagina = HediffDef.Named("HydraulicVagina");
		public static HediffDef bionic_vagina = HediffDef.Named("BionicVagina");
		public static HediffDef archotech_vagina = HediffDef.Named("ArchotechVagina");

		public static HediffDef flat_breasts = HediffDef.Named("FlatBreasts");
		public static HediffDef small_breasts = HediffDef.Named("SmallBreasts");
		public static HediffDef average_breasts = HediffDef.Named("Breasts");
		public static HediffDef large_breasts = HediffDef.Named("LargeBreasts");
		public static HediffDef huge_breasts = HediffDef.Named("HugeBreasts");
		public static HediffDef hydraulic_breasts = HediffDef.Named("HydraulicBreasts");
		public static HediffDef bionic_breasts = HediffDef.Named("BionicBreasts");
		public static HediffDef archotech_breasts = HediffDef.Named("ArchotechBreasts");
		public static HediffDef featureless_chest = HediffDef.Named("FeaturelessChest");
		public static HediffDef udder = HediffDef.Named("Udder");

		public static HediffDef micro_anus = HediffDef.Named("MicroAnus");
		public static HediffDef tight_anus = HediffDef.Named("TightAnus");
		public static HediffDef loose_anus = HediffDef.Named("LooseAnus");
		public static HediffDef average_anus = HediffDef.Named("Anus");
		public static HediffDef gaping_anus = HediffDef.Named("GapingAnus");
		public static HediffDef hydraulic_anus = HediffDef.Named("HydraulicAnus");
		public static HediffDef bionic_anus = HediffDef.Named("BionicAnus");
		public static HediffDef archotech_anus = HediffDef.Named("ArchotechAnus");

		public static HediffDef peg_penis = HediffDef.Named("PegDick");

		public static HediffDef insect_anus = HediffDef.Named("InsectAnus");
		public static HediffDef ovipositorM = HediffDef.Named("OvipositorM");
		public static HediffDef ovipositorF = HediffDef.Named("OvipositorF");

		public static HediffDef demonT_penis = HediffDef.Named("DemonTentaclePenis");
		public static HediffDef demon_penis = HediffDef.Named("DemonPenis");
		public static HediffDef demon_vagina = HediffDef.Named("DemonVagina");
		public static HediffDef demon_anus = HediffDef.Named("DemonAnus");

		public static HediffDef slime_breasts = HediffDef.Named("SlimeBreasts");
		public static HediffDef slime_penis = HediffDef.Named("SlimeTentacles");
		public static HediffDef slime_vagina = HediffDef.Named("SlimeVagina");
		public static HediffDef slime_anus = HediffDef.Named("SlimeAnus");

		public static HediffDef feline_penis = HediffDef.Named("CatPenis");
		public static HediffDef feline_vagina = HediffDef.Named("CatVagina");

		public static HediffDef canine_penis = HediffDef.Named("DogPenis");
		public static HediffDef canine_vagina = HediffDef.Named("DogVagina");

		public static HediffDef equine_penis = HediffDef.Named("HorsePenis");
		public static HediffDef equine_vagina = HediffDef.Named("HorseVagina");

		public static HediffDef dragon_penis = HediffDef.Named("DragonPenis");
		public static HediffDef dragon_vagina = HediffDef.Named("DragonVagina");

		public static HediffDef raccoon_penis = HediffDef.Named("RaccoonPenis");

		public static HediffDef hemipenis = HediffDef.Named("Hemipenis");

		public static HediffDef crocodilian_penis = HediffDef.Named("CrocodilianPenis");

		public static List<HediffDef> penise = new List<HediffDef> { generic_penis,
			feline_penis, canine_penis, equine_penis, dragon_penis, raccoon_penis, hemipenis,
			ovipositorM, crocodilian_penis, average_penis, micro_penis, small_penis, big_penis,
			huge_penis, peg_penis, hydraulic_penis, bionic_penis, archotech_penis };

		public static List<HediffDef> vaginae = new List<HediffDef> { generic_vagina,
			feline_vagina, canine_vagina, equine_vagina, dragon_vagina, ovipositorF,
			average_vagina, micro_vagina, tight_vagina, loose_vagina, gaping_vagina,
			hydraulic_vagina, bionic_vagina, archotech_vagina };

		public static BodyPartRecord get_genitals(Pawn pawn)
		{
			//--Log.Message("Genital_Helper::get_genitals( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = pawn?.RaceProps.body.AllParts.Find(bpr => bpr.def.defName == "Genitals");

			if (Part == null)
			{
				//--Log.Message("[RJW] get_genitals( " + xxx.get_pawnname(pawn) + " ) - Part is null");
				return null;
			}
			return Part;
		}

		public static BodyPartRecord get_breasts(Pawn pawn)
		{
			//--Log.Message("[RJW] get_breasts( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = pawn?.RaceProps.body.AllParts.Find(bpr => bpr.def.defName == "Chest");

			if (Part == null)
			{
				//--Log.Message("[RJW] get_breasts( " + xxx.get_pawnname(pawn) + " ) - Part is null");
				return null;
			}
			return Part;
		}

		public static BodyPartRecord get_mouth(Pawn pawn)
		{
			//--Log.Message("[RJW] get_mouth( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = pawn?.RaceProps.body.AllParts.Find(bpr => bpr.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"));

			if (Part == null)
			{
				//--Log.Message("[RJW] get_mouth( " + xxx.get_pawnname(pawn) + " ) - Part is null");
				return null;
			}
			return Part;
		}

		public static BodyPartRecord get_anus(Pawn pawn)
		{
			//--Log.Message("[RJW] get_anus( " + xxx.get_pawnname(pawn) + " ) called");
			BodyPartRecord Part = pawn?.RaceProps.body.AllParts.Find(bpr => bpr.def.defName == "Anus");

			if (Part == null)
			{
				//--Log.Message("[RJW] get_anus( " + xxx.get_pawnname(pawn) + " ) - Part is null");
				return null;
			}
			return Part;
		}

		public static bool breasts_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_breasts))
						return true;
				}
			}
			return false;
		}

		public static bool anus_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_anus))
						return true;
				}
			}
			return false;
		}

		public static bool genitals_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_penis || gear_def.blocks_vagina))
						return true;
				}
			}
			return false;
		}

		public static bool hands_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_hands))
						return true;
				}
			}
			return false;
		}

		public static bool penis_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_penis))
						return true;
				}
			}
			return false;
		}

		public static bool oral_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_oral))
						return true;
				}
			}
			return false;
		}

		public static bool vagina_blocked(Pawn pawn)
		{
			if (pawn.apparel != null)
			{
				foreach (var app in pawn.apparel.WornApparel)
				{
					if ((app.def is bondage_gear_def gear_def) && (gear_def.blocks_vagina))
						return true;
				}
			}
			return false;
		}

		public static bool has_genitals(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
												  (hed.Part == Part) &&
												  (hed is Hediff_Implant || hed is Hediff_AddedPart));
		}

		public static bool has_breasts(Pawn pawn)
		{
			BodyPartRecord Part = get_breasts(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
				(hed.Part == Part) &&
				(hed is Hediff_Implant || hed is Hediff_AddedPart) &&
				!hed.def.defName.ToLower().Contains("featureless"));
		}

		public static bool has_male_breasts(Pawn pawn)
		{
			BodyPartRecord Part = get_breasts(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
				(hed.Part == Part) &&
				(hed is Hediff_Implant || hed is Hediff_AddedPart) && hed.def == flat_breasts);
		}

		public static bool has_mouth(Pawn pawn)
		{
			BodyPartRecord Part = get_mouth(pawn);
			if (Part is null)
				return false;

			return !pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) && (hed is Hediff_MissingPart));
		}

		public static bool has_anus(Pawn pawn)
		{
			BodyPartRecord Part = get_anus(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) && (hed is Hediff_Implant || hed is Hediff_AddedPart));
		}

		public static bool has_penis(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("penis") || hed.def.defName.ToLower().Contains("ovipositorm")));
		}

		public static bool has_multipenis(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return (pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("hemipenis")))
						   ||
					pawn.health.hediffSet.hediffs.Count((Hediff hed) =>
						   (hed.Part == Part) && (
						   (hed.def.defName.ToLower().Contains("penis")) ||
						   (hed.def.defName.ToLower().Contains("pegdick")) ||
						   (hed.def.defName.ToLower().Contains("tentacle"))
						   )) > 1);
		}

		public static bool has_penis_infertile(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("pegdick") || hed.def.defName.ToLower().Contains("tentacle")));
		}

		public static bool has_ovipositorF(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("ovipositorf")));

		}
		public static bool has_ovipositorM(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("ovipositorm")));
		}

		public static bool has_vagina(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return false;

			return pawn.health.hediffSet.hediffs.Any((Hediff hed) =>
						   (hed.Part == Part) &&
						   (hed.def.defName.ToLower().Contains("vagina") || hed.def.defName.ToLower().Contains("ovipositorf")));
		}

		public static Hediff get_penis(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.Find((Hediff hed) =>
						   (hed.Part == Part) && penise.Contains(hed.def));
		}

		public static Hediff get_penis_all(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.Find((Hediff hed) =>
							(hed.Part == Part) &&
							(!hed.def.defName.ToLower().Contains("vagina")) &&
							(penise.Contains(hed.def) ||
							hed.def.defName.ToLower().Contains("penis") ||
							hed.def.defName.ToLower().Contains("pegdick") ||
							hed.def.defName.ToLower().Contains("ovipositorm") ||
							hed.def.defName.ToLower().Contains("tentacle")));
		}

		public static Hediff get_vagina(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.Find((Hediff hed) =>
						   (hed.Part == Part) && vaginae.Contains(hed.def));
		}

		public static Hediff get_vagina_all(Pawn pawn)
		{
			BodyPartRecord Part = get_genitals(pawn);
			if (Part is null)
				return null;

			return pawn.health.hediffSet.hediffs.Find((Hediff hed) =>
							(hed.Part == Part) &&
							(vaginae.Contains(hed.def) ||
							hed.def.defName.ToLower().Contains("ovipositorf")));
		}

		public static bool is_futa(Pawn pawn)
		{
			return (has_penis(pawn) && has_vagina(pawn));
		}
	}
}


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

namespace rjw
{
	public class HediffHelper
	{
		/// <summary>
		/// Creates hediff with custom hediff class
		/// </summary>
		/// <typeparam name="T">hediff class. Should be class defined by hediff or subclass of that class</typeparam>
		/// <param name="def">hediffDef</param>
		/// <param name="pawn">owner of newly created hediff</param>
		/// <param name="partRecord">bodypart heduff assigned to</param>
		/// <returns>newly created hediff</returns>
		public static T MakeHediff<T>(HediffDef def, Pawn pawn, BodyPartRecord partRecord = null) where T : Hediff
		{
			if (!def.hediffClass.IsAssignableFrom(typeof(T)))
			{
				throw new InvalidOperationException($"trying to create hediff with incompatible class: {typeof(T).Name} is not a {def.hediffClass.Name} or its subclass");
			}

			T hediff = (T)Activator.CreateInstance(typeof(T));
			hediff.def = def;
			hediff.pawn = pawn;
			hediff.Part = partRecord;
			hediff.loadID = Find.UniqueIDsManager.GetNextHediffID();
			hediff.PostMake();

			return hediff;
		}

		/// <summary>
		/// Fill existing hediff class with data
		/// </summary>
		/// <typeparam name="T">hediff class. Should be class defined by hediff or subclass of that class</typeparam>
		/// <param name="def">hediffDef</param>
		/// <param name="hediff">empty hediff instance</Hediff>
		/// <param name="pawn">owner of newly created hediff</param>
		/// <param name="partRecord">bodypart heduff assigned to</param>
		/// <returns>newly created hediff</returns>
		public static T FillHediff<T>(HediffDef def, T hediff, Pawn pawn, BodyPartRecord partRecord = null) where T : Hediff
		{
			if (!def.hediffClass.IsAssignableFrom(typeof(T)))
			{
				throw new InvalidOperationException($"trying to create hediff with incompatible class: {typeof(T).Name} is not a {def.hediffClass.Name} or its subclass");
			}

			hediff.def = def;
			hediff.pawn = pawn;
			hediff.Part = partRecord;
			hediff.loadID = Find.UniqueIDsManager.GetNextHediffID();
			hediff.PostMake();

			return hediff;
		}
	}
}


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