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

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

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

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

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

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

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

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

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

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

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

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

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

			return new nymph_passion_chances(maj, min);
		}

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

			tr.child = child.vatgrown_sex_slave;

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

			var beauty = 0;
			var rv = Rand.Value;
			var rv2 = Rand.Value;
			if (rv < 0.300)
			{
				tr.adult = adult.feisty;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Brawler));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.Bloodlust));
				else
					tr.traits.Add(new Trait(xxx.rapist));
			}
/* removed in B19?
 to remove later from cs
			else if (rv < 0.475)
			{
				tr.adult = adult.curious;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDef.Named("Prosthophile")));
			}
*/
			else if (rv < 0.650)
			{
				tr.adult = adult.tender;
				beauty = Rand.RangeInclusive(1, 2);
				if (rv2 < 0.50)
					tr.traits.Add(new Trait(TraitDefOf.Kind));
			}
			else if (rv < 0.825)
			{
				tr.adult = adult.chatty;
				beauty = 2;
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.Greedy));
			}
			else
			{
				tr.adult = adult.broken;
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 1));
				else if (rv2 < 0.67)
					tr.traits.Add(new Trait(TraitDefOf.DrugDesire, 2));
			}

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

			return tr;
		}
	}
}


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

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

		public static Gender setnymphgender()
		{
			//with males 100% its still 99%, coz im  to lazy to fix it
			//float rnd = Rand.Value;
			float chance = RJWSettings.male_nymph_chance;

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

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

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

			// add broken body to broken nymph
			if (pawn.story.adulthood == nymph_backstories.adult.broken)
			{
				BodyPartRecord torso = pawn.RaceProps.body.AllParts.Find((bpr) => String.Equals(bpr.def.defName, "Torso"));
				pawn.health.AddHediff(xxx.feelingBroken, torso);
				(pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken)).Severity = Rand.Range(0.4f, 1.0f);
			}

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

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

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

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

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

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

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

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

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

		// Apply nymphoverrides for generated pawn
		public static void nymph_override(Pawn pawn)
		{
			set_story(pawn);
			set_skills(pawn);
		}

		public static Pawn spawn_nymph(IntVec3 around_loc, ref Map map, Faction faction = null)
		{
			PawnKindDef pkd;
			{
				pkd = PawnKindDef.Named("Nymph");
				pkd.minGenerationAge = 18;
				pkd.maxGenerationAge = 27;
			}
			PawnGenerationRequest request = new PawnGenerationRequest(pkd,
												 faction,
												 PawnGenerationContext.NonPlayer,
												 map.Tile,	// tile(default is -1), since Inhabitant is true, then tile should have a value here; otherwise an error will pop
												 false, // Force generate new pawn
												 false, // Newborn
												 false, // Allow dead
												 false, // Allow downed
												 false, // Can generate pawn relations
												 false, // Must be capable of violence
												 0.0f, // Colonist relation chance factor
												 false, // Force add free warm layer if needed
												 true, // Allow gay
												 true, // Allow food
												 true, // Inhabitant
												 false, // Been in Cryosleep
												 false, // ForceRedressWorldPawnIfFormerColonist
												 false, // WorldPawnFactionDoesntMatter
												 c => (c.story.bodyType == BodyTypeDefOf.Female) || (c.story.bodyType == BodyTypeDefOf.Thin), // ValidatorPreGear
												 c => (c.story.bodyType == BodyTypeDefOf.Female) || (c.story.bodyType == BodyTypeDefOf.Thin), // ValidatorPostGear
												 null, // MinChanceToRedressWorldPawn
												 null, // Fixed biological age
												 null, // Fixed chronological age
												 setnymphgender(), // Fixed gender
												 null, // Fixed melanin
												 null); // Fixed last name

			IntVec3 spawn_loc = CellFinder.RandomClosewalkCellNear(around_loc, map, 6);//RandomSpawnCellForPawnNear could be an alternative

			//Log.Message("[RJW] spawn_nymph1: " + request.FixedGender);

			//generate random pawn
			Pawn pawn = rjw_PawnGenerator.GeneratePawn(request);

			//i have no fucking idea why, but core PawnGenerator always generate females ignoring request settings
			//Pawn pawn = PawnGenerator.GeneratePawn(request);
			//Log.Message("[RJW] spawn_nymph3: " + pawn.gender);

			//override random generated pawn stuff with nymph stuff
			nymph_override(pawn);
			GenSpawn.Spawn(pawn, spawn_loc, map);
			return pawn;
		}
	}
}

repo_name: chippy/rjw

license: mit


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

namespace rjw
{
	public class Recipe_InstallAnus : Recipe_InstallPrivates
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.anus_blocked(p);
			foreach (var part in base.GetPartsToApplyOn(p, r))
				if ((!gen_blo) || (part != xxx.anus))
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallBreasts : Recipe_InstallPrivates
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.breasts_blocked(p);
			foreach (var part in base.GetPartsToApplyOn(p, r))
				if ((!gen_blo) || (part != xxx.breasts))
					yield return part;
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallGenitals : Recipe_InstallPrivates
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			var gen_blo = Genital_Helper.genitals_blocked(p);
			foreach (var part in base.GetPartsToApplyOn(p, r))
				if ((!gen_blo) || (part != xxx.genitals))
					yield return part;

		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallPrivates : rjw_CORE_EXPOSED.Recipe_InstallArtificialBodyPart
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill) {
			GenderHelper.Sex before = GenderHelper.GetSex(pawn);

			base.ApplyOnPawn(pawn, part, billDoer, ingredients, bill);

			GenderHelper.Sex after = GenderHelper.GetSex(pawn);

			if (before!=after)
				GenderHelper.ChangeSex(pawn, before, after);
		}
	}
}


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

namespace rjw
{
    public class Recipe_Abortion : RecipeWorker
    {

		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			BodyPartRecord part = pawn.RaceProps.body.corePart;
			if (recipe.appliedOnFixedBodyParts[0] != null)
				part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null)
			{
				if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy"), true) && recipe.removesHediff == HediffDef.Named("RJW_pregnancy"))
				{
					Hediff_HumanlikePregnancy pregnancy = (Hediff_HumanlikePregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy"));
					if (pregnancy.is_checked)
						yield return part;
				}

				else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_beast"), true) && recipe.removesHediff == HediffDef.Named("RJW_pregnancy_beast"))
				{
					Hediff_BestialPregnancy pregnancy = (Hediff_BestialPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_beast"));
					if (pregnancy.is_checked)
						yield return part;
				}

				else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech"), true) && recipe.removesHediff == HediffDef.Named("RJW_pregnancy_mech"))
				{
					Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
					if (pregnancy.is_checked)
						yield return part;
				}
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_InstallChastityBelt : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			return base.GetPartsToApplyOn(p, r);
		}
	}

	public class Recipe_UnlockChastityBelt : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			return base.GetPartsToApplyOn(p, r);
		}
	}
}


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

namespace rjw
{
	public class Recipe_ClaimChild :  RecipeWorker
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			if (pawn != null)//I have no idea how it works but Recipe_ShutDown : RecipeWorker does not return values when not applicable
			{
				//Log.Message("RJW Claim child check on " + pawn);
				if (xxx.is_human(pawn) && !pawn.IsColonist)
				{
					if ( (pawn.ageTracker.CurLifeStageIndex < 2))//Guess it is hardcoded for now to `baby` and `toddler` of standard 4 stages of human life
					{
						BodyPartRecord brain = pawn.health.hediffSet.GetBrain();
						if (brain != null)
						{
							//Log.Message("RJW Claim child is applicable");
							yield return brain;
						}
					}
				}
			}
		}

		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
		{
			if (pawn == null)
			{
				Log.Error("Error applying medical recipe, pawn is null");
				return;
			}
			pawn.SetFaction(Faction.OfPlayer);
			//we could do
			//pawn.SetFaction(billDoer.Faction);
			//but that is useless because GetPartsToApplyOn does not support factions anyway and all recipes are hardcoded to player.
		}
	}
}


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

namespace rjw
{
    public class Recipe_DeterminePregnancy : RecipeWorker
    {
        public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
        {
            BodyPartRecord part = pawn.RaceProps.body.corePart;
            if (recipe.appliedOnFixedBodyParts[0] != null)
                part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null && (pawn.ageTracker.CurLifeStage.reproductive)
				|| pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy"), true)
				|| pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_beast"), true)
				|| pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech"), true)
				)
			{
				yield return part;
			}
        }

        public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
        {
            if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy")))
            {
                Hediff_HumanlikePregnancy pregnancy = (Hediff_HumanlikePregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy"));
				pregnancy.CheckPregnancy();
            }

            else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_beast")))
            {
                Hediff_BestialPregnancy pregnancy = (Hediff_BestialPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_beast"));
				pregnancy.CheckPregnancy();
            }

            else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech")))
            {
                Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
				pregnancy.CheckPregnancy();
            }

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


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

namespace rjw
{
	public class Recipe_ForceOffGear : Recipe_Surgery
	{
		public static bool is_wearing(Pawn p, ThingDef apparel_def)
		{
			if (p.apparel != null)
				foreach (var app in p.apparel.WornApparel)
					if (app.def == apparel_def)
						return true;
			return false;
		}

		public static BodyPartRecord find_part_record(BodyPartDef part_def, Pawn p)
		{
			return p.RaceProps.body.AllParts.Find((BodyPartRecord bpr) => bpr.def == part_def);
		}

		// Puts the recipe in the operations list only if "p" is wearing the relevant apparel. The little trick here is that yielding
		// null causes the game to put the recipe in the list but not actually apply it to a body part.
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef generic_def)
		{
			var r = (force_off_gear_def)generic_def;
			if (is_wearing(p, r.removes_apparel))
				yield return null;
		}

		public static void apply_burns(Pawn p, List<BodyPartDef> parts, float min_severity, float max_severity)
		{
			foreach (var part in parts)
			{
				var rec = find_part_record(part, p);
				if (rec != null)
				{
					var to_deal = Rand.Range(min_severity, max_severity) * part.GetMaxHealth(p);
					var dealt = 0.0f;
					var counter = 0;
					while ((counter < 100) && (dealt < to_deal) && (!p.health.hediffSet.PartIsMissing(rec)))
					{
						var dam = Rand.RangeInclusive(3, 5);
						p.TakeDamage(new DamageInfo(DamageDefOf.Burn, dam, 999, -1.0f, null, rec, null));
						++counter;
						dealt += (float)dam;
					}
				}
			}
		}

		public override void ApplyOnPawn(Pawn p, BodyPartRecord null_part, Pawn surgeon, List<Thing> ingredients,Bill bill)
		{
			var r = (force_off_gear_def)recipe;
			if ((surgeon != null) &&
				(p.apparel != null) &&
				(!CheckSurgeryFail(surgeon, p, ingredients, find_part_record(r.failure_affects, p),bill)))
			{
				// Remove apparel
				foreach (var app in p.apparel.WornApparel)
					if (app.def == r.removes_apparel)
					{
						p.apparel.Remove(app);
						break;
					}

				// Destroy parts
				var def_to_destroy = r.destroys_one_of.RandomElement<BodyPartDef>();
				if (def_to_destroy != null)
				{
					var record_to_destroy = find_part_record(def_to_destroy, p);
					if (record_to_destroy != null)
					{
						var dam = (int)(1.5f * def_to_destroy.GetMaxHealth(p));
						p.TakeDamage(new DamageInfo(DamageDefOf.Burn, dam, 999, -1.0f, null, record_to_destroy, null));
					}
				}

				if (r.major_burns_on != null)
					apply_burns(p, r.major_burns_on, 0.30f, 0.60f);
				if (r.minor_burns_on != null)
					apply_burns(p, r.minor_burns_on, 0.15f, 0.35f);
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// IUD - prevent pregnancy
	/// </summary>
	public class Recipe_InstallIUD : Recipe_InstallImplantToExistParts
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			if (!xxx.is_female(pawn))
			{
				return new List<BodyPartRecord>();
			}
			return base.GetPartsToApplyOn(pawn, recipe);
		}
	}
}


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

namespace rjw
{
    public class Recipe_PregnancyHackMech : RecipeWorker
    {
        public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
        {
            BodyPartRecord part = pawn.RaceProps.body.corePart;
            if (recipe.appliedOnFixedBodyParts[0] != null)
                part = pawn.RaceProps.body.AllParts.Find(x => x.def == recipe.appliedOnFixedBodyParts[0]);
			if (part != null && pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech"), part, true))
			{
				Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
				//Log.Message("RJW_pregnancy_mech hack check: " + pregnancy.is_checked);
				if (pregnancy.is_checked)
					yield return part;
			}
        }

        public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
        {

            if (pawn.health.hediffSet.HasHediff(HediffDef.Named("RJW_pregnancy_mech")))
            {
                Hediff_MechanoidPregnancy pregnancy = (Hediff_MechanoidPregnancy)pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("RJW_pregnancy_mech"));
				pregnancy.Hack();
            }
        }
    }
}


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

namespace rjw
{
	/// <summary>
	/// Sterilization
	/// </summary>
	public class Recipe_InstallImplantToExistParts : Recipe_InstallImplant
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			for (int i = 0; i < recipe.appliedOnFixedBodyParts.Count; i++)
			{
				BodyPartDef part = recipe.appliedOnFixedBodyParts[i];
				List<BodyPartRecord> bpList = pawn.RaceProps.body.AllParts;
				for (int j = 0; j < bpList.Count; j++)
				{
					BodyPartRecord record = bpList[j];
					if (record.def == part)
					{
						if (pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined).Contains(record))
						{
							if (!pawn.health.hediffSet.hediffs.Any((Hediff x) => x.Part == record && x.def == recipe.addsHediff))
							{
								yield return record;
							}
						}
					}
				}
			}
			yield break;
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveAnus : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_anus(p))
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) &&
						((part != xxx.anus) || (!Genital_Helper.anus_blocked(p))))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveBreasts : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_breasts(p))
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) &&
						((part != xxx.breasts) || (!Genital_Helper.breasts_blocked(p))))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveGenitals : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_genitals(p))
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) &&
						((part != xxx.genitals) || (!Genital_Helper.genitals_blocked(p))))
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemovePart : rjw_CORE_EXPOSED.Recipe_RemoveBodyPart
	{
		// Quick and dirty method to guess whether the player is harvesting the genitals or amputating them
		// due to infection. The core code can't do this properly because it considers the private part
		// hediffs as "unclean".
		public bool is_harvest(Pawn p, BodyPartRecord part)
		{
			foreach (var hed in p.health.hediffSet.hediffs)
				if ((hed.Part == part) && hed.def.isBad && (hed.Severity >= 0.70f))
					return false;

			return true;
		}

		public override void ApplyOnPawn(Pawn p, BodyPartRecord part, Pawn doer, List<Thing> ingredients, Bill bill)
		{
			var har = is_harvest(p, part);

			base.ApplyOnPawn(p, part, doer, ingredients, bill);

			if (har)
			{
				if (!p.Dead)
					ThoughtUtility.GiveThoughtsForPawnOrganHarvested(p);
				else
					ThoughtUtility.GiveThoughtsForPawnExecuted(p, PawnExecutionKind.OrganHarvesting);
			}
		}

		public override string GetLabelWhenUsedOn(Pawn p, BodyPartRecord part)
		{
			return recipe.label.CapitalizeFirst();
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemovePenis : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if ((Genital_Helper.has_penis(p) || Genital_Helper.has_penis_infertile(p) || Genital_Helper.has_ovipositorF(p)) && (!Genital_Helper.genitals_blocked(p)))
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) &&	(part != xxx.genitals))
						//yield return Genital_Helper.get_penis(p);
						//i guess this thing needs to be rewritten ro work
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_RemoveVagina : Recipe_RemovePart
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn p, RecipeDef r)
		{
			if (Genital_Helper.has_vagina(p) && (!Genital_Helper.genitals_blocked(p)))
			{
				foreach (var part in p.health.hediffSet.GetNotMissingParts())
					if (r.appliedOnFixedBodyParts.Contains(part.def) && (part != xxx.genitals))
						//yield return Genital_Helper.get_vagina(p);
						//i guess this thing needs to be rewritten ro work
						yield return part;
			}
		}
	}
}


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

namespace rjw
{
	public class Recipe_MakeFuta : Recipe_InstallArtificialBodyPart
	{
		public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill) {
			GenderHelper.Sex before = GenderHelper.GetSex(pawn);

			if (!base.CheckSurgeryFail(billDoer, pawn, ingredients, part, bill))
			{
				//Log.Message("Recipe_MakeFuta::CheckSurgery( " + xxx.get_pawnname(pawn) + " ) Succsess");
				var heddiff = bill.recipe.addsHediff;
				pawn.health.AddHediff(heddiff, part);
				var recipe = bill.recipe;
				foreach (var item in ingredients)
				{
					if (!item.Destroyed)
						base.ConsumeIngredient(item, recipe, item.Map);
				}
			}
			else
			{
				//Log.Message("Recipe_MakeFuta::CheckSurgery( " + xxx.get_pawnname(pawn) + " ) Fail");
				Messages.Message(xxx.get_pawnname(billDoer) + " failed to keep both parts on " + xxx.get_pawnname(pawn) + ".", MessageTypeDefOf.NegativeHealthEvent);

				//ED86:
				//if enabled - replaces part with new one, old one spawned near
				//if disabled - keeps current part, new one consumed
				//ED86.2:
				//50% to keep existing part, or replace with new part
				//if (Rand.Value < .5f)
				//	base.ApplyOnPawn(pawn, part, billDoer, ingredients, bill);
			}

			GenderHelper.Sex after = GenderHelper.GetSex(pawn);
			GenderHelper.ChangeSex(pawn, before, after);
		}
	}
}


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

namespace rjw
{
	public class Recipe_MakeFutaF : Recipe_MakeFuta
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			var gen_blo = Genital_Helper.genitals_blocked(pawn);
			var has_vag = Genital_Helper.has_vagina(pawn);
			var has_cock = Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || Genital_Helper.has_ovipositorF(pawn);

			foreach (var part in base.GetPartsToApplyOn(pawn, recipe))
				if ((part != xxx.genitals) ||
					(!gen_blo && (has_vag && !has_cock)))
					yield return part;

		}
	}
}


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

namespace rjw
{
	public class Recipe_MakeFutaM : Recipe_MakeFuta
	{
		public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
		{
			var gen_blo = Genital_Helper.genitals_blocked(pawn);
			var has_vag = Genital_Helper.has_vagina(pawn);
			var has_cock = Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || Genital_Helper.has_ovipositorF(pawn);

			foreach (var part in base.GetPartsToApplyOn(pawn, recipe))
				if ((part != xxx.genitals) ||
					(!gen_blo && (!has_vag && has_cock)))
					yield return part;

		}
	}
}


code:
using System;
using HugsLib.Settings;
using UnityEngine;
using Verse;

namespace rjw.Properties {

    // This class allows you to handle specific events on the settings class:
    //  The SettingChanging event is raised before a setting's value is changed.
    //  The PropertyChanged event is raised after a setting's value is changed.
    //  The SettingsLoaded event is raised after the setting values are loaded.
    //  The SettingsSaving event is raised before the setting values are saved.
    internal sealed partial class Settings {

        public Settings() {
            // // To add event handlers for saving and changing settings, uncomment the lines below:
            //
            // this.SettingChanging += this.SettingChangingEventHandler;
            //
            // this.SettingsSaving += this.SettingsSavingEventHandler;
            //
        }

        private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
            // Add code to handle the SettingChangingEvent event here.
        }

        private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
            // Add code to handle the SettingsSaving event here.
        }

		private static readonly Color SelectedOptionColor = new Color(0.5f, 1f, 0.5f, 1f);

		public static bool CustomDrawer_Tabs(Rect rect, SettingHandle<string> selected, string defaultValues)
		{
			int labelWidth = 140;
			//int offset = -287;
			int offset = 0;
			bool change = false;

				Rect buttonRect = new Rect(rect)
				{
					width = labelWidth
				};
				buttonRect.position = new Vector2(buttonRect.position.x + offset, buttonRect.position.y);
				Color activeColor = GUI.color;
				bool isSelected = defaultValues == selected.Value;
				if (isSelected)
					GUI.color = SelectedOptionColor;
				bool clicked = Widgets.ButtonText(buttonRect, defaultValues);
				if (isSelected)
					GUI.color = activeColor;

				if (clicked)
				{
					selected.Value = selected.Value != defaultValues ? defaultValues : "none";
					change = true;
				}

				offset += labelWidth;
			return change;
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw
{
	public class RJWDebugSettings : ModSettings
	{
		public static bool submit_button_enabled = true;
		public static bool show_RJW_designation_box = true;

		public static bool DevMode = false;
		public static bool WildMode = false;
		public static bool GenderlessAsFuta = false;

		public static void DoWindowContents(Rect inRect)
		{
			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.ColumnWidth = inRect.width / 2.05f;
			listingStandard.Begin(inRect);
				listingStandard.Gap(4f);
				listingStandard.CheckboxLabeled("submit_button_enabled".Translate(), ref submit_button_enabled, "submit_button_enabled_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("RJW_designation_box".Translate(), ref show_RJW_designation_box, "RJW_designation_box_desc".Translate());
				listingStandard.Gap(5f);
				//listingStandard.Gap(30f);

			listingStandard.NewColumn();

				GUI.contentColor = Color.yellow;
				listingStandard.CheckboxLabeled("DevMode_name".Translate(), ref DevMode, "DevMode_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("WildMode_name".Translate(), ref WildMode, "WildMode_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("GenderlessAsFuta_name".Translate(), ref GenderlessAsFuta, "GenderlessAsFuta_desc".Translate());
				GUI.contentColor = Color.white;

			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref submit_button_enabled, "submit_button_enabled", true, true);
			Scribe_Values.Look(ref show_RJW_designation_box, "show_RJW_designation_box", true, true);
			Scribe_Values.Look(ref DevMode, "DevMode", false, true);
			Scribe_Values.Look(ref GenderlessAsFuta, "GenderlessAsFuta", false, true);
			Scribe_Values.Look(ref WildMode, "Wildmode", false, true);
		}
	}
}


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

namespace rjw
{
	// TODO: Add option for logging pregnancy chance after sex (dev mode only?)
	// TODO: Add an alernate more complex system for pregnancy calculations, by using bodyparts, genitalia, and size (similar species -> higher chance, different -> lower chance)

	// TODO: Old settings that are not in use - evalute if they're needed and either convert these to settings, or delete them.
	/*public bool std_show_roll_to_catch; // Updated
	public float std_min_severity_to_pitch; // Updated
	public float std_env_pitch_cleanliness_exaggeration; // Updated
	public float std_env_pitch_dirtiness_exaggeration; // Updated
	public float std_outdoor_cleanliness; // Updated
	public float significant_pain_threshold; // Updated
	public float extreme_pain_threshold; // Updated
	public float base_chance_to_hit_prisoner; // Updated
	public int min_ticks_between_hits; // Updated
	public int max_ticks_between_hits; // Updated
	public float max_nymph_fraction; // Updated
	public float opp_inf_initial_immunity; // Updated
	public float comfort_prisoner_rape_mtbh_mul; // Updated
	public float whore_mtbh_mul; // Updated
	public float nymph_spawn_with_std_mul; // Updated
	public static bool comfort_prisoners_enabled; // Updated //this one is in config.cs as well!
	public static bool ComfortColonist; // New
	public static bool ComfortAnimal; // New
	public static bool rape_me_sticky_enabled; // Updated
	public static bool bondage_gear_enabled; // Updated
	public static bool nymph_joiners_enabled; // Updated
	public static bool always_accept_whores; // Updated
	public static bool nymphs_always_JoinInBed; // Updated
	public static bool zoophis_always_rape; // Updated
	public static bool rapists_always_rape; // Updated
	public static bool pawns_always_do_fapping; // Updated
	public static bool whores_always_findjob; // Updated
	public bool show_regular_dick_and_vag; // Updated
	*/

	public class RJWPreferenceSettings : ModSettings
	{
		public static float vaginal = 1.20f;
		public static float anal = 0.80f;
		public static float fellatio = 0.80f;
		public static float cunnilingus = 0.80f;
		public static float rimming = 0.40f;
		public static float double_penetration = 0.60f;
		public static float breastjob = 0.50f;
		public static float handjob = 0.80f;
		public static float mutual_masturbation = 0.70f;
		public static float fingering = 0.50f;
		public static float footjob = 0.30f;
		public static float scissoring = 0.50f;
		public static float fisting = 0.30f;
		public static float sixtynine = 0.69f;

		public static float asexual_ratio = 0.02f;
		public static float pansexual_ratio = 0.03f;
		public static float heterosexual_ratio = 0.7f;
		public static float bisexual_ratio = 0.15f;
		public static float homosexual_ratio = 0.1f;

		public static Clothing sex_wear = Clothing.Nude;
		public static RapeAlert rape_alert_sound = RapeAlert.Enabled;

		public enum Clothing
		{
			Nude,
			Headgear,
			Clothed
		};

		public enum RapeAlert
		{
			Enabled,
			Colonists,
			Humanlikes,
			Disabled
		};

		public static void DoWindowContents(Rect inRect)
		{
			Listing_Standard listingStandard3 = new Listing_Standard();
			listingStandard3.ColumnWidth = inRect.width / 3.15f;
			listingStandard3.Begin(inRect);
			listingStandard3.Gap(4f);
			listingStandard3.Label("SexTypeFrequency".Translate());
			listingStandard3.Gap(6f);
			listingStandard3.Label("  " + "vaginal_sex".Translate() + ": " + Math.Round(vaginal * 100, 0), -1, "vaginal_sex_desc".Translate());
			vaginal = listingStandard3.Slider(vaginal, 0.01f, 3.0f);
			listingStandard3.Label("  " + "anal_sex".Translate() + ": " + Math.Round(anal * 100, 0), -1, "anal_sex_desc".Translate());
			anal = listingStandard3.Slider(anal, 0.01f, 3.0f);
			listingStandard3.Label("  " + "double_penetrative_sex".Translate() + ": " + Math.Round(double_penetration * 100, 0), -1, "double_penetrative_desc".Translate());
			double_penetration = listingStandard3.Slider(double_penetration, 0.01f, 3.0f);
			listingStandard3.Label("  " + "fellatio_sex".Translate() + ": " + Math.Round(fellatio * 100, 0), -1, "fellatio_sex_desc".Translate());
			fellatio = listingStandard3.Slider(fellatio, 0.01f, 3.0f);
			listingStandard3.Label("  " + "cunnilingus_sex".Translate() + ": " + Math.Round(cunnilingus * 100, 0), -1, "cunnilingus_sex_desc".Translate());
			cunnilingus = listingStandard3.Slider(cunnilingus, 0.01f, 3.0f);
			listingStandard3.Label("  " + "rimming_sex".Translate() + ": " + Math.Round(rimming * 100, 0), -1, "rimming_sex_desc".Translate());
			rimming = listingStandard3.Slider(rimming, 0.01f, 3.0f);
			listingStandard3.Label("  " + "sixtynine".Translate() + ": " + Math.Round(sixtynine * 100, 0), -1, "sixtynine_desc".Translate());
			sixtynine = listingStandard3.Slider(sixtynine, 0.01f, 3.0f);
			listingStandard3.Gap(40f);
			if (listingStandard3.ButtonText("Reset".Translate()))
			{
				vaginal = 1.20f;
				anal = 0.80f;
				fellatio = 0.80f;
				cunnilingus = 0.80f;
				rimming = 0.40f;
				double_penetration = 0.60f;
				breastjob = 0.50f;
				handjob = 0.80f;
				mutual_masturbation = 0.70f;
				fingering = 0.50f;
				footjob = 0.30f;
				scissoring = 0.50f;
				fisting = 0.30f;
				sixtynine = 0.69f;
			}
			listingStandard3.NewColumn();
			listingStandard3.Gap(2f);
			listingStandard3.Label("  " + "breastjob".Translate() + ": " + Math.Round(breastjob * 100, 0), -1, "breastjob_desc".Translate());
			breastjob = listingStandard3.Slider(breastjob, 0.01f, 3.0f);
			listingStandard3.Label("  " + "handjob".Translate() + ": " + Math.Round(handjob * 100, 0), -1, "handjob_desc".Translate());
			handjob = listingStandard3.Slider(handjob, 0.01f, 3.0f);
			listingStandard3.Label("  " + "fingering".Translate() + ": " + Math.Round(fingering * 100, 0), -1, "fingering_desc".Translate());
			fingering = listingStandard3.Slider(fingering, 0.01f, 3.0f);
			listingStandard3.Label("  " + "fisting".Translate() + ": " + Math.Round(fisting * 100, 0), -1, "fisting_desc".Translate());
			fisting = listingStandard3.Slider(fisting, 0.01f, 3.0f);
			listingStandard3.Label("  " + "mutual_masturbation".Translate() + ": " + Math.Round(mutual_masturbation * 100, 0), -1, "mutual_masturbation_desc".Translate());
			mutual_masturbation = listingStandard3.Slider(mutual_masturbation, 0.01f, 3.0f);
			listingStandard3.Label("  " + "footjob".Translate() + ": " + Math.Round(footjob * 100, 0), -1, "footjob_desc".Translate());
			footjob = listingStandard3.Slider(footjob, 0.01f, 3.0f);
			listingStandard3.Label("  " + "scissoring".Translate() + ": " + Math.Round(scissoring * 100, 0), -1, "scissoring_desc".Translate());
			scissoring = listingStandard3.Slider(scissoring, 0.01f, 3.0f);
			listingStandard3.NewColumn();
			listingStandard3.Gap(4f);
			// TODO: Add translation
			if (listingStandard3.ButtonText("SexClothing".Translate() + sex_wear.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("SexClothingNude".Translate(), (() => sex_wear = Clothing.Nude), MenuOptionPriority.Default),
				  new FloatMenuOption("SexClothingHeadwear".Translate(), (() => sex_wear = Clothing.Headgear), MenuOptionPriority.Default),
				  new FloatMenuOption("SexClothingFull".Translate(), (() => sex_wear = Clothing.Clothed), MenuOptionPriority.Default)
				}));
			}
			listingStandard3.Gap(4f);
			if (listingStandard3.ButtonText("RapeSoundAlert".Translate() + rape_alert_sound.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("RapeSoundAlertAlways".Translate(), (() => rape_alert_sound = RapeAlert.Enabled), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertHumanlike".Translate(), (() => rape_alert_sound = RapeAlert.Humanlikes), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertColonist".Translate(), (() => rape_alert_sound = RapeAlert.Colonists), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertDisabled".Translate(), (() => rape_alert_sound = RapeAlert.Disabled), MenuOptionPriority.Default)
				}));
			}
			listingStandard3.Gap(26f);
			listingStandard3.Label("SexualitySpread1".Translate(), -1, "SexualitySpread2".Translate());
			if (xxx.RomanceDiversifiedIsActive)
				listingStandard3.Label("SexualitySpreadRationalRomance".Translate(), -1);
			else if (xxx.PsychologyIsActive)
				listingStandard3.Label("SexualitySpreadPsychology".Translate(), -1);
			else if (xxx.IndividualityIsActive)
				listingStandard3.Label("SexualitySpreadIndividuality".Translate(), -1);
			else
			{
				listingStandard3.Label("SexualityAsexual".Translate() + Math.Round((asexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityAsexual_desc".Translate());
				asexual_ratio = listingStandard3.Slider(asexual_ratio, 0.0f, 1.0f);
				listingStandard3.Label("SexualityPansexual".Translate() + Math.Round((pansexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityPansexual_desc".Translate());
				pansexual_ratio = listingStandard3.Slider(pansexual_ratio, 0.0f, 1.0f);
				listingStandard3.Label("SexualityHeterosexual".Translate() + Math.Round((heterosexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityHeterosexual_desc".Translate());
				heterosexual_ratio = listingStandard3.Slider(heterosexual_ratio, 0.0f, 1.0f);
				listingStandard3.Label("SexualityBisexual".Translate() + Math.Round((bisexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityBisexual_desc".Translate());
				bisexual_ratio = listingStandard3.Slider(bisexual_ratio, 0.0f, 1.0f);
				listingStandard3.Label("SexualityGay".Translate() + Math.Round((homosexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityGay_desc".Translate());
				homosexual_ratio = listingStandard3.Slider(homosexual_ratio, 0.0f, 1.0f);
			}

			listingStandard3.End();
		}

		public static float GetTotal()
		{
			return asexual_ratio + pansexual_ratio + heterosexual_ratio + bisexual_ratio + homosexual_ratio;
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref vaginal, "vaginal_frequency");
			Scribe_Values.Look(ref anal, "anal_frequency");
			Scribe_Values.Look(ref double_penetration, "double_penetration_frequency");
			Scribe_Values.Look(ref fellatio, "fellatio_frequency");
			Scribe_Values.Look(ref cunnilingus, "cunnilingus_frequency");
			Scribe_Values.Look(ref rimming, "rimming_frequency");
			Scribe_Values.Look(ref sixtynine, "sixtynine_frequency");
			Scribe_Values.Look(ref breastjob, "breastjob_frequency");
			Scribe_Values.Look(ref handjob, "handjob_frequency");
			Scribe_Values.Look(ref footjob, "footjob_frequency");
			Scribe_Values.Look(ref fingering, "fingering_frequency");
			Scribe_Values.Look(ref fisting, "fisting_frequency");
			Scribe_Values.Look(ref mutual_masturbation, "mutual_masturbation_frequency");
			Scribe_Values.Look(ref scissoring, "scissoring_frequency");
			Scribe_Values.Look(ref sex_wear, "sex_wear");
			Scribe_Values.Look(ref rape_alert_sound, "rape_alert_sound");
			Scribe_Values.Look(ref asexual_ratio, "asexual_ratio");
			Scribe_Values.Look(ref pansexual_ratio, "pansexual_ratio");
			Scribe_Values.Look(ref heterosexual_ratio, "heterosexual_ratio");
			Scribe_Values.Look(ref bisexual_ratio, "bisexual_ratio");
			Scribe_Values.Look(ref homosexual_ratio, "homosexual_ratio");
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw
{
	public class RJWPregnancySettings : ModSettings
	{
		public static bool humanlike_pregnancy_enabled = true;
		public static bool animal_pregnancy_enabled = true;
		public static bool bestial_pregnancy_enabled = true;
		public static bool insect_pregnancy_enabled = true;
		public static bool mechanoid_enabled = true;

		public static bool trait_filtering_enabled = true;
		public static bool use_parent_method = true;
		public static bool complex_interspecies = true;

		public static int animal_impregnation_chance = 25;
		public static int humanlike_impregnation_chance = 25;
		public static float interspecies_impregnation_modifier = 0.2f;
		public static float humanlike_DNA_from_mother = 0.5f;
		public static float bestial_DNA_from_mother = 1.0f;
		public static float fertility_endage_male = 1.2f;
		public static float fertility_endage_female_humanlike = 0.58f;
		public static float fertility_endage_female_animal = 0.96f;

		public static void DoWindowContents(Rect inRect)
		{
			Listing_Standard listingStandard2 = new Listing_Standard();
			listingStandard2.ColumnWidth = inRect.width / 2.05f;
			listingStandard2.Begin(inRect);
			listingStandard2.Gap(4f);
			listingStandard2.CheckboxLabeled("RJWH_pregnancy".Translate(), ref humanlike_pregnancy_enabled, "RJWH_pregnancy_desc".Translate());
			if (humanlike_pregnancy_enabled)
			{
				listingStandard2.Gap(5f);
				listingStandard2.CheckboxLabeled("  " + "genetic_trait_filter".Translate(), ref trait_filtering_enabled, "genetic_trait_filter_desc".Translate());
			}
			else
			{
				trait_filtering_enabled = false;
			}
			listingStandard2.Gap(5f);
			listingStandard2.CheckboxLabeled("RJWA_pregnancy".Translate(), ref animal_pregnancy_enabled, "RJWA_pregnancy_desc".Translate());
			listingStandard2.Gap(5f);
			listingStandard2.CheckboxLabeled("RJWB_pregnancy".Translate(), ref bestial_pregnancy_enabled, "RJWB_pregnancy_desc".Translate());
			listingStandard2.Gap(5f);
			listingStandard2.CheckboxLabeled("RJWI_pregnancy".Translate(), ref insect_pregnancy_enabled, "RJWI_pregnancy_desc".Translate());
			listingStandard2.Gap(12f);
			listingStandard2.CheckboxLabeled("UseParentMethod".Translate(), ref use_parent_method, "UseParentMethod_desc".Translate());
			listingStandard2.Gap(5f);
			if (use_parent_method)
			{
				if (humanlike_DNA_from_mother == 0.0f)
				{
					listingStandard2.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + "AlwaysFather".Translate(), -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard2.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}
				else if (humanlike_DNA_from_mother == 1.0f)
				{
					listingStandard2.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + "AlwaysMother".Translate(), -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard2.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}
				else
				{
					int value = (int)(humanlike_DNA_from_mother * 100);
					listingStandard2.Label("  " + "OffspringLookLikeTheirMother".Translate() + ": " + value + "%", -1f, "OffspringLookLikeTheirMother_desc".Translate());
					humanlike_DNA_from_mother = listingStandard2.Slider(humanlike_DNA_from_mother, 0.0f, 1.0f);
				}

				if (bestial_DNA_from_mother == 0.0f)
				{
					listingStandard2.Label("  " + "OffspringIsHuman".Translate() + ": " + "AlwaysFather".Translate(), -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard2.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}
				else if (bestial_DNA_from_mother == 1.0f)
				{
					listingStandard2.Label("  " + "OffspringIsHuman".Translate() + ": " + "AlwaysMother".Translate(), -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard2.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}
				else
				{
					int value = (int)(bestial_DNA_from_mother * 100);
					listingStandard2.Label("  " + "OffspringIsHuman".Translate() + ": " + value + "%", -1f, "OffspringIsHuman_desc".Translate());
					bestial_DNA_from_mother = listingStandard2.Slider(bestial_DNA_from_mother, 0.0f, 1.0f);
				}
			}
			else
				humanlike_DNA_from_mother = 100;
			listingStandard2.CheckboxLabeled("MechanoidImplanting".Translate(), ref mechanoid_enabled, "MechanoidImplanting_desc".Translate());
			listingStandard2.Gap(5f);
			listingStandard2.CheckboxLabeled("ComplexImpregnation".Translate(), ref complex_interspecies, "ComplexImpregnation_desc".Translate());
			listingStandard2.Gap(60f);
			GUI.contentColor = Color.cyan;
			listingStandard2.Label("Base pregnancy chances:");
			listingStandard2.Gap(5f);
			if (humanlike_pregnancy_enabled)
				listingStandard2.Label("  Humanlike/Humanlike (same race): " + humanlike_impregnation_chance + "%");
			else
				listingStandard2.Label("  Humanlike/Humanlike (same race): -DISABLED-");
			if (humanlike_pregnancy_enabled && !(humanlike_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard2.Label("  Humanlike/Humanlike (different race): " + Math.Round(humanlike_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard2.Label("  Humanlike/Humanlike (different race): -DEPENDS ON SPECIES-");
			else
				listingStandard2.Label("  Humanlike/Humanlike (different race): -DISABLED-");
			if (animal_pregnancy_enabled)
				listingStandard2.Label("  Animal/Animal (same race): " + animal_impregnation_chance + "%");
			else
				listingStandard2.Label("  Animal/Animal (same race): -DISABLED-");
			if (animal_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard2.Label("  Animal/Animal (different race): " + Math.Round(animal_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard2.Label("  Animal/Animal (different race): -DEPENDS ON SPECIES-");
			else
				listingStandard2.Label("  Animal/Animal (different race): -DISABLED-");
			if (RJWSettings.bestiality_enabled && bestial_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard2.Label("  Humanlike/Animal: " + Math.Round(animal_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard2.Label("  Humanlike/Animal: -DEPENDS ON SPECIES-");
			else
				listingStandard2.Label("  Humanlike/Animal: -DISABLED-");
			if (RJWSettings.bestiality_enabled && bestial_pregnancy_enabled && !(animal_impregnation_chance * interspecies_impregnation_modifier <= 0.0f) && !complex_interspecies)
				listingStandard2.Label("  Animal/Humanlike: " + Math.Round(humanlike_impregnation_chance * interspecies_impregnation_modifier, 1) + "%");
			else if (complex_interspecies)
				listingStandard2.Label("  Animal/Humanlike: -DEPENDS ON SPECIES-");
			else
				listingStandard2.Label("  Animal/Humanlike: -DISABLED-");
			GUI.contentColor = Color.white;

			listingStandard2.NewColumn();
			listingStandard2.Gap(4f);
			listingStandard2.Label("PregnantCoeffecientForHuman".Translate() + ": " + humanlike_impregnation_chance + "%", -1f, "PregnantCoeffecientForHuman_desc".Translate());
			humanlike_impregnation_chance = (int)listingStandard2.Slider(humanlike_impregnation_chance, 0.0f, 100f);
			listingStandard2.Label("PregnantCoeffecientForAnimals".Translate() + ": " + animal_impregnation_chance + "%", -1f, "PregnantCoeffecientForAnimals_desc".Translate());
			animal_impregnation_chance = (int)listingStandard2.Slider(animal_impregnation_chance, 0.0f, 100f);
			if (!complex_interspecies)
			{
				switch (interspecies_impregnation_modifier)
				{
					case 0.0f:
						GUI.contentColor = Color.grey;
						listingStandard2.Label("InterspeciesImpregnantionModifier".Translate() + ": " + "InterspeciesDisabled".Translate(), -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						GUI.contentColor = Color.white;
						break;
					case 1.0f:
						GUI.contentColor = Color.cyan;
						listingStandard2.Label("InterspeciesImpregnantionModifier".Translate() + ": " + "InterspeciesMaximum".Translate(), -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						GUI.contentColor = Color.white;
						break;
					default:
						listingStandard2.Label("InterspeciesImpregnantionModifier".Translate() + ": " + Math.Round(interspecies_impregnation_modifier * 100, 1) + "%", -1f, "InterspeciesImpregnantionModifier_desc".Translate());
						break;
				}
				interspecies_impregnation_modifier = listingStandard2.Slider(interspecies_impregnation_modifier, 0.0f, 1.0f);
			}
			listingStandard2.Label("RJW_fertility_endAge_male".Translate() + ": " + (int)(fertility_endage_male * 80) + "In_human_years".Translate(), -1f, "RJW_fertility_endAge_male_desc".Translate());
			fertility_endage_male = listingStandard2.Slider(fertility_endage_male, 0.1f, 3.0f);
			listingStandard2.Label("RJW_fertility_endAge_female_humanlike".Translate() + ": " + (int)(fertility_endage_female_humanlike * 80) + "In_human_years".Translate(), -1f, "RJW_fertility_endAge_female_humanlike_desc".Translate());
			fertility_endage_female_humanlike = listingStandard2.Slider(fertility_endage_female_humanlike, 0.1f, 3.0f);
			listingStandard2.Label("RJW_fertility_endAge_female_animal".Translate() + ": " + (int)(fertility_endage_female_animal * 100) + "XofLifeExpectancy".Translate(), -1f, "RJW_fertility_endAge_female_animal_desc".Translate());
			fertility_endage_female_animal = listingStandard2.Slider(fertility_endage_female_animal, 0.1f, 3.0f);

			listingStandard2.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref humanlike_pregnancy_enabled, "humanlike_pregnancy_enabled");
			Scribe_Values.Look(ref animal_pregnancy_enabled, "animal_enabled");
			Scribe_Values.Look(ref bestial_pregnancy_enabled, "bestial_pregnancy_enabled");
			Scribe_Values.Look(ref insect_pregnancy_enabled, "insect_pregnancy_enabled");
			Scribe_Values.Look(ref mechanoid_enabled, "mechanoid_enabled");
			Scribe_Values.Look(ref trait_filtering_enabled, "trait_filtering_enabled");
			Scribe_Values.Look(ref use_parent_method, "use_parent_method");
			Scribe_Values.Look(ref humanlike_DNA_from_mother, "humanlike_DNA_from_mother");
			Scribe_Values.Look(ref bestial_DNA_from_mother, "bestial_DNA_from_mother");
			Scribe_Values.Look(ref animal_impregnation_chance, "animal_impregnation_chance");
			Scribe_Values.Look(ref fertility_endage_male, "RJW_fertility_endAge_male");
			Scribe_Values.Look(ref fertility_endage_female_humanlike, "fertility_endage_female_humanlike");
			Scribe_Values.Look(ref fertility_endage_female_animal, "fertility_endage_female_animal");
			Scribe_Values.Look(ref humanlike_impregnation_chance, "humanlike_impregnation_chance");
			Scribe_Values.Look(ref interspecies_impregnation_modifier, "interspecies_impregnation_chance");
			Scribe_Values.Look(ref complex_interspecies, "complex_interspecies");
		}
	}
}


code:
using System;
using UnityEngine;
using Verse;

namespace rjw
{
	public class RJWSettings : ModSettings
	{
		public static bool animal_on_animal_enabled = true;
		public static bool bestiality_enabled;
		public static bool necrophilia_enabled;
		public static bool animal_CP_rape;
		public static bool visitor_CP_rape;
		private static bool overdrive = false;

		public static bool rape_enabled = false;
		public static bool rape_beating;

		public static bool cum_filth = true;
		public static bool cum_on_body = true;
		public static bool cum_overlays = false;
		public static bool sounds_enabled = true;

		public static bool stds_enabled = true;
		public static bool std_floor = true;

		public static bool nymphos;
		public static bool FemaleFuta;
		public static bool MaleTrap;
		public static float male_nymph_chance = 0.0f;
		public static float futa_nymph_chance = 0.0f;
		public static float futa_natives_chance = 0.0f;
		public static float futa_spacers_chance = 0.5f;

		public static int sex_minimum_age = 13;
		public static int sex_free_for_all_age = 18;
		public static float sexneed_decay_rate = 1.0f;

		public static float nonFutaWomenRaping_MaxVulnerability = 0.8f;
		public static float rapee_MinVulnerability_human = 1.2f;

		public static bool RPG_direct_control;
		public static bool RPG_hero_control;

		public static bool submit_button_enabled = RJWDebugSettings.submit_button_enabled;
		public static bool show_RJW_designation_box = RJWDebugSettings.show_RJW_designation_box;

		public static bool DevMode = RJWDebugSettings.DevMode;
		public static bool WildMode = RJWDebugSettings.WildMode;
		public static bool GenderlessAsFuta = RJWDebugSettings.GenderlessAsFuta;

		public static void DoWindowContents(Rect inRect)
		{
			sexneed_decay_rate = Mathf.Clamp(sexneed_decay_rate, 0.0f, 10000.0f);
			nonFutaWomenRaping_MaxVulnerability = Mathf.Clamp(nonFutaWomenRaping_MaxVulnerability, 0.0f, 3.0f);
			rapee_MinVulnerability_human = Mathf.Clamp(rapee_MinVulnerability_human, 0.0f, 3.0f);

			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.ColumnWidth = inRect.width / 2.05f;
			listingStandard.Begin(inRect);
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("animal_on_animal_enabled".Translate(), ref animal_on_animal_enabled, "animal_on_animal_enabled_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("necrophilia_enabled".Translate(), ref necrophilia_enabled, "necrophilia_enabled_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("bestiality_enabled".Translate(), ref bestiality_enabled, "bestiality_enabled_desc".Translate());
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("rape_enabled".Translate(), ref rape_enabled, "rape_enabled_desc".Translate());
			if (rape_enabled)
			{
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("  " + "VisitorsCanCP".Translate(), ref visitor_CP_rape, "VisitorsCanCP_desc".Translate());
				listingStandard.Gap(3f);
				if (!bestiality_enabled)
				{
					GUI.contentColor = Color.grey;
					animal_CP_rape = false;
				}
				listingStandard.CheckboxLabeled("  " + "AnimalsCanCP".Translate(), ref animal_CP_rape, "AnimalsCanCP_desc".Translate());
				if (!bestiality_enabled)
					GUI.contentColor = Color.white;
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("  " + "PrisonersBeating".Translate(), ref rape_beating, "PrisonersBeating_desc".Translate());
			}
			else
			{
				visitor_CP_rape = false;
				animal_CP_rape = false;
				rape_beating = false;
			}
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("STD_enabled".Translate(), ref stds_enabled, "STD_enabled_desc".Translate());
			listingStandard.Gap(5f);
			if (stds_enabled)
				listingStandard.CheckboxLabeled("  " + "STD_FromFloors".Translate(), ref std_floor, "STD_FromFloors_desc".Translate());
			else
				std_floor = false;
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("cum_filth".Translate(), ref cum_filth, "cum_filth_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("cum_on_body".Translate(), ref cum_on_body, "cum_on_body_desc".Translate());
			listingStandard.Gap(5f);
			if (cum_on_body)
				listingStandard.CheckboxLabeled("cum_overlays".Translate(), ref cum_overlays, "cum_overlays_desc".Translate());
			else
				cum_overlays = false;
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("sounds_enabled".Translate(), ref sounds_enabled, "sounds_enabled_desc".Translate());
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("RPG_direct_control_name".Translate(), ref RPG_direct_control, "RPG_direct_control_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("RPG_hero_control_name".Translate(), ref RPG_hero_control, "RPG_hero_control_desc".Translate());
			listingStandard.Gap(5f);

			listingStandard.NewColumn();
			listingStandard.Gap(4f);
			GUI.contentColor = Color.white;
			if (sexneed_decay_rate < 2.5f)
			{
				overdrive = false;
				listingStandard.Label("sexneed_decay_rate_name".Translate() + ": " + Math.Round(sexneed_decay_rate * 100f, 0) + "%", -1f, "sexneed_decay_rate_desc".Translate());
				sexneed_decay_rate = listingStandard.Slider(sexneed_decay_rate, 0.0f, 5.0f);
			}
			else if (sexneed_decay_rate <= 5.0f && !overdrive)
			{
				GUI.contentColor = Color.yellow;
				listingStandard.Label("sexneed_decay_rate_name".Translate() + ": " + Math.Round(sexneed_decay_rate * 100f, 0) + "% [Not recommended]", -1f, "sexneed_decay_rate_desc".Translate());
				sexneed_decay_rate = listingStandard.Slider(sexneed_decay_rate, 0.0f, 5.0f);
				if (sexneed_decay_rate == 5.0f)
				{
					GUI.contentColor = Color.red;
					if (listingStandard.ButtonText("OVERDRIVE"))
						overdrive = true;
				}
				GUI.contentColor = Color.white;
			}
			else
			{
				GUI.contentColor = Color.red;
				listingStandard.Label("sexneed_decay_rate_name".Translate() + ": " + Math.Round(sexneed_decay_rate * 100f, 0) + "% [WARNING: UNSAFE]", -1f, "sexneed_decay_rate_desc".Translate());
				GUI.contentColor = Color.white;
				sexneed_decay_rate = listingStandard.Slider(sexneed_decay_rate, 0.0f, 10000.0f);
			}
			listingStandard.Label("SexMinimumAge".Translate() + ": " + sex_minimum_age, -1f, "SexMinimumAge_desc".Translate());
			sex_minimum_age = (int)listingStandard.Slider(sex_minimum_age, 0, 100);
			listingStandard.Label("SexFreeForAllAge".Translate() + ": " + sex_free_for_all_age, -1f, "SexFreeForAllAge_desc".Translate());
			sex_free_for_all_age = (int)listingStandard.Slider(sex_free_for_all_age, 0, 100);
			if (rape_enabled)
			{
				listingStandard.Label("NonFutaWomenRaping_MaxVulnerability".Translate() + ": " + (int)(nonFutaWomenRaping_MaxVulnerability * 100), -1f, "NonFutaWomenRaping_MaxVulnerability_desc".Translate());
				nonFutaWomenRaping_MaxVulnerability = listingStandard.Slider(nonFutaWomenRaping_MaxVulnerability, 0.0f, 3.0f);
				listingStandard.Label("Rapee_MinVulnerability_human".Translate() + ": " + (int)(rapee_MinVulnerability_human * 100), -1f, "Rapee_MinVulnerability_human_desc".Translate());
				rapee_MinVulnerability_human = listingStandard.Slider(rapee_MinVulnerability_human, 0.0f, 3.0f);
			}
			listingStandard.Gap(20f);
			listingStandard.CheckboxLabeled("NymphsJoin".Translate(), ref nymphos, "NymphsJoin_desc".Translate());
			listingStandard.Gap(5f);

			// Save compatibility check for 1.9.7
			// This can probably be safely removed at a later date, I doubt many players use old saves for long.
			if (male_nymph_chance > 1.0f || futa_nymph_chance > 1.0f || futa_natives_chance > 1.0f || futa_spacers_chance > 1.0f)
			{
				male_nymph_chance = 0.0f;
				futa_nymph_chance = 0.0f;
				futa_natives_chance = 0.0f;
				futa_spacers_chance = 0.0f;
			}

			listingStandard.CheckboxLabeled("FemaleFuta".Translate(), ref FemaleFuta, "FemaleFuta_desc".Translate());
			listingStandard.CheckboxLabeled("MaleTrap".Translate(), ref MaleTrap, "MaleTrap_desc".Translate());
			if (nymphos)
			{
				listingStandard.Label("male_nymph_chance".Translate() + ": " + (int)(male_nymph_chance * 100) + "%", -1f, "male_nymph_chance_desc".Translate());
				male_nymph_chance = listingStandard.Slider(male_nymph_chance, 0.0f, 1.0f);
				if (FemaleFuta || MaleTrap)
				{
					listingStandard.Label("futa_nymph_chance".Translate() + ": " + (int)(futa_nymph_chance * 100) + "%", -1f, "futa_nymph_chance_desc".Translate());
					futa_nymph_chance = listingStandard.Slider(futa_nymph_chance, 0.0f, 1.0f);
				}
			}
			if (FemaleFuta || MaleTrap)
			{
				listingStandard.Label("futa_natives_chance".Translate() + ": " + (int)(futa_natives_chance * 100) + "%", -1f, "futa_natives_chance_desc".Translate());
				futa_natives_chance = listingStandard.Slider(futa_natives_chance, 0.0f, 1.0f);
				listingStandard.Label("futa_spacers_chance".Translate() + ": " + (int)(futa_spacers_chance * 100) + "%", -1f, "futa_spacers_chance_desc".Translate());
				futa_spacers_chance = listingStandard.Slider(futa_spacers_chance, 0.0f, 1.0f);
			}

			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref animal_on_animal_enabled, "animal_on_animal_enabled", true, true);
			Scribe_Values.Look(ref bestiality_enabled, "bestiality_enabled", false, true);
			Scribe_Values.Look(ref necrophilia_enabled, "necrophilia_enabled", false, true);
			Scribe_Values.Look(ref rape_enabled, "rape_enabled", false, true);
			Scribe_Values.Look(ref visitor_CP_rape, "visitor_CP_rape", false, true);
			Scribe_Values.Look(ref animal_CP_rape, "animal_CP_rape", false, true);
			Scribe_Values.Look(ref rape_beating, "rape_beating", false, true);
			Scribe_Values.Look(ref nymphos, "nymphs_join", true, true);
			Scribe_Values.Look(ref FemaleFuta, "FemaleFuta", true, true);
			Scribe_Values.Look(ref MaleTrap, "MaleTrap", true, true);
			Scribe_Values.Look(ref stds_enabled, "STD_enabled", true, true);
			Scribe_Values.Look(ref std_floor, "STD_FromFloors", true, true);
			Scribe_Values.Look(ref sounds_enabled, "sounds_enabled", true, true);
			Scribe_Values.Look(ref cum_filth, "cum_filth", true, true);
			Scribe_Values.Look(ref cum_on_body, "cum_on_body", true, true);
			Scribe_Values.Look(ref cum_overlays, "cum_overlays", false, true);
			Scribe_Values.Look(ref sex_minimum_age, "sex_minimum_age", 13, true);
			Scribe_Values.Look(ref sex_free_for_all_age, "sex_free_for_all", 18, true);
			Scribe_Values.Look(ref sexneed_decay_rate, "sexneed_decay_rate", 1.0f, true);
			Scribe_Values.Look(ref nonFutaWomenRaping_MaxVulnerability, "nonFutaWomenRaping_MaxVulnerability", 0.8f, true);
			Scribe_Values.Look(ref rapee_MinVulnerability_human, "rapee_MinVulnerability_human", 1.2f, true);
			Scribe_Values.Look(ref male_nymph_chance, "male_nymph_chance", 0.0f, true);
			Scribe_Values.Look(ref futa_nymph_chance, "futa_nymph_chance", 0.0f, true);
			Scribe_Values.Look(ref futa_natives_chance, "futa_natives_chance", 0.0f, true);
			Scribe_Values.Look(ref futa_spacers_chance, "futa_spacers_chance", 0.5f, true);
			Scribe_Values.Look(ref RPG_direct_control, "RPG_direct_control", false, true);
			Scribe_Values.Look(ref RPG_hero_control, "RPG_hero_control", false, true);
		}
	}
}


code:
using UnityEngine;
using Verse;

namespace rjw.Settings
{
	public class RJWSettingsController : Mod
	{
		public RJWSettingsController(ModContentPack content) : base(content)
		{
			GetSettings<RJWSettings>();
		}

		public override string SettingsCategory()
		{
			return "RJWSettingsOne".Translate();
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			RJWSettings.DoWindowContents(inRect);
		}
	}

	public class RJWDebugController : Mod
	{
		public RJWDebugController(ModContentPack content) : base(content)
		{
			GetSettings<RJWDebugSettings>();
		}

		public override string SettingsCategory()
		{
			return "RJWDebugSettings".Translate();
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			RJWDebugSettings.DoWindowContents(inRect);
		}
	}

	public class RJWPregnancySettingsController : Mod
	{
		public RJWPregnancySettingsController(ModContentPack content) : base(content)
		{
			GetSettings<RJWPregnancySettings>();
		}

		public override string SettingsCategory()
		{
			return "RJWSettingsTwo".Translate();
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			RJWPregnancySettings.DoWindowContents(inRect);
		}
	}

	public class RJWPreferenceSettingsController : Mod
	{
		public RJWPreferenceSettingsController(ModContentPack content) : base(content)
		{
			GetSettings<RJWPreferenceSettings>();
		}

		public override string SettingsCategory()
		{
			return "RJWSettingsThree".Translate();
		}

		public override void DoSettingsWindowContents(Rect inRect)
		{
			RJWPreferenceSettings.DoWindowContents(inRect);
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_Bestiality : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			float base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul; // Default is 4.0

			float desire_factor;
			{
				Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();

				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.40f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.80f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;

				if (xxx.is_nympho(pawn))
					personality_factor *= 0.5f;
				else if (xxx.is_prude(pawn) || pawn.story.traits.HasTrait(TraitDefOf.BodyPurist))
					personality_factor *= 2f;

				if (pawn.story.traits.HasTrait(TraitDefOf.Nudist))
					personality_factor *= 0.9f;

				// Pawns with no zoophile trait should first try to find other outlets.
				if (!xxx.is_zoophile(pawn))
					personality_factor *= 8f;

				// Less likely to engage in bestiality if the pawn has a lover... unless the lover is an animal (there's mods for that, so need to check).
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_animal(LovePartnerRelationUtility.ExistingMostLikedLovePartner(pawn, false)) && !xxx.is_lecher(pawn) && !xxx.is_nympho(pawn))
					personality_factor *= 2.5f;

				// Pawns with few or no prior animal encounters are more reluctant to engage in bestiality.
				if (pawn.records.GetValue(xxx.CountOfSexWithAnimals) < 3)
					personality_factor *= 3f;
				else if (pawn.records.GetValue(xxx.CountOfSexWithAnimals) > 10)
					personality_factor *= 0.8f;
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			return base_mtb * desire_factor * personality_factor * fun_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException)
			{
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Bestiality:TryIssueJobPackage - error message" + e.Message);
				//--Log.Message("[RJW]ThinkNode_ChancePerHour_Bestiality:TryIssueJobPackage - error stacktrace" + e.StackTrace);
				return ThinkResult.NoJob; ;
			}
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Cooldown for breeding
	/// something like 4.0*0.2 = 0.8 hours
	/// </summary>
	public class ThinkNode_ChancePerHour_Breed : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			return xxx.config.comfort_prisoner_rape_mtbh_mul * 0.20f ;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException)
			{
				return ThinkResult.NoJob; ;
			}
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_Fappin : ThinkNode_ChancePerHour
	{
		public static float get_fappin_mtb_hours(Pawn p)
		{
			if (DebugSettings.alwaysDoLovin)
				return 0.1f;

			return (xxx.is_nympho(p) ? 0.5f : 1.0f) * rjw_CORE_EXPOSED.LovePartnerRelationUtility.LovinMtbSinglePawnFactor(p);
		}

		protected override float MtbHours(Pawn p)
		{
			// No fapping for animals... for now, at least.
			// Maybe enable this for monsters girls and such in future, but that'll need code changes to avoid errors.
			if (xxx.is_animal(p))
				return -1.0f;

			bool is_horny = xxx.need_some_sex(p) > 1f;
			if (is_horny)
			{
				bool isAlone = !p.Map.mapPawns.AllPawnsSpawned.Any(x => p.CanSee(x) && xxx.is_human(x));
				// More likely to fap if alone.
				float aloneFactor = isAlone ? 0.6f : 1.2f;
				if (xxx.has_quirk(p, "Exhibitionist"))
					aloneFactor = isAlone ? 1.0f : 0.6f;

				// More likely to fap if nude.
				float clothingFactor = p.apparel.PsychologicallyNude ? 0.8f : 1.0f;

				float SexNeedFactor = (4 - xxx.need_some_sex(p)) / 2f;
				return get_fappin_mtb_hours(p) * SexNeedFactor * aloneFactor * clothingFactor;
			}

			return -1.0f;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Necro rape corpse
	/// </summary>
	public class ThinkNode_ChancePerHour_Necro : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			float base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul; // Default of 4.0

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.15f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.60f;
					else if (need_sex.CurLevel <= need_sex.thresh_satisfied())
						desire_factor = 1.00f;
					else // Recently had sex.
						desire_factor = 2.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;

				if (xxx.is_nympho(pawn))
					personality_factor *= 0.8f;
				if (xxx.is_prude(pawn) || pawn.story.traits.HasTrait(TraitDefOf.BodyPurist))
					personality_factor *= 2f;
				if (xxx.is_psychopath(pawn))
					personality_factor *= 0.5f;

				// Pawns with no necrophiliac trait should first try to find other outlets.
				if (!xxx.is_necrophiliac(pawn))
					personality_factor *= 8f;
				else
					personality_factor *= 0.5f;

				// Less likely to engage in necrophilia if the pawn has a lover.
				if (!xxx.isSingleOrPartnerNotHere(pawn))
					personality_factor *= 1.25f;

				// Pawns with no records of prior necrophilia are less likely to engage in it.
				if (pawn.records.GetValue(xxx.CountOfSexWithCorpse) == 0)
					personality_factor *= 16f;
				else if (pawn.records.GetValue(xxx.CountOfSexWithCorpse) > 10)
					personality_factor *= 0.8f;
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_necrophiliac(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			// I'm normally against gender factors, but necrophilia is far more frequent among males. -Zaltys
			float gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException)
			{
				return ThinkResult.NoJob;
			}
		}
	}
}


code:
//using System;
//using RimWorld;
//using UnityEngine;
//using Verse;
//using Verse.AI;
//
//namespace rjw
//{
//	/// <summary>
//	/// not  used?
//	/// </summary>
//	public class ThinkNode_ChancePerHour_RandomRape : ThinkNode_ChancePerHour
//	{
//		protected override float MtbHours(Pawn pawn)
//		{
//			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul;
//
//			float desire_factor;
//			{
//				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
//				if (need_sex != null)
//				{
//					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
//						desire_factor = 0.15f;
//					else if (need_sex.CurLevel <= need_sex.thresh_horny())
//						desire_factor = 0.60f;
//					else
//						desire_factor = 1.00f;
//				}
//				else
//					desire_factor = 1.00f;
//			}
//
//			float personality_factor;
//			{
//				personality_factor = 1.0f;
//				if (pawn.story != null)
//				{
//					foreach (var trait in pawn.story.traits.allTraits)
//					{
//						if (trait.def == TraitDefOf.Bloodlust) personality_factor *= 0.25f;
//						else if (trait.def == TraitDefOf.Brawler) personality_factor *= 0.50f;
//						else if (trait.def == TraitDefOf.Psychopath) personality_factor *= 0.50f;
//						else if (trait.def == TraitDefOf.Kind) personality_factor *= 30.00f;
//					}
//				}
//			}
//
//			float fun_factor;
//			{
//				if ((pawn.needs.joy != null) && (xxx.is_bloodlust(pawn)))
//					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
//				else
//					fun_factor = 1.00f;
//			}
//
//			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;
//
//			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
//		}
//
//		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
//		{
//			try
//			{
//				return base.TryIssueJobPackage(pawn, jobParams);
//			}
//			catch (NullReferenceException)
//			{
//				return ThinkResult.NoJob; ;
//			}
//		}
//	}
//}


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

namespace rjw
{
	/// <summary>
	/// Colonists and prisoners try to rape CP
	/// </summary>
	public class ThinkNode_ChancePerHour_RapeCP : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul; //Default 4.0

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.10f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.50f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				if (xxx.has_traits(pawn))
				{
					// Most of the checks are done in the xxx.would_rape method.

					personality_factor = 1.0f;

					if (!RJWSettings.rape_beating)
					{
						if (xxx.is_bloodlust(pawn))
							personality_factor *= 0.5f;
					}

					if (xxx.is_nympho(pawn))
						personality_factor *= 0.5f;
					else if (xxx.is_prude(pawn) || pawn.story.traits.HasTrait(TraitDefOf.BodyPurist))
						personality_factor *= 2f;

					if (xxx.is_rapist(pawn))
						personality_factor *= 0.5f;
					if (xxx.is_psychopath(pawn))
						personality_factor *= 0.75f;
					else if (xxx.is_kind(pawn))
						personality_factor *= 5.0f;

					float rapeCount = pawn.records.GetValue(xxx.CountOfRapedHumanlikes) +
					                pawn.records.GetValue(xxx.CountOfRapedAnimals) +
					                pawn.records.GetValue(xxx.CountOfRapedInsects) +
					                pawn.records.GetValue(xxx.CountOfRapedOthers);

					// Pawns with few or no rapes are more reluctant to rape CPs.
					if (rapeCount < 3.0f)
						personality_factor *= 1.5f;
				}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_rapist(pawn) || xxx.is_psychopath(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			float animal_factor = 1.0f;
			if (xxx.is_animal(pawn))
			{
				// Much slower for female animals.
				animal_factor = (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)) ? 2.5f : 6f;
			}

			//if (xxx.is_animal(pawn)) { Log.Message("Chance for " + pawn + " : " + base_mtb * desire_factor * personality_factor * fun_factor * animal_factor); }
			return base_mtb * desire_factor * personality_factor * fun_factor * animal_factor;
		}

		public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobParams)
		{
			try
			{
				return base.TryIssueJobPackage(pawn, jobParams);
			}
			catch (NullReferenceException)
			{
				return ThinkResult.NoJob; ;
			}
		}
	}
}


code:
namespace rjw
{
	//This class is not used now.
	/*
	public class ThinkNode_ChancePerHour_Whore : ThinkNode_ChancePerHour
	{
		protected override float MtbHours(Pawn pawn)
		{
			// Use the fappin mtb hours as the base number b/c it already accounts for things like age
			var base_mtb = xxx.config.whore_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
			if (base_mtb < 0.0f)
				return -1.0f;

			float desire_factor;
			{
				var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
				if (need_sex != null)
				{
					if (need_sex.CurLevel <= need_sex.thresh_frustrated())
						desire_factor = 0.15f;
					else if (need_sex.CurLevel <= need_sex.thresh_horny())
						desire_factor = 0.60f;
					else
						desire_factor = 1.00f;
				}
				else
					desire_factor = 1.00f;
			}

			float personality_factor;
			{
				personality_factor = 1.0f;
				if (pawn.story != null)
				{
					foreach (var trait in pawn.story.traits.allTraits)
					{
						if (trait.def == xxx.nymphomaniac) personality_factor *= 0.25f;
						else if (trait.def == TraitDefOf.Greedy) personality_factor *= 0.50f;
						else if (xxx.RomanceDiversifiedIsActive&&(trait.def==xxx.philanderer || trait.def == xxx.polyamorous)) personality_factor *= 0.75f;
						else if (xxx.RomanceDiversifiedIsActive && (trait.def == xxx.faithful)&& LovePartnerRelationUtility.HasAnyLovePartner(pawn)) personality_factor *= 30f;
					}
				}
			}

			float fun_factor;
			{
				if ((pawn.needs.joy != null) && (xxx.is_nympho(pawn)))
					fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
				else
					fun_factor = 1.00f;
			}

			var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;

			return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
		}
	}
	*/
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the non animal is eligible for a Bestiality job
	/// </summary>
	public class ThinkNode_ConditionalBestiality : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//if (p.Faction != null && p.Faction.IsPlayer)
			//	Log.Message("[RJW]ThinkNode_ConditionalBestiality " + xxx.get_pawnname(p) + " is animal: " + xxx.is_animal(p));

			// No bestiality for animals, animal-on-animal is handled in Breed job.
			if (xxx.is_animal(p))
				return false;

			if (DebugSettings.alwaysDoLovin)
				return true;

			// Bestiality off
			if (!RJWSettings.bestiality_enabled)
				return false;

			// No free will while designated for rape.
			if (p.IsDesignatedComfort() && !RJWSettings.WildMode)
				return false;

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the animal is eligible for a breed job
	/// </summary>
	public class ThinkNode_ConditionalCanBreed : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalCanBreed " + p);

			//Rimworld of Magic polymorphed humanlikes also get animal think node
			//if (p.Faction != null && p.Faction.IsPlayer)
			//	Log.Message("[RJW]ThinkNode_ConditionalCanBreed " + xxx.get_pawnname(p) + " is animal: " + xxx.is_animal(p));

			// No Breed jobs for humanlikes, that's handled by bestiality.
			if (!xxx.is_animal(p))
				return false;

			if (DebugSettings.alwaysDoLovin)
				return true;

			// Animal stuff disabled.
			if (!RJWSettings.bestiality_enabled && !RJWSettings.animal_on_animal_enabled)
				return false;

			//return p.IsDesignatedBreedingAnimal() || RJWSettings.WildMode;
			return p.IsDesignatedBreedingAnimal();
		}
	}
}


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

namespace rjw
{
	public class ThinkNode_ConditionalCanRapeCP : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalCanRapeCP " + pawn);

			if (DebugSettings.alwaysDoLovin)
				return true;

			if (!RJWSettings.rape_enabled)
				return false;

			// Hostiles cannot use CP.
			if (p.HostileTo(Faction.OfPlayer))
				return false;

			// Designated pawns are not allowed to rape other CP.
			if (p.IsDesignatedComfort())
				return false;

			// Animals cannot rape CP if the setting is disabled.
			if (!(RJWSettings.bestiality_enabled && RJWSettings.animal_CP_rape) && xxx.is_animal(p) )
				return false;

			// Visitors(humans) cannot rape CP if the setting is disabled.
			if (!RJWSettings.visitor_CP_rape && p.Faction?.IsPlayer == false && xxx.is_human(p))
				return false;

			// Visitors(animals/caravan) cannot rape CP if the setting is disabled.
			if (!RJWSettings.visitor_CP_rape && p.Faction?.IsPlayer == false && p.Faction != Faction.OfInsects && xxx.is_animal(p))
				return false;

			// Wild animals, insects cannot rape CP.
			if ((p.Faction == null || p.Faction == Faction.OfInsects) && xxx.is_animal(p))
				return false;

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Pawn frustrated
	/// </summary>
	public class ThinkNode_ConditionalFrustrated : ThinkNode_Conditional
	{
		protected override bool Satisfied (Pawn p)
		{
			return xxx.need_some_sex(p) > 2f;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Pawn is horny
	/// </summary>
	public class ThinkNode_ConditionalHorny : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.need_some_sex(p) > 1f;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the pawn can engage in necrophilia.
	/// </summary>
	public class ThinkNode_ConditionalNecro : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalNecro " + p);

			if (DebugSettings.alwaysDoLovin)
				return true;

			if (!RJWSettings.necrophilia_enabled)
				return false;

			// No necrophilia for animals. At least for now.
			// This would be easy to enable, if we actually want to add it.
			if (xxx.is_animal(p))
				return false;

			// No free will while designated for rape.
			if (p.IsDesignatedComfort() && !RJWSettings.WildMode)
				return false;

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Nymph nothing to do, seek sex-> join in bed
	/// </summary>
	public class ThinkNode_ConditionalNympho : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			return xxx.is_nympho(p);
		}
	}
}