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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			bool silent = false;

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

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

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

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

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

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

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

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

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

			float do_cleaning = 0.5f; // 50%

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			if (!usedCondom)
			{
				CumFilthGenerator(pawn);

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

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

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

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

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

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

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

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

			CumFilthGenerator(pawn);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			if (scaled_age < 1)
				scaled_age = 1;

			return (int)scaled_age;
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			}
		}

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

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

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

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

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

			float tick = 1.0f;

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

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

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

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

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

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

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

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

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

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

			IncreaseTicksToNextLovin(pawn);
			IncreaseTicksToNextLovin(partner);

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

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

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

			pawn.Drawer.Notify_MeleeAttackOn(partner);
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			float maxValue = sexTypes.Max();

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

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

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

			Pawn giving = pawn;

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

			int typeIndex = sexTypes.IndexOf(maxValue);

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

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

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

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

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

			return sextype;
		}

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

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

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

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

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


code:
using Multiplayer.API;
using Verse;

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

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

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

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

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

				//natives/spacer futa
				float chance = (int)pawn.Faction.def.techLevel < 5 ? RJWSettings.futa_natives_chance : RJWSettings.futa_spacers_chance;
				//nymph futa gender
				chance = xxx.is_nympho(pawn) ? RJWSettings.futa_nymph_chance : chance;

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

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

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

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

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

repo_name: Eggs/rjw

license: mit


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

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

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


code:
using Verse;

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

		public bool triggered_after_load = false;

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

		public override void MapComponentUpdate()
		{
		}

		public override void MapComponentTick()
		{
		}

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

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

		public override void ExposeData()
		{
		}
	}
}


code:
using System;
using Verse;

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


code:
using Verse;

namespace rjw
{
	/* TO BE FINISHED
	public enum RandomDialogueJobType
	{
		Rape = 0,
		Love = 1,
		Other = 2
	}

	public sealed class RandomDialoguesDef : Def
	{
		public RandomDialogueJobType JobType;
		public string RD0;
		public string RD1;
		public string RD2;
		public string RD3;
		public string RD4;
		public string RD5;
		public string RD6;
		public string RD7;
		public string RD8;
		public string RD9;
		public string RD10;
		public string RD11;
		public string RD12;
		public string RD13;
		public string RD14;
		public string RD15;
		public string RD16;
		public string RD17;
		public string RD18;
		public string RD19;
	}

	[DefOf]
	public static class RandomDialoguesDefOf
	{
		//0 means before, 1 means during, 2 means after
		//public readonly static RandomDialoguesDef rape0;
		//public readonly static RandomDialoguesDef rape1;
		//public readonly static RandomDialoguesDef rape2;

		//public readonly static RandomDialoguesDef love0;
		//public readonly static RandomDialoguesDef love1;
		//public readonly static RandomDialoguesDef love2;

		//public readonly static RandomDialoguesDef other0;
		//public readonly static RandomDialoguesDef other1;
		//public readonly static RandomDialoguesDef other2;
	}
	*/

	// TO BE FINISHED
	/*
	public static class RandomDialogues
	{
		public static void processBeforeDialogue(Pawn psubject,Pawn pobject,int Type = 4)
		{
			switch (Type)
			{
				case 0:
					string traitDefName;
					if (xxx.has_traits(psubject)&&Rand.Value<0.25){
						traitDefName = psubject.random_pick_a_trait();
						switch (traitDefName)
						{
							case "Nymphomaniac":
								break;

							case "Rapist":
								break;

							case "Necrophiliac":
								break;

							case "Zoophile":
								break;

							case "Bloodlust":
								break;

							case "Psychopath":
								break;

							case "Cannibal":
								break;

							case "Masochist":
								break;

							case "Prosthophile":
								break;

							case "Prosthophobe":
								break;

							case "Jealous":
								break;

							case "Gay":
								break;

							default:
								break;
						}
					}
					if (xxx.has_traits(pobject))
					{
					}
					return;

				case 1:
					return;

				case 2:
					return;

				default:
					Log.Error("[RJW]RandomDialogues::processBeforeDialogue - Type not found");
					return;
			}
		}

		public static void processDuringDialogue(Pawn psubject, Pawn pobject, int Type = 4)
		{
			switch (Type)
			{
				case 0:
					return;

				case 1:
					return;

				case 2:
					return;

				default:
					Log.Error("[RJW]RandomDialogues::processBeforeDialogue - Type not found");
					return;
			}
		}

		public static void processAfterDialogue(Pawn psubject, Pawn pobject, int Type = 4)
		{
			switch (Type)
			{
				case 0:
					return;

				case 1:
					return;

				case 2:
					return;

				default:
					Log.Error("[RJW]RandomDialogues::processBeforeDialogue - Type not found");
					return;
			}
		}
	}
	*/
}


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

namespace rjw
{
	/// <summary>
	/// Responsible for judging the sexiness of potential partners.
	/// </summary>
	public static class SexAppraiser
	{
		public const float base_sat_per_fuck = 0.40f;
		public const float base_attraction = 0.60f;
		public const float no_partner_ability = 0.8f;

		static readonly SimpleCurve fuckability_per_reserved = new SimpleCurve
		{
			new CurvePoint(0f, 1.0f),
			new CurvePoint(0.3f, 0.4f),
			new CurvePoint(1f, 0.2f)
		};

		static readonly SimpleCurve attractiveness_from_age_male = new SimpleCurve
		{
			new CurvePoint(0f,  0.0f),
			new CurvePoint(4f,  0.1f),
			new CurvePoint(5f,  0.2f),
			new CurvePoint(15f, 0.8f),
			new CurvePoint(20f, 1.0f),
			new CurvePoint(32f, 1.0f),
			new CurvePoint(40f, 0.9f),
			new CurvePoint(45f, 0.77f),
			new CurvePoint(50f, 0.7f),
			new CurvePoint(55f, 0.5f),
			new CurvePoint(75f, 0.1f),
			new CurvePoint(100f, 0f)
		};

		//These were way too low and could be increased further. Anything under 0.7f pretty much stops sex from happening.
		static readonly SimpleCurve attractiveness_from_age_female = new SimpleCurve
		{
			new CurvePoint(0f,  0.0f),
			new CurvePoint(4f,  0.1f),
			new CurvePoint(5f,  0.2f),
			new CurvePoint(14f, 0.8f),
			new CurvePoint(28f, 1.0f),
			new CurvePoint(30f, 1.0f),
			new CurvePoint(45f, 0.7f),
			new CurvePoint(55f, 0.3f),
			new CurvePoint(75f, 0.1f),
			new CurvePoint(100f, 0f)
		};

		/// <summary>
		///	Returns boolean, no more messing around with floats.
		/// Just a simple 'Would rape? True/false'.
		/// </summary>
		[SyncMethod]
		public static bool would_rape(Pawn rapist, Pawn rapee)
		{
			float rape_factor = 0.3f; // start at 30%

			float vulnerabilityFucker = xxx.get_vulnerability(rapist); //0 to 3
			float vulnerabilityPartner = xxx.get_vulnerability(rapee); //0 to 3

			// More inclined to rape someone from another faction.
			if (rapist.HostileTo(rapee) || rapist.Faction != rapee.Faction)
				rape_factor += 0.25f;

			// More inclined to rape if the target is designated as CP.
			if (rapee.IsDesignatedComfort())
				rape_factor += 0.25f;

			// More inclined to rape when horny.
			Need_Sex horniness = rapist.needs.TryGetNeed<Need_Sex>();
			if (!xxx.is_animal(rapist) && horniness?.CurLevel <= horniness?.thresh_horny())
			{
				rape_factor += 0.25f;
			}

			if (xxx.is_animal(rapist))
			{
				if (vulnerabilityFucker < vulnerabilityPartner)
					rape_factor -= 0.1f;
				else
					rape_factor += 0.25f;
			}
			else if (xxx.is_animal(rapee))
			{
				if (xxx.is_zoophile(rapist))
					rape_factor += 0.5f;
				else
					rape_factor -= 0.2f;
			}
			else
			{
				rape_factor *= 0.5f + Mathf.InverseLerp(vulnerabilityFucker, 3f, vulnerabilityPartner);
			}

			if (rapist.health.hediffSet.HasHediff(HediffDef.Named("AlcoholHigh")))
				rape_factor *= 1.25f; //too drunk to care

			// Increase factor from traits.
			if (xxx.is_rapist(rapist))
				rape_factor *= 1.5f;
			if (xxx.is_nympho(rapist))
				rape_factor *= 1.25f;
			if (xxx.is_bloodlust(rapist))
				rape_factor *= 1.2f;
			if (xxx.is_psychopath(rapist))
				rape_factor *= 1.2f;
			if (xxx.is_masochist(rapee))
				rape_factor *= 1.2f;

			// Lower factor from traits.
			if (xxx.is_masochist(rapist))
				rape_factor *= 0.8f;

			if (rapist.needs.joy != null && rapist.needs.joy.CurLevel < 0.1f) // The rapist is really bored...
				rape_factor *= 1.2f;

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			if (rapist.relations == null || xxx.is_animal(rapist)) return Rand.Chance(rape_factor);
			int opinion = rapist.relations.OpinionOf(rapee);

			// Won't rape friends, unless rapist or psychopath.
			if (xxx.is_kind(rapist))
			{   //<-80: 1f /-40: 0.5f / 0+: 0f
				rape_factor *= 1f - Mathf.Pow(GenMath.InverseLerp(-80, 0, opinion), 2);
			}
			else if (xxx.is_rapist(rapist) || xxx.is_psychopath(rapist))
			{   //<40: 1f /80: 0.5f / 120+: 0f
				rape_factor *= 1f - Mathf.Pow(GenMath.InverseLerp(40, 120, opinion), 2); // This can never be 0, since opinion caps at 100.
			}
			else
			{   //<-60: 1f /-20: 0.5f / 40+: 0f
				rape_factor *= 1f - Mathf.Pow(GenMath.InverseLerp(-60, 40, opinion), 2);
			}

			//Log.Message("rjw::xxx rape_factor for " + get_pawnname(rapee) + " is " + rape_factor);

			return Rand.Chance(rape_factor);
		}

		public static float would_fuck(Pawn fucker, Corpse fucked, bool invert_opinion = false, bool ignore_bleeding = false, bool ignore_gender = false)
		{
			CompRottable comp = fucked.GetComp<CompRottable>();

			//--Log.Message("rotFactor:" + rotFactor);

			// Things that don't rot, such as mechanoids and weird mod-added stuff such as Rimworld of Magic's elementals.
			if (comp == null)
			{
				// Trying to necro the weird mod-added stuff causes an error, so skipping those for now.
				return 0.0f;
			}

			float maxRot = ((CompProperties_Rottable)comp.props).TicksToDessicated;
			float rotFactor = (maxRot - comp.RotProgress) / maxRot;
			//--Log.Message("rotFactor:" + rotFactor);
			return would_fuck(fucker, fucked.InnerPawn, invert_opinion, ignore_bleeding, ignore_gender) * rotFactor;
		}

		public static float would_fuck_animal(Pawn pawn, Pawn target, bool invert_opinion = false, bool ignore_bleeding = false, bool ignore_gender = false)
		{
			float wildness_modifier = 1.0f;
			List<float> size_preference = new List<float>() { pawn.BodySize * 0.75f, pawn.BodySize * 1.6f };
			float fuc = would_fuck(pawn, target, invert_opinion, ignore_bleeding, ignore_gender); // 0.0 to ~3.0, orientation checks etc.

			if (fuc < 0.1f)
			{   // Would not fuck
				return 0;
			}

			if (xxx.has_quirk(pawn, "Teratophile"))
			{   // Teratophiles prefer more 'monstrous' partners.
				size_preference[0] = pawn.BodySize * 0.8f;
				size_preference[1] = pawn.BodySize * 2.0f;
				wildness_modifier = 0.3f;
			}
			if (pawn.health.hediffSet.HasHediff(HediffDef.Named("AlcoholHigh")))
			{
				wildness_modifier = 0.5f; //Drunk and making poor judgments.
				size_preference[1] *= 1.5f;
			}
			else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("YayoHigh")))
			{
				wildness_modifier = 0.2f; //This won't end well.
				size_preference[1] *= 2.5f;
			}
			if (Genital_Helper.has_vagina(pawn) || Genital_Helper.has_anus(pawn))
			{
				if (!(Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)))
				{
					size_preference[1] = pawn.BodySize * 1.3f;
				}
			}
			if (xxx.is_animal(pawn))
			{
				size_preference[1] = pawn.BodySize * 1.3f;
				wildness_modifier = 0.4f;
			}
			else
			{
				if (pawn.story.traits.HasTrait(TraitDefOf.Tough) || pawn.story.traits.HasTrait(TraitDefOf.Brawler))
				{
					size_preference[1] += 0.2f;
					wildness_modifier -= 0.2f;
				}
				else if (pawn.story.traits.HasTrait(TraitDef.Named("Wimp")))
				{
					size_preference[0] -= 0.2f;
					size_preference[1] -= 0.2f;
					wildness_modifier += 0.25f;
				}
			}

			float wildness = target.RaceProps.wildness; // 0.0 to 1.0
			float petness = target.RaceProps.petness; // 0.0 to 1.0
			float distance = pawn.Position.DistanceToSquared(target.Position);

			//Log.Message("[RJW]would_fuck_animal:: base: " + fuc + ", wildness: " + wildness + ", petness: " + petness + ", distance: " + distance);

			fuc = fuc + fuc * petness - fuc * wildness * wildness_modifier;

			if (fuc < 0.1f)
			{   // Would not fuck
				return 0;
			}

			// Adjust by distance, nearby targets preferred.
			fuc *= 1.0f - Mathf.Max(distance / 10000, 0.1f);

			// Adjust by size difference.
			if (target.BodySize < size_preference[0])
			{
				fuc *= Mathf.Lerp(0.1f, size_preference[0], target.BodySize);
			}
			else if (target.BodySize > size_preference[1])
			{
				fuc *= Mathf.Lerp(size_preference[1] * 10, size_preference[1], target.BodySize);
			}

			if (target.Faction != pawn.Faction)
			{
				//Log.Message("[RJW]would_fuck_animal(NT):: base: " + fuc + ", bound1: " + fuc * 0.75f);
				//Log.Message("[RJW]would_fuck_animal(NT):: base: " + fuc + ", bound2: " + fuc + 0.25f);
				fuc *= 0.75f; // Less likely to target wild animals.
			}
			else if (pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, target))
			{
				//Log.Message("[RJW]would_fuck_animal(T):: base: " + fuc + ", bound1: " + fuc * 1.25f);
				//Log.Message("[RJW]would_fuck_animal(T):: base: " + fuc + ", bound2: " + fuc + 0.25f);
				fuc *= 1.25f; // Bonded animals preferred.
			}

			return fuc;
		}

		// Returns how fuckable 'fucker' thinks 'p' is on a scale from 0.0 to 1.0
		public static float would_fuck(Pawn fucker, Pawn fucked, bool invert_opinion = false, bool ignore_bleeding = false, bool ignore_gender = false)
		{
			//--Log.Message("[RJW]would_fuck("+xxx.get_pawnname(fucker)+","+xxx.get_pawnname(fucked)+","+invert_opinion.ToString()+") is called");
			if (!xxx.is_healthy_enough(fucker) && !xxx.is_psychopath(fucker)) // Not healthy enough to have sex, shouldn't have got this far.
				return 0f;
			if ((xxx.is_animal(fucker) || xxx.is_animal(fucked)) && (!xxx.is_animal(fucker) || !xxx.is_animal(fucked)) && !RJWSettings.bestiality_enabled)
				return 0f; // Animals disabled.
			if (fucked.Dead && !RJWSettings.necrophilia_enabled)
				return 0f; // Necrophilia disabled.
			if (fucker.Dead || fucker.Suspended || fucked.Suspended)
				return 0f; // Target unreachable. Shouldn't have got this far, but doesn't hurt to double-check.
			if (xxx.is_starved(fucked) && fucked.Faction == fucker.Faction && !xxx.is_psychopath(fucker) && !xxx.is_rapist(fucker))
				return 0f;
			if (!ignore_bleeding && !xxx.is_not_dying(fucked) && !xxx.is_psychopath(fucker) && !xxx.is_rapist(fucker) && !xxx.is_bloodlust(fucker))
				return 0f; // Most people wouldn't fuck someone who's dying.

			if (!IsGenderOk(fucker, fucked))
			{
				return 0.0f;
			}

			int fucker_age = fucker.ageTracker.AgeBiologicalYears;
			string fucker_quirks = CompRJW.Comp(fucker).quirks.ToString();
			int p_age = fucked.ageTracker.AgeBiologicalYears;

			// --- Age checks ---
			if (!IsAgeOk(fucker, fucked, fucker_age, p_age))
			{
				return 0.0f;
			}
			float age_factor = GetAgeFactor(fucker, fucked, p_age);

			// --- Orientation checks ---
			float orientation_factor = GetOrientationFactor(fucker, fucked, ignore_gender);
			if (orientation_factor == 0.0f)
			{
				return 0.0f;
			}

			// --- Body and appearance checks ---
			float body_factor = GetBodyFactor(fucker, fucked);

			// --- Opinion checks ---
			float opinion_factor = GetOpinionFactor(fucker, fucked, invert_opinion);
			float horniness_factor = GetHorninessFactor(fucker);

			float reservedPercentage = (fucked.Dead ? 1f : fucked.ReservedCount()) / xxx.max_rapists_per_prisoner;
			//Log.Message("would_fuck() reservedPercentage:" + reservedPercentage + "fuckability_per_reserved"+ fuckability_per_reserved.Evaluate(reservedPercentage));
			//Log.Message("would_fuck() - horniness_factor = " + horniness_factor.ToString());

			float prenymph_att = Mathf.InverseLerp(0f, 2.8f, base_attraction * orientation_factor * horniness_factor * age_factor * body_factor * opinion_factor);
			float final_att = !xxx.is_nympho(fucker) ? prenymph_att : 0.2f + 0.8f * prenymph_att;
			//Log.Message("would_fuck( " + xxx.get_pawnname(fucker) + ", " + xxx.get_pawnname(fucked) + " ) - prenymph_att = " + prenymph_att.ToString() + ", final_att = " + final_att.ToString());

			return Mathf.Min(final_att, fuckability_per_reserved.Evaluate(reservedPercentage));
		}

		private static bool IsGenderOk(Pawn fucker, Pawn fucked)
		{
			if (fucker.gender == Gender.Male)
			{
				switch (RJWPreferenceSettings.Malesex)
				{
					case RJWPreferenceSettings.AllowedSex.All:
						break;
					case RJWPreferenceSettings.AllowedSex.Homo:
						if (fucked.gender != Gender.Male)
							return false;
						break;
					case RJWPreferenceSettings.AllowedSex.Nohomo:
						if (fucked.gender == Gender.Male)
							return false;
						break;
				}
			}
			if (fucker.gender == Gender.Female)
			{
				switch (RJWPreferenceSettings.FeMalesex)
				{
					case RJWPreferenceSettings.AllowedSex.All:
						break;
					case RJWPreferenceSettings.AllowedSex.Homo:
						if (fucked.gender != Gender.Female)
							return false;
						break;
					case RJWPreferenceSettings.AllowedSex.Nohomo:
						if (fucked.gender == Gender.Female)
							return false;
						break;
				}
			}
			return true;
		}

		private static float GetHorninessFactor(Pawn fucker)
		{
			float horniness_factor; // 1 to 1.6
			{
				float need_sex = xxx.need_some_sex(fucker);
				switch (need_sex)
				{
					case 3:
						horniness_factor = 1.6f;
						break;

					case 2:
						horniness_factor = 1.3f;
						break;

					case 1:
						horniness_factor = 1.1f;
						break;

					default:
						horniness_factor = 1f;
						break;
				}
			}
			//Log.Message("would_fuck() - horniness_factor = " + horniness_factor.ToString());
			return horniness_factor;
		}

		private static float GetOpinionFactor(Pawn fucker, Pawn fucked, bool invert_opinion)
		{
			float opinion_factor;
			{
				if (fucked.relations != null && fucker.relations != null && !xxx.is_animal(fucker) && !xxx.is_animal(fucked))
				{
					float opi = !invert_opinion ? fucker.relations.OpinionOf(fucked) : 100 - fucker.relations.OpinionOf(fucked); // -100 to 100
					opinion_factor = 0.8f + (opi + 100.0f) * (.45f / 200.0f); // 0.8 to 1.25
				}
				else if ((xxx.is_animal(fucker) || xxx.is_animal(fucked)) && fucker.relations.DirectRelationExists(PawnRelationDefOf.Bond, fucked))
				{
					opinion_factor = 1.3f;
				}
				else
				{
					opinion_factor = 1.0f;
				}

				// More likely to take advantege of CP.
				if (fucked.IsDesignatedComfort() || (fucked.IsDesignatedBreeding() && xxx.is_animal(fucker)))
					opinion_factor += 0.25f;
				else if (fucked.IsDesignatedService())
					opinion_factor += 0.1f;

				// Less picky if designated for whorin'.
				if (fucker.IsDesignatedService())
					opinion_factor += 0.1f;

				if (Quirk.Sapiosexual.IsSatisfiedBy(fucker, fucked))
				{
					opinion_factor *= 1.4f;
				}
			}
			//Log.Message("would_fuck() - opinion_factor = " + opinion_factor.ToString());
			return opinion_factor;
		}

		private static float GetBodyFactor(Pawn fucker, Pawn fucked)
		{
			float body_factor; //0.4 to 1.6
			{
				if (fucker.health.hediffSet.HasHediff(HediffDef.Named("AlcoholHigh")))
				{
					if (!xxx.is_zoophile(fucker) && xxx.is_animal(fucked))
						body_factor = 0.8f;
					else
						body_factor = 1.25f; //beer lens
				}
				else if (xxx.is_zoophile(fucker) && !xxx.is_animal(fucked))
				{
					body_factor = 0.7f;
				}
				else if (!xxx.is_zoophile(fucker) && xxx.is_animal(fucked))
				{
					body_factor = 0.45f;
				}
				else if (Quirk.Teratophile.IsSatisfiedBy(fucker, fucked))
				{
					body_factor = 1.4f;
				}
				else if (fucked.story != null)
				{
					if (fucked.story.bodyType == BodyTypeDefOf.Female) body_factor = 1.25f;
					else if (fucked.story.bodyType == BodyTypeDefOf.Fat) body_factor = 1.0f;
					else body_factor = 1.1f;

					if (fucked.story.traits.HasTrait(TraitDefOf.CreepyBreathing))
						body_factor *= 0.9f;

					if (fucked.story.traits.HasTrait(TraitDefOf.Beauty))
					{
						switch (fucked.story.traits.DegreeOfTrait(TraitDefOf.Beauty))
						{
							case 2: // Beautiful
								body_factor *= 1.25f;
								break;
							case 1: // Pretty
								body_factor *= 1.1f;
								break;
							case -1: // Ugly
								body_factor *= 0.8f;
								break;
							case -2: // Staggeringly Ugly
								body_factor *= 0.5f;
								break;
						}
					}

					if (RelationsUtility.IsDisfigured(fucked))
					{
						body_factor *= 0.8f;
					}

					// Nude target is more tempting.
					if (!fucked.Dead && fucked.apparel.PsychologicallyNude && fucker.CanSee(fucked))
						body_factor *= 1.1f;
				}
				else
				{
					body_factor = 1.1f;
				}

				if (Quirk.Somnophile.IsSatisfiedBy(fucker, fucked))
				{
					body_factor *= 1.25f;
				}

				if (Quirk.PregnancyFetish.IsSatisfiedBy(fucker, fucked))
				{
					body_factor *= 1.25f;
				}

				if (Quirk.ImpregnationFetish.IsSatisfiedBy(fucker, fucked))
				{
					body_factor *= 1.25f;
				}

				if (xxx.AlienFrameworkIsActive && !xxx.is_animal(fucker))
				{
					if (xxx.is_xenophile(fucker))
					{
						if (fucker.def.defName == fucked.def.defName)
							body_factor *= 0.5f; // Same species, xenophile less interested.
					}
					else if (xxx.is_xenophobe(fucker))
					{
						if (fucker.def.defName != fucked.def.defName)
							body_factor *= 0.25f; // Different species, xenophobe less interested.
					}
				}

				if (fucked.Dead && !xxx.is_necrophiliac(fucker))
				{
					body_factor *= 0.5f;
				}
			}
			//Log.Message("would_fuck() - body_factor = " + body_factor.ToString());
			return body_factor;
		}

		private static float GetOrientationFactor(Pawn fucker, Pawn fucked, bool ignore_gender)
		{
			float orientation_factor; //0 or 1
			{
				orientation_factor = 1.0f;

				if (!ignore_gender && !xxx.is_animal(fucker))
				{
					if (xxx.is_asexual(fucker))
					{
						orientation_factor = 0.0f;
					}
					else if (!CompRJW.CheckPreference(fucker, fucked))
					{
						//Log.Message("would_fuck( " + xxx.get_pawnname(fucker) + ", " + xxx.get_pawnname(fucked) + " )");
						//Log.Message("would_fuck() - preference fail");
						orientation_factor = 0.0f;
					}
				}
			}
			//Log.Message("would_fuck() - orientation_factor = " + orientation_factor.ToString());
			return orientation_factor;
		}

		private static float GetAgeFactor(Pawn fucker, Pawn fucked, int p_age)
		{
			float age_factor;

			//The human age curve needs work. Currently pawns refuse to have sex with anyone over age of ~50 no matter what the other factors are, which is just silly...
			age_factor = fucked.gender == Gender.Male ? attractiveness_from_age_male.Evaluate(SexUtility.ScaleToHumanAge(fucked)) : attractiveness_from_age_female.Evaluate(SexUtility.ScaleToHumanAge(fucked));
			//--Log.Message("would_fuck() - age_factor = " + age_factor.ToString());

			if (xxx.is_animal(fucker))
			{
				age_factor = 1.0f;  //using flat factors, since human age is not comparable to animal ages
			}
			else if (xxx.is_animal(fucked))
			{
				if (p_age <= 1 && fucked.RaceProps.lifeExpectancy > 8)
					age_factor = 0.5f;
				else
					age_factor = 1.0f;
				//--Log.Message("would_fuck() - animal age_factor = " + age_factor.ToString());
			}
			if (Quirk.Gerontophile.IsSatisfiedBy(fucker, fucked))
			{
				age_factor = 1.0f;
			}

			return age_factor;
		}

		private static bool IsAgeOk(Pawn fucker, Pawn fucked, int fucker_age, int p_age)
		{
			bool age_ok;
			{
				if (xxx.is_animal(fucker) && p_age >= RJWSettings.sex_minimum_age)
				{
					age_ok = true;
				}
				else if (xxx.is_animal(fucked) && fucker_age >= RJWSettings.sex_minimum_age)
				{
					// don't check the age of animals when they are the victim
					age_ok = true;
				}
				else if (fucker_age >= RJWSettings.sex_free_for_all_age && p_age >= RJWSettings.sex_free_for_all_age)
				{
					age_ok = true;
				}
				else if (fucker_age < RJWSettings.sex_minimum_age || p_age < RJWSettings.sex_minimum_age)
				{
					age_ok = false;
				}
				else
				{
					age_ok = Math.Abs(fucker.ageTracker.AgeBiologicalYearsFloat - fucked.ageTracker.AgeBiologicalYearsFloat) < 2.05f;
				}
			}
			//Log.Message("would_fuck() - age_ok = " + age_ok.ToString());

			return age_ok;
		}

		static int ReservedCount(this Thing pawn)
		{
			int ret = 0;
			if (pawn == null) return 0;
			try
			{
				ReservationManager reserver = pawn.Map.reservationManager;
				IList reservations = (IList)AccessTools.Field(typeof(ReservationManager), "reservations").GetValue(reserver);

				if (reservations.Count == 0) return 0;
				Type reserveType = reservations[0].GetType();
				ret += (from object t in reservations
						where t != null
						let target = (LocalTargetInfo)AccessTools.Field(reserveType, "target").GetValue(t)
						let claimant = (Pawn)AccessTools.Field(reserveType, "claimant").GetValue(t)
						where target != null
						where target.Thing != null
						where target.Thing.ThingID == pawn.ThingID
						select (int)AccessTools.Field(reserveType, "stackCount").GetValue(t)).Count();
			}
			catch (Exception e)
			{
				Log.Warning(e.ToString());
			}
			return ret;
		}
	}
}


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

namespace rjw
{
	public enum SexPartType : byte
	{
		Anus = 0,
		FemaleBreast = 1,
		FemaleGenital = 2,
		MaleBreast = 3,
		MaleGenital = 4
	}
}


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

namespace rjw
{
	/// <summary>
	/// Compares traits for equality but ignores forsed flag
	/// </summary>
	class TraitComparer : IEqualityComparer<Trait>
	{
		bool ignoreForced;
		bool ignoreDegree;

		public TraitComparer(bool ignoreDegree = false, bool ignoreForced = true)
		{
			this.ignoreDegree = ignoreDegree;
			this.ignoreForced = ignoreForced;
		}

		public bool Equals(Trait x, Trait y)
		{
			return
				x.def == y.def &&
				(ignoreDegree || (x.Degree == y.Degree)) &&
				(ignoreForced || (x.ScenForced == y.ScenForced));
		}

		public int GetHashCode(Trait obj)
		{
			return
				(obj.def.GetHashCode() << 5) +
				(ignoreDegree ? 0 : obj.Degree) +
				((ignoreForced || obj.ScenForced) ? 0 : 0x10);
		}
	}
}


code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace rjw
{

	public static class Unprivater
	{
		internal const BindingFlags flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
		/// <summary>
		/// T is for returned type. For instance-class fields
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="fieldName"></param>
		/// <param name="obj"></param>
		/// <returns></returns>
		public static T GetProtectedValue<T>(string fieldName, object obj)
		{
			var fieldinfo = obj.GetType().GetField(fieldName, flags);
			var readData = fieldinfo.GetValue(obj);
			if (readData is T)
			{
				return (T)readData;
			}
			else
			{
				try
				{
					return (T)Convert.ChangeType(readData, typeof(T));
				}
				catch (InvalidCastException)
				{
					return default(T);
				}
			}
		}

		public static bool SetProtectedValue(string fieldName, object obj, object value)
		{
			var fieldinfo = obj.GetType().GetField(fieldName, flags);

			try
				{
					fieldinfo.SetValue(obj, value);
					return true;
				}
				catch (Exception)
				{
					return false;
			}

		}

		/// <summary>
		/// T is for returned type. For instance-class properties ( [ get; thingies ] )
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="fieldName"></param>
		/// <param name="obj"></param>
		/// <returns></returns>
		public static T GetProtectedProperty<T>(string propertyName, object obj)
		{
			var propinfo = obj.GetType().GetProperty(propertyName, flags);

			var getter = propinfo.GetGetMethod(nonPublic: true);
			var readData = getter.Invoke(obj, null);
			if (readData is T)
			{
				return (T)readData;
			}
			else
			{
				try
				{
					return (T)Convert.ChangeType(readData, typeof(T));
				}
				catch (InvalidCastException)
				{
					return default(T);
				}
			}
		}

		public static bool SetProtectedProperty(string propertyName, object obj, object value)
		{
			var propinfo = obj.GetType().GetProperty(propertyName, flags);

			try
			{
				propinfo.SetValue(obj, value, null);
				return true;
			}
				catch (Exception)
				{
				return false;
				}

		}

		/// <summary>
		/// T is for returned type. For static-class fields
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="fieldName"></param>
		/// <param name="t"></param>
		/// <returns></returns>
		public static T GetProtectedValue<T>(string fieldName, Type t)
		{
			var fieldinfo = t.GetField(fieldName, flags);
			var readData = fieldinfo.GetValue(null);
			if (readData is T)
			{
				return (T)readData;
			}
			else
			{
				try
				{
					return (T)Convert.ChangeType(readData, typeof(T));
				}
				catch (InvalidCastException)
				{
					return default(T);
				}
			}
		}

		public static bool SetProtectedValue(string fieldName, Type t, object value)
		{
			var fieldinfo = t.GetField(fieldName, flags);

			try
			{
				fieldinfo.SetValue(null, value);
				return true;
			}
			catch (Exception)
			{
				return false;
			}

		}

		// needs testing, dunno if it works for statics
		public static T GetProtectedProperty<T>(string propertyName, Type t)
		{
			var propinfo = t.GetProperty(propertyName, flags);

			var getter = propinfo.GetGetMethod(nonPublic: true);
			var readData = getter.Invoke(null, null);
			if (readData is T)
			{
				return (T)readData;
			}
			else
			{
				try
				{
					return (T)Convert.ChangeType(readData, typeof(T));
				}
				catch (InvalidCastException)
				{
					return default(T);
				}
			}
		}

	}

}


code:
namespace rjw
{
	/*This is useless for now.
	public class Verb_Fuck : Verb
	{
		private const int TargetCooldown = 40;

		protected override bool TryCastShot()
		{
			Pawn casterPawn = base.CasterPawn;

			Thing thing = this.currentTarget.Thing;
			casterpawn.rotationTracker.Face(thing.DrawPos);

			this.SoundJuicy().PlayOneShot(new TargetInfo(thing.Position, casterPawn.Map, false));
			casterPawn.Drawer.Notify_MeleeAttackOn(thing);

			casterpawn.rotationTracker.FaceCell(thing.Position);
			if (casterPawn.caller != null) {
				casterPawn.caller.Notify_DidMeleeAttack();
			}
			return true;
		}

		private SoundDef SoundJuicy()
		{
			return SoundDef.Named ("Sex");
		}
	}
	*/
}