code:
using Verse;

namespace rjw
{
	public static class ModLog
	{
		/// <summary>
		/// Logs the given message with [SaveStorage.ModId] appended.
		/// </summary>
		public static void Error(string message)
		{
			Log.Error($"[{SaveStorage.ModId}] {message}");
		}

		/// <summary>
		/// Logs the given message with [SaveStorage.ModId] appended.
		/// </summary>
		public static void Message(string message)
		{
			Log.Message($"[{SaveStorage.ModId}] {message}");
		}

		/// <summary>
		/// Logs the given message with [SaveStorage.ModId] appended.
		/// </summary>
		public static void Warning(string message)
		{
			Log.Warning($"[{SaveStorage.ModId}] {message}");
		}
	}
}


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 TryAddRacePart(this Pawn pawn, SexPartType sexPartType)
		{
			return RaceGroupDef_Helper.TryAddRacePart(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));
		}

		public static List<Hediff> GetGenitalsList(this Pawn pawn)
		{
			List<Hediff> set;
			try//error at world gen
			{
				set = pawn.GetRJWPawnData().genitals;
				if (set.NullOrEmpty())
				{
					var partBPR = Genital_Helper.get_genitalsBPR(pawn);
					set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
					pawn.GetRJWPawnData().genitals = set;
				}

			}
			catch
			{
				var partBPR = Genital_Helper.get_genitalsBPR(pawn);
				set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			}
			return set;
		}

		public static List<Hediff> GetBreastList(this Pawn pawn)
		{
			List<Hediff> set;
			try//error at world gen
			{
				set = pawn.GetRJWPawnData().breasts;
				if (set.NullOrEmpty())
				{
					var partBPR = Genital_Helper.get_breastsBPR(pawn);
					set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
					pawn.GetRJWPawnData().breasts = set;
				}

			}
			catch
			{
				var partBPR = Genital_Helper.get_breastsBPR(pawn);
				set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			}
			return set;
		}
		public static List<Hediff> GetUdderList(this Pawn pawn)
		{
			List<Hediff> set;
			try//error at world gen
			{
				set = pawn.GetRJWPawnData().udders;
				if (set.NullOrEmpty())
				{
					var partBPR = Genital_Helper.get_uddersBPR(pawn);
					set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
					pawn.GetRJWPawnData().udders = set;
				}

			}
			catch
			{
				var partBPR = Genital_Helper.get_breastsBPR(pawn);
				set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			}
			return set;
		}
		public static List<Hediff> GetAnusList(this Pawn pawn)
		{
			List<Hediff> set;
			try//error at world gen
			{
				set = pawn.GetRJWPawnData().anus;
				if (set.NullOrEmpty())
				{
					var partBPR = Genital_Helper.get_anusBPR(pawn);
					set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
					pawn.GetRJWPawnData().anus = set;
				}

			}
			catch
			{
				var partBPR = Genital_Helper.get_anusBPR(pawn);
				set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			}
			return set;
		}

		public static List<Hediff> GetTorsoList(this Pawn pawn)
		{
			List<Hediff> set;
			try//error at world gen
			{
				set = pawn.GetRJWPawnData().torso;
				if (set.NullOrEmpty())
				{
					var partBPR = Genital_Helper.get_torsoBPR(pawn);
					set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
					pawn.GetRJWPawnData().torso = set;
				}

			}
			catch
			{
				var partBPR = Genital_Helper.get_torsoBPR(pawn);
				set = Genital_Helper.get_PartsHediffList(pawn, partBPR);
			}
			return set;
		}
		public static SexProps GetRMBSexPropsCache(this Pawn pawn)
		{
			SexProps set;
			set = pawn.GetRJWPawnData().SexProps;
			return set;
		}

		public static PawnData GetRJWPawnData(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn);
		}
	}
}

repo_name: aelina/rjw

license: mit


code:
using HarmonyLib;
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
using Multiplayer.API;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Implementation;

namespace rjw
{
	[StaticConstructorOnStartup]
	static class RMB_Masturbate
	{

		static RMB_Masturbate()
		{
			Harmony harmony = new Harmony("rjw");
			//start sex options
			harmony.Patch(AccessTools.Method(typeof(RMB_Menu), "GenerateRMBOptions"), prefix: null,
				postfix: new HarmonyMethod(typeof(RMB_Masturbate), nameof(ChoicesAtFor)));
		}

		public static List<FloatMenuOption> ChoicesAtFor(List<FloatMenuOption> __instance, Pawn pawn, LocalTargetInfo target)
		{
			FloatMenuOption(pawn, ref __instance, ref target);
			return __instance;
		}
		public static void FloatMenuOption(Pawn pawn, ref List<FloatMenuOption> opts, ref LocalTargetInfo target)
		{
			opts.AddRange(GenerateRMBOptions(pawn, target).Where(x => x.action != null));
		}

		public static List<FloatMenuOption> GenerateRMBOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			if (target.Pawn == pawn)
			{
				option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Masturbate".Translate(), delegate ()
				{
					FloatMenuUtility.MakeMenu(GenerateSoloSexRoleOptions(pawn, target).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
				}, MenuOptionPriority.High), pawn, target);
				opts.AddDistinct(option);
			}
			return opts;
		}

		//this options probably can be merged into 1, but for now/testing keep it separate
		public static List<FloatMenuOption> GenerateSoloSexRoleOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Masturbate_Bed".Translate(), delegate ()
			{
				Find.Targeter.BeginTargeting(TargetParemetersMasturbationChairOrBed(target), (LocalTargetInfo targetThing) =>
				{
					FloatMenuUtility.MakeMenu(GenerateSoloSexPoseOptions(pawn, targetThing).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
				});
			}, MenuOptionPriority.High), pawn, target);
			opts.AddDistinct(option);

			option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Masturbate_At".Translate(), delegate ()
			{
				Find.Targeter.BeginTargeting(TargetParemetersMasturbationLoc(target), (LocalTargetInfo targetThing) =>
				{
					FloatMenuUtility.MakeMenu(GenerateSoloSexPoseOptions(pawn, targetThing).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
				});
			}, MenuOptionPriority.High), pawn, target);
			opts.AddDistinct(option);

			option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Masturbate_Here".Translate(), delegate ()
			{
				FloatMenuUtility.MakeMenu(GenerateSoloSexPoseOptions(pawn, target).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
			}, MenuOptionPriority.High), pawn, target);
			opts.AddDistinct(option);
			return opts;
		}

		public static TargetingParameters TargetParemetersMasturbationChairOrBed(LocalTargetInfo target)
		{
			return new TargetingParameters()
			{
				canTargetBuildings = true,
				mapObjectTargetsMustBeAutoAttackable = false,
				validator = (TargetInfo target) =>
				{
					if (!target.HasThing)
						return false;
					Building building = target.Thing as Building;
					if (building == null)
						return false;
					if (building.def.building.isSittable)
						return true;
					if (building is Building_Bed)
						return true;
					return false;
				}
			};
		}
		public static TargetingParameters TargetParemetersMasturbationLoc(LocalTargetInfo target)
		{
			return new TargetingParameters()
			{
				canTargetLocations = true,
				mapObjectTargetsMustBeAutoAttackable = false,
				validator = (TargetInfo target) =>
				{
					if (!target.HasThing)
						return true;
					return false;
				}
			};
		}
		public static List<FloatMenuOption> GenerateSoloSexPoseOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;
			var job = xxx.Masturbate;

			List<InteractionDef> validintdefs = new List<InteractionDef>();
			foreach (InteractionDef d in SexUtility.SexInterractions)
			{
				var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(d);
				if (interaction.Extension.rjwSextype != xxx.rjwSextype.Masturbation.ToStringSafe())
					continue;

				ILewdInteractionValidatorService service = LewdInteractionValidatorService.Instance;
				if (service.IsValid(d, pawn, pawn))
					validintdefs.Add(d);
			}

			foreach (InteractionDef dictionaryKey in validintdefs)
			{
				var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(dictionaryKey);
				var dev = "";
				if (RJWSettings.DevMode)
					dev = " ( defName: " + dictionaryKey.defName + ")";

				var label = interaction.Extension.RMBLabel.CapitalizeFirst() + dev;
				option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(label, delegate ()
				{
					RMB_Menu.HaveSex(pawn, job, target, dictionaryKey);
				}, MenuOptionPriority.High), pawn, target);
				opts.AddDistinct(option);
			}

			if (opts.NullOrEmpty())
				opts.AddDistinct(new FloatMenuOption("none", null));

			return opts;
		}
	}
}


code:
using HarmonyLib;
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using Verse.AI;
using Multiplayer.API;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Implementation;
using rjw.Modules.Interactions.Objects;

namespace rjw
{
	[StaticConstructorOnStartup]
	static class RMB_Menu
	{
		static RMB_Menu()
		{
			Harmony harmony = new Harmony("rjw");
			//start sex options
			harmony.Patch(AccessTools.Method(typeof(FloatMenuMakerMap), "ChoicesAtFor"), prefix: null,
				postfix: new HarmonyMethod(typeof(RMB_Menu), nameof(ChoicesAtFor)));
		}

		//show rmb on
		public static TargetingParameters TargetParameters
		{
			get
			{
				if (targetParameters == null)
				{
					targetParameters = new TargetingParameters()
					{
						canTargetHumans = true,
						canTargetAnimals = true,
						canTargetItems = true,
						mapObjectTargetsMustBeAutoAttackable = false,
					};
				}
				return targetParameters;
			}
		}

		private static TargetingParameters targetParameters = null;
		private static Vector3 rjwclickPos;

		public static TargetingParameters TargetParemetersMasturbationChairOrBed(LocalTargetInfo target)
		{
			return new TargetingParameters()
			{
				canTargetBuildings = true,
				mapObjectTargetsMustBeAutoAttackable = false,
				validator = (TargetInfo target) =>
				{
					if (!target.HasThing)
						return false;
					Building building = target.Thing as Building;
					if (building == null)
						return false;
					if (building.def.building.isSittable)
						return true;
					if (building is Building_Bed)
						return true;
					return false;
				}
			};
		}

		public static TargetingParameters TargetParemetersMasturbationLoc(LocalTargetInfo target)
		{
			return new TargetingParameters()
			{
				canTargetLocations = true,
				mapObjectTargetsMustBeAutoAttackable = false,
				validator = (TargetInfo target) =>
				{
					if (!target.HasThing)
						return true;
					return false;
				}
			};
		}

		//TODO: dildo selection for masturbation/sex
		public static TargetingParameters TargetParemetersDildos(LocalTargetInfo target)
		{
			return new TargetingParameters()
			{
				canTargetItems = true,
				mapObjectTargetsMustBeAutoAttackable = false,
				validator = ((TargetInfo target) =>
				{
					if (!target.HasThing)
						return false;
					Thing dildo = target.Thing as Thing;
					if (dildo == null)
						return false;

					return true;
				})
			};
		}

		public static List<FloatMenuOption> ChoicesAtFor(List<FloatMenuOption> __instance, Vector3 clickPos, Pawn pawn, bool suppressAutoTakeableGoto = false)
		{
			rjwclickPos = clickPos;
			if (SaveStorage.ModId == "RJW")// disable rmb for SJW
				SexFloatMenuOption(pawn, ref __instance);
			return __instance;
		}
		public static void SexFloatMenuOption(Pawn pawn, ref List<FloatMenuOption> opts)
		{
			if (!ShowRMB(pawn)) return;

			//Log.Message("show options");

			// Find valid targets for sex.
			var validtargets = GenUI.TargetsAt(rjwclickPos, TargetParameters);
			//Log.Message("targets count " + validtargets.Count());

			foreach (LocalTargetInfo target in validtargets)
			{
				if (target.Pawn != null && target.Pawn.Drafted)
					continue;
				// Ensure target is reachable.
				if (!pawn.CanReach(target, PathEndMode.ClosestTouch, Danger.Deadly))
				{
					//option = new FloatMenuOption("CannotReach".Translate(target.Thing.LabelCap, target.Thing) + " (" + "NoPath".Translate() + ")", null);
					continue;
				}

				//Log.Message("target " +  target.Label);
				opts.AddRange(GenerateRMBOptions(pawn, target).Where(x => x.action != null));
				//sex-role?-pose ?
				//rjw?-sex(do fuck/rape checks)-role?-pose ?
				//sex-rjwsextype?-interactiondefs?
			}
		}

		public static bool ShowRMB(Pawn pawn)
		{
			// If the pawn in question cannot take jobs, don't bother.
			if (pawn.jobs == null)
				return false;

			// If the pawn is drafted - quit.
			if (pawn.Drafted)
				return false;

			// Getting raped - no control
			if (pawn.jobs.curDriver is JobDriver_SexBaseRecieverRaped)
				return false;

			//is colonist?, is hospitality colonist/guest?, no control for guests
			if (!pawn.IsFreeColonist || pawn.Faction == null || pawn.GetExtraHomeFaction(null) != null)
				return false;

			//not hero mode or override_control - quit
			if (!(RJWSettings.RPG_hero_control || RJWSettings.override_control))
				return false;

			var HeroOK0 = false;    //is hero
			var HeroOK1 = false;    //owned hero?
			var HeroOK2 = false;    //not owned hero? maybe prison check etc in future
									// || xxx.is_slave(pawn)
			if (RJWSettings.RPG_hero_control)
			{
				HeroOK0 = pawn.IsDesignatedHero();
				HeroOK1 = HeroOK0 && pawn.IsHeroOwner();
				HeroOK2 = HeroOK0 && !pawn.IsHeroOwner();

				//Log.Message("show options HeroOK0 " + HeroOK0);
				//Log.Message("show options HeroOK1 " + HeroOK1);
				//Log.Message("show options HeroOK2 " + HeroOK2);

			}
			else if (!RJWSettings.override_control)
				return false;

			//not hero, not override_control - quit
			if (!HeroOK0 && !RJWSettings.override_control)
				return false;

			//not owned hero - quit
			if (HeroOK0 && HeroOK2)
				return false;

			if (pawn.IsPrisoner || xxx.is_slave(pawn))
				return false;

			return true;
		}

		public static List<FloatMenuOption> GenerateRMBOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			// Already doing sex.
			// Pose switch
			//if (pawn.jobs.curDriver is JobDriver_Sex)
			//{
			//	// Masturbating
			//	if (target.Pawn == pawn)
			//	{
			//		if (pawn.jobs.curDriver is JobDriver_Masturbate)
			//		{
			//			option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Masturbate".Translate(), delegate ()
			//			{
			//				FloatMenuUtility.MakeMenu(GenerateSoloSexPoseOptions(pawn, target).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);

			//			}, MenuOptionPriority.High), pawn, target);
			//			opts.AddDistinct(option);
			//		}
			//	}
			//	// TODO: Add pose switch
			//	return opts;
			//}

			return opts;
		}

		public static List<FloatMenuOption> GenerateNonSoloSexRoleOptions(Pawn pawn, LocalTargetInfo target, JobDef job, bool rape, bool reverse = false)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			var partner = target.Pawn;

			if (target.Thing is Corpse)
				partner = (target.Thing as Corpse).InnerPawn;

			var arraytype = 0;      //sex
			if (xxx.is_animal(partner))
			{
				arraytype = 2;      //bestiality/breeding
			}
			else if (rape)
			{
				arraytype = 1;      //rape
			}
			List<InteractionDef> validintdefs = new List<InteractionDef>();
			foreach (InteractionDef d in SexUtility.SexInterractions)
			{
				var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(d);
				if (interaction.Extension.rjwSextype == xxx.rjwSextype.None.ToStringSafe())
					continue;
				if (interaction.Extension.rjwSextype == xxx.rjwSextype.Masturbation.ToStringSafe())
					continue;

				if (
					(interaction.HasInteractionTag(InteractionTag.Consensual) && arraytype == 0) ||
					(interaction.HasInteractionTag(InteractionTag.Rape) && arraytype == 1) ||
					(interaction.HasInteractionTag(InteractionTag.Bestiality) && arraytype == 2)
					)
					if (reverse && interaction.HasInteractionTag(InteractionTag.Reverse) || (!reverse && !interaction.HasInteractionTag(InteractionTag.Reverse)))
					{
						ILewdInteractionValidatorService service = LewdInteractionValidatorService.Instance;
						if (service.IsValid(d, pawn, partner))
							validintdefs.Add(d);
					}
			}

			foreach (InteractionDef dictionaryKey in validintdefs)
			{
				var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(dictionaryKey);
				var dev = "";
				if (RJWSettings.DevMode)
					dev = " ( defName: " + dictionaryKey.defName + ")";

				var label = interaction.Extension.RMBLabel.CapitalizeFirst() + dev;
				option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(label, delegate ()
				{
					HaveSex(pawn, job, target, dictionaryKey);
				}, MenuOptionPriority.High), pawn, target);
				opts.AddDistinct(option);
			}

			if (opts.NullOrEmpty())
				opts.AddDistinct(new FloatMenuOption("none", null));

			return opts;
		}

		//multiplayer synch actions

		[SyncMethod]
		public static void HaveSex(Pawn pawn, JobDef jobDef, LocalTargetInfo target, InteractionDef dictionaryKey)
		{
			bool rape;
			Pawn partner = null;

			if (target.Thing is Corpse)
				partner = (target.Thing as Corpse).InnerPawn;
			else if (target.Pawn == null || pawn == target.Pawn) // masturbation
			{
			}
			else
				partner = target.Pawn;

			InteractionWithExtension interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(dictionaryKey);

			rape = interaction.HasInteractionTag(InteractionTag.Rape);

			Job job;
			if (jobDef == xxx.casual_sex)
				job = new Job(jobDef, target, partner.CurrentBed());
			else if (jobDef == xxx.bestialityForFemale)
				job = new Job(jobDef, target, pawn.ownership.OwnedBed);
			else if (jobDef == xxx.Masturbate)
			{
				job = new Job(jobDef, pawn, null, target.Cell);
				partner = pawn;
			}
			else
				job = new Job(jobDef, target);

			var SP = new SexProps();
			SP.pawn = pawn;
			SP.partner = partner;
			SP.sexType = SexUtility.rjwSextypeGet(dictionaryKey); ;
			SP.isRape = rape;
			SP.isRapist = rape;
			SP.canBeGuilty = false; //pawn.IsHeroOwner();//TODO: fix for MP someday
			SP.dictionaryKey = dictionaryKey;
			SP.rulePack = SexUtility.SexRulePackGet(dictionaryKey);

			pawn.GetRJWPawnData().SexProps = SP;
			pawn.jobs.EndCurrentJob(JobCondition.InterruptForced);
			pawn.jobs.TryTakeOrderedJob(job);
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	static class RMB_Rape
	{

		static RMB_Rape()
		{
			Harmony harmony = new Harmony("rjw");
			//start sex options
			harmony.Patch(AccessTools.Method(typeof(RMB_Menu), "GenerateRMBOptions"), prefix: null,
				postfix: new HarmonyMethod(typeof(RMB_Rape), nameof(ChoicesAtFor)));
		}

		public static List<FloatMenuOption> ChoicesAtFor(List<FloatMenuOption> __instance, Pawn pawn, LocalTargetInfo target)
		{
			FloatMenuOption(pawn, ref __instance, ref target);
			return __instance;
		}
		public static void FloatMenuOption(Pawn pawn, ref List<FloatMenuOption> opts, ref LocalTargetInfo target)
		{
			opts.AddRange(GenerateRMBOptions(pawn, target).Where(x => x.action != null));
			opts.AddRange(GenerateRMBOptions(pawn, target, true).Where(x => x.action != null));
		}

		public static List<FloatMenuOption> GenerateRMBOptions(Pawn pawn, LocalTargetInfo target, bool reverse = false)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;
			bool rape = true;

			if (target.Pawn != pawn)
				if (target.Thing != pawn as Thing)// && !MP.IsInMultiplayer)
				{
					if (target.Pawn != null || target.Thing is Corpse)
						if (xxx.can_rape(pawn, true))
						{
							//Log.Message("targets can_rape " + target.Label);
							string text = null;
							Action action = null;

							if (target.Thing is Corpse && RJWSettings.necrophilia_enabled)
							{
								if (reverse)
								{
									text = "RJW_RMB_RapeCorpse_Reverse".Translate() + ((Corpse)target.Thing).InnerPawn.NameShortColored;
								}
								else
									text = "RJW_RMB_RapeCorpse".Translate() + ((Corpse)target.Thing).InnerPawn.NameShortColored;
								action = delegate ()
								{
									JobDef job = xxx.RapeCorpse;
									var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
									if (validinteractions.Any())
										FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
									else
										Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);

								};
							}
							else if (target.Pawn != null && xxx.can_be_fucked(target.Pawn) &&
							(xxx.is_human(target.Pawn) || (xxx.is_animal(target.Pawn) && RJWSettings.bestiality_enabled)))
									//(xxx.is_human(target.Pawn)))
							{
								//Log.Message("targets can_rape 1 " + target.Label);
								if (target.Pawn.HostileTo(pawn))
								{
									//Log.Message("targets can_rape HostileTo " + target.Label);
									if (target.Pawn.Downed)
									{
										if (reverse)
										{
											text = "RJW_RMB_Rape_Reverse".Translate() + target.Pawn.NameShortColored;
										}
										else
											text = "RJW_RMB_Rape".Translate() + target.Pawn.NameShortColored;
										action = delegate ()
										{
											JobDef job = xxx.RapeEnemy;
											var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
											if (validinteractions.Any())
												FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
											else
												Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
										};
									}
								}
								else if (xxx.is_animal(target.Pawn) && xxx.can_fuck(pawn))
								{
									if (reverse)
									{
										text = "RJW_RMB_RapeAnimal_Reverse".Translate() + target.Pawn.NameShortColored;
									}
									else
										text = "RJW_RMB_RapeAnimal".Translate() + target.Pawn.NameShortColored;
									action = delegate ()
									{
										JobDef job = xxx.bestiality;
										var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
										if (validinteractions.Any())
											FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
										else
											Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
									};
								}
								else if (target.Pawn.IsDesignatedComfort())
								{
									//Log.Message("targets can_rape IsDesignatedComfort " + target.Label);
									if (reverse)
									{
										text = "RJW_RMB_Rape_Reverse".Translate() + target.Pawn.NameShortColored;
									}
									else
										text = "RJW_RMB_Rape".Translate() + target.Pawn.NameShortColored;
									action = delegate ()
									{
										JobDef job = xxx.RapeCP;
										var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
										if (validinteractions.Any())
											FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
										else
											Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
									};
								}
								else if (xxx.can_get_raped(target.Pawn) && (xxx.get_vulnerability(target.Pawn) >= xxx.get_vulnerability(pawn)))
								{
									//Log.Message("targets can_rape else " + target.Label);
									if (reverse)
									{
										text = "RJW_RMB_Rape_Reverse".Translate() + target.Pawn.NameShortColored;
									}
									else
										text = "RJW_RMB_Rape".Translate() + target.Pawn.NameShortColored;
									action = delegate ()
									{
										JobDef job = xxx.RapeRandom;
										var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
										if (validinteractions.Any())
											FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
										else
											Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
									};
								}
							}

							option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(text, action, MenuOptionPriority.High), pawn, target);
							opts.AddDistinct(option);
						}
				}
			return opts;
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	static class RMB_Sex
	{

		static RMB_Sex()
		{
			Harmony harmony = new Harmony("rjw");
			//start sex options
			harmony.Patch(AccessTools.Method(typeof(RMB_Menu), "GenerateRMBOptions"), prefix: null,
				postfix: new HarmonyMethod(typeof(RMB_Sex), nameof(ChoicesAtFor)));
		}

		public static List<FloatMenuOption> ChoicesAtFor(List<FloatMenuOption> __instance, Pawn pawn, LocalTargetInfo target)
		{
			FloatMenuOption(pawn, ref __instance, ref target);
			return __instance;
		}
		public static void FloatMenuOption(Pawn pawn, ref List<FloatMenuOption> opts, ref LocalTargetInfo target)
		{
			opts.AddRange(GenerateRMBOptions(pawn, target).Where(x => x.action != null));
			opts.AddRange(GenerateRMBOptions(pawn, target, true).Where(x => x.action != null));
		}

		public static List<FloatMenuOption> GenerateRMBOptions(Pawn pawn, LocalTargetInfo target, bool reverse = false)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;
			bool rape = false;

			if (target.Pawn != pawn)
				if (target.Thing != pawn as Thing)// && !MP.IsInMultiplayer)
				{
					if (target.Pawn != null)
						if (!target.Pawn.Downed && !target.Pawn.HostileTo(pawn) && (xxx.can_be_fucked(target.Pawn) || xxx.can_fuck(target.Pawn)))
						{
							string text = null;
							//Action action = null;
							if (xxx.is_human(target.Pawn) && SexAppraiser.would_fuck(target.Pawn, pawn) > 0.1f)
							{
								if (reverse)
								{
									text = "RJW_RMB_Sex_Reverse".Translate() + target.Pawn.NameShortColored;
								}
								else
									text = "RJW_RMB_Sex".Translate() + target.Pawn.NameShortColored;
								option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(text, delegate ()
								{
									JobDef job = null;
									if (target.Pawn.InBed())
										job = xxx.casual_sex;
									else
										job = xxx.quick_sex;

									var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
									if (validinteractions.Any())
										FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
									else
										Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
								}, MenuOptionPriority.High), pawn, target);
								opts.AddDistinct(option);
							}
							else if (xxx.is_animal(target.Pawn) && RJWSettings.bestiality_enabled && target.Pawn.Faction == pawn.Faction)
							{
								if (pawn.ownership.OwnedBed != null && target.Pawn.CanReach(pawn.ownership.OwnedBed, PathEndMode.OnCell, Danger.Some) && !AnimalPenUtility.NeedsToBeManagedByRope(target.Pawn))
								{
									if (reverse)
									{
										text = "RJW_RMB_Bestiality_Reverse".Translate() + target.Pawn.NameShortColored;
									}
									else
										text = "RJW_RMB_Bestiality".Translate() + target.Pawn.NameShortColored;
									option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(text, delegate ()
									{
										JobDef job = xxx.bestialityForFemale;
										var validinteractions = RMB_Menu.GenerateNonSoloSexRoleOptions(pawn, target, job, rape, reverse).Where(x => x.action != null);
										if (validinteractions.Any())
											FloatMenuUtility.MakeMenu(validinteractions, (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
										else
											Messages.Message("No valid interactions found for " + text, MessageTypeDefOf.RejectInput, false);
									}, MenuOptionPriority.High), pawn, target);
									opts.AddDistinct(option);
								}
							}
						}
				}
			return opts;
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	static class RMB_Socialize
	{

		static RMB_Socialize()
		{
			Harmony harmony = new Harmony("rjw");
			//start sex options
			harmony.Patch(AccessTools.Method(typeof(RMB_Menu), "GenerateRMBOptions"), prefix: null,
				postfix: new HarmonyMethod(typeof(RMB_Socialize), nameof(ChoicesAtFor)));
		}

		public static List<FloatMenuOption> ChoicesAtFor(List<FloatMenuOption> __instance, Pawn pawn, LocalTargetInfo target)
		{
			FloatMenuOption(pawn, ref __instance, ref target);
			return __instance;
		}
		public static void FloatMenuOption(Pawn pawn, ref List<FloatMenuOption> opts, ref LocalTargetInfo target)
		{
			opts.AddRange(GenerateRMBOptions(pawn, target).Where(x => x.action != null));
		}

		public static List<FloatMenuOption> GenerateRMBOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			if (target.Pawn != null && target.Pawn != pawn && !pawn.interactions.InteractedTooRecentlyToInteract() && pawn.interactions.CanInteractNowWith(target.Pawn))
			{
				//if (pawn.jobs.curDriver is JobDriver_Masturbate)
				{
					option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Social".Translate(), delegate ()
					{
						FloatMenuUtility.MakeMenu(GenerateSocialOptions(pawn, target).Where(x => x.action != null), (FloatMenuOption opt) => opt.Label, (FloatMenuOption opt) => opt.action);
					}, MenuOptionPriority.High), pawn, target);
					opts.AddDistinct(option);
				}
			}
			return opts;
		}

		public static List<FloatMenuOption> GenerateSocialOptions(Pawn pawn, LocalTargetInfo target)
		{
			List<FloatMenuOption> opts = new List<FloatMenuOption>();
			FloatMenuOption option = null;

			option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Insult".Translate(), delegate ()
			{
				Socialize(pawn, target, InteractionDefOf.Insult);
			}, MenuOptionPriority.High), pawn, target);
			opts.AddDistinct(option);

			if (!pawn.HostileTo(target.Pawn))
			{
				option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_SocialFight".Translate(), delegate ()
				{
					SocializeFight(pawn, target);
				}, MenuOptionPriority.High), pawn, target);
				opts.AddDistinct(option);

				option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_Chat".Translate(), delegate ()
				{
					Socialize(pawn, target, InteractionDefOf.Chitchat);
				}, MenuOptionPriority.High), pawn, target);
				opts.AddDistinct(option);

				// OP shit +12 relations, enable in cheat menu
				if (RJWSettings.Allow_RMB_DeepTalk)
				{
					option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_DeepTalk".Translate(), delegate ()
					{
						pawn.interactions.TryInteractWith(target.Pawn, InteractionDefOf.DeepTalk);
					}, MenuOptionPriority.High), pawn, target);
					opts.AddDistinct(option);
				}

				if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target.Pawn))
				{
					option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_RomanceAttempt".Translate(), delegate ()
					{
						Socialize(pawn, target, InteractionDefOf.RomanceAttempt);
					}, MenuOptionPriority.High), pawn, target);
					opts.AddDistinct(option);
				}

				if (pawn.relations.DirectRelationExists(PawnRelationDefOf.Lover, target.Pawn) || pawn.relations.DirectRelationExists(PawnRelationDefOf.Fiance, target.Pawn))
				{
					option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("RJW_RMB_MarriageProposal".Translate(), delegate ()
					{
						Socialize(pawn, target, InteractionDefOf.MarriageProposal);
					}, MenuOptionPriority.High), pawn, target);
					opts.AddDistinct(option);
				}
			}

			return opts;
		}

		//multiplayer synch actions
		[SyncMethod]
		static void SocializeFight(Pawn pawn, LocalTargetInfo target)
		{
			pawn.interactions.StartSocialFight(target.Pawn);
		}
		[SyncMethod]
		static void Socialize(Pawn pawn, LocalTargetInfo target, InteractionDef interaction)
		{
			pawn.interactions.TryInteractWith(target.Pawn, interaction);
		}
	}
}


code:
using HarmonyLib;
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Helpers;
using rjw.Modules.Shared.Logs;
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
	{
		private static ILog _log = LogManager.GetLogger(nameof(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)
		};

		/// <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);
			float opinion = rapist.relations.OpinionOf(rapee);

			//remove Pretty trait bonus
			//ThoughtWorker_Pretty
			float beauty = rapee.GetStatValue(StatDefOf.PawnBeauty, true) * 20f;

			opinion -= beauty;

			// 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
				opinion -= beauty;
				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
				opinion -= beauty;
				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;
			}
			var parts = pawn.GetGenitalsList();
			if (Genital_Helper.has_vagina(pawn, parts) || Genital_Helper.has_anus(pawn))
			{
				if (!(Genital_Helper.has_penis_fertile(pawn, parts) || Genital_Helper.has_penis_infertile(pawn, parts)))
				{
					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 (xxx.has_traits(pawn))
			{
				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(TraitDefOf.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.DistanceTo(target.Position);

			//ModLog.Message("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)
			{
				//ModLog.Message("would_fuck_animal(NT):: base: " + fuc + ", bound1: " + fuc * 0.75f);
				//ModLog.Message("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))
			{
				//ModLog.Message("would_fuck_animal(T):: base: " + fuc + ", bound1: " + fuc * 1.25f);
				//ModLog.Message("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)
		{

			//--ModLog.Message("would_fuck("+xxx.get_pawnname(fucker)+","+xxx.get_pawnname(fucked)+","+invert_opinion.ToString()+") is called");
			if (xxx.is_human(fucker) && !xxx.is_healthy_enough(fucker) && !xxx.is_psychopath(fucker)) // Not healthy enough to have sex, shouldn't have got this far.
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Not healthy enough");
				return 0f;
			}
			if ((xxx.is_animal(fucker) || xxx.is_animal(fucked)) && (!xxx.is_animal(fucker) || !xxx.is_animal(fucked)) && !RJWSettings.bestiality_enabled)
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Animals disabled");
				return 0f;
			}
			if (fucked.Dead && !RJWSettings.necrophilia_enabled)
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Necrophilia disabled");
				return 0f;
			}
			if (fucker.Dead || fucker.Suspended || fucked.Suspended)
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Target unreachable");
				return 0f;
			}
			if (xxx.is_starved(fucked) && fucked.Faction == fucker.Faction && !xxx.is_psychopath(fucker) && !xxx.is_rapist(fucker))
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Target is starving");
				return 0f;
			}
			if (xxx.is_human(fucker) && !ignore_bleeding && !xxx.is_not_dying(fucked) && !xxx.is_psychopath(fucker) && !xxx.is_rapist(fucker) && !xxx.is_bloodlust(fucker))
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Target is dying");
				return 0f;
			}
			if (!IsGenderOk(fucker, fucked))
			{
				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug("Genders not compatible");
				return 0.0f;
			}

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

			// --- Age checks ---
			if (!IsAgeOk(fucker, fucked, fucker_age, fucked_age))
			{
				float scaledFuckerAge = AgeHelper.ScaleToHumanAge(fucker);
				float scaledFuckedAge = AgeHelper.ScaleToHumanAge(fucked);

				_log.Debug($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
				_log.Debug($"ages KO : original {fucker_age}/{fucked_age} - scaled {scaledFuckerAge}/{scaledFuckedAge}");
				return 0.0f;
			}
			float age_factor = GetAgeFactor(fucker, fucked, fucked_age);

			// --- Orientation checks ---
			float orientation_factor = GetOrientationFactor(fucker, fucked, ignore_gender);
			if (orientation_factor == 0.0f)
			{
				orientation_factor = fucker.relations.SecondaryLovinChanceFactor(fucked);
				//Log.Message("would_fuck() SecondaryLovinChanceFactor:" + orientation_factor);
				if (orientation_factor <= 0)
					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) || fucker.health.hediffSet.HasHediff(HediffDef.Named("HumpShroomEffect"))) ? 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());

			//if (xxx.is_human(fucker) && xxx.is_human(fucked))
			//{
			//	Log.Message($"{nameof(would_fuck)} - {fucker.GetName()} on {fucked.GetName()}");
			//	Log.Message($"[age {age_factor}] [orientation {orientation_factor}] [body {body_factor}] [opinion {opinion_factor}] [horniness {horniness_factor}] [final {final_att}]");
			//}

			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 (fucked.GetStatValue(StatDefOf.PawnBeauty) >= 2)
						body_factor *= 1.25f;
					else if (fucked.GetStatValue(StatDefOf.PawnBeauty) >= 1)
						body_factor *= 1.1f;
					else if (fucked.GetStatValue(StatDefOf.PawnBeauty) < 0)
						if (fucked.GetStatValue(StatDefOf.PawnBeauty) >= -1)
							body_factor *= 0.8f;
						else
							body_factor *= 0.5f;

					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 (!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;

			age_factor = fucked.gender == Gender.Male
				? AgeConfigDef.Instance.attractivenessByAgeMale.Evaluate(AgeHelper.ScaleToHumanAge(fucker))
				: AgeConfigDef.Instance.attractivenessByAgeFemale.Evaluate(AgeHelper.ScaleToHumanAge(fucked));

			//if (RJWSettings.DevMode) ModLog.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 fucked_age)
		{
			bool age_ok = false;
			{
				if (xxx.is_mechanoid(fucker) || xxx.can_do_loving(fucker))
				{
					age_ok = true;
				}

				if (age_ok && (xxx.is_mechanoid(fucked) || xxx.can_do_loving(fucked)))
				{
					age_ok = true;
				}
				//else if (xxx.is_human(fucker) && xxx.is_animal(fucked))
				//{
				//	float li = fucked.RaceProps.lifeExpectancy;
				//	if (fucked_age > li * 0.07f && fucked_age < li * 0.625f)
				//	{
				//		return true;
				//	}
				//}
				else
					age_ok = false;

				if (age_ok && xxx.is_human(fucker) && xxx.is_human(fucked))
				{
					if (RJWSettings.sex_age_legacymode)
					{
						if (fucker_age < RJWSettings.sex_free_for_all_age || fucked_age < RJWSettings.sex_free_for_all_age)
							age_ok = Math.Abs(fucker.ageTracker.AgeBiologicalYearsFloat - fucked.ageTracker.AgeBiologicalYearsFloat) < 2.05f;
					}
					else
					{
						float scaledFuckerAge = AgeHelper.ScaleToHumanAge(fucker);
						float scaledFuckedAge = AgeHelper.ScaleToHumanAge(fucked);

						if (fucker.ageTracker.Growth < 1 || fucked.ageTracker.Growth < 1)
							age_ok = Math.Abs(scaledFuckerAge - scaledFuckedAge) < 2.05f;
						//Log.Message(" AdultMinAge " + fucker.ageTracker.AdultMinAge);
					}
				}
			}
			//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)
			{
				ModLog.Warning(e.ToString());
			}
			return ret;
		}
	}
}


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

namespace rjw
{
	[StaticConstructorOnStartup]
	public class SexGizmo : Gizmo
	{
		public SexGizmo(Pawn __instance)
		{
			this.pawn = __instance;
			this.order = -100f;
			this.LimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/EntropyLimit/Limited", true);
			this.UnlimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/EntropyLimit/Unlimited", true);
			this.AttackTex = ContentFinder<Texture2D>.Get("UI/Icons/HostilityResponse/Attack", true);
			//this.LimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/HostilityResponse/Ignore", true);
			//this.UnlimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/HostilityResponse/Attack", true);
			//this.LimitedTex = ContentFinder<Texture2D>.Get("UI/Commands/AttackMelee", true);
			//this.UnlimitedTex = ContentFinder<Texture2D>.Get("UI/Commands/AttackMelee", true);
		}

		public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
		{
			if (pawn.jobs?.curDriver is JobDriver_Sex)
			{
				Rect rect = new Rect(topLeft.x, topLeft.y, this.GetWidth(maxWidth), 75f);
				Rect rect2 = rect.ContractedBy(6f);
				Widgets.DrawWindowBackground(rect);

				//row1 label1
				Text.Font = GameFont.Small;
				Rect rect3 = rect2;
				rect3.y += 6f;
				rect3.height = Text.LineHeight;
				Widgets.Label(rect3, "RJW_SexGizmo_Orgasm".Translate());

				//row2 label2
				Rect rect4 = rect2;
				rect4.y += 38f;
				rect4.height = Text.LineHeight;
				Widgets.Label(rect4, "RJW_SexGizmo_RestNeed".Translate());

				//row1 meter bar fill
				Rect rect5 = rect2;
				rect5.x += 63f;
				rect5.y += 6f;
				rect5.width = 100f;
				rect5.height = 22f;
				float duration = (pawn.jobs?.curDriver as JobDriver_Sex).duration;
				//float ticks_left = ((JobDriver_Sex)pawn.jobs?.curDriver).ticks_left;
				float sex_ticks = (pawn.jobs?.curDriver as JobDriver_Sex).sex_ticks;
				float orgasm = 0;
				//Log.Message("duration:" +duration+", ticks_left:" +ticks_left+ ", ticks_left/duration:" + duration/ticks_left);
				//Log.Message(""+ ((ticks_left/duration) - 1f));
				orgasm = (1f - sex_ticks / duration);
				//orgasm = sex_ticks;
				Widgets.FillableBar(rect5, Mathf.Max(orgasm, 0f), SexGizmo.EntropyBarTex, SexGizmo.EmptyBarTex, true);

				//row1 meter values
				string label = (orgasm * 100).ToString("F0") + " / " + 100;
				Text.Font = GameFont.Small;
				Text.Anchor = TextAnchor.MiddleCenter;
				Widgets.Label(rect5, label);

				Text.Anchor = TextAnchor.UpperLeft;
				Text.Font = GameFont.Tiny;
				GUI.color = Color.white;

				//row1 meter description
				//Rect rect6 = rect2;
				//rect6.width = 175f;
				//rect6.height = 38f;
				//TooltipHandler.TipRegion(rect6, delegate()
				//{
				//	float f = this.tracker.EntropyValue / this.tracker.RecoveryRate;
				//	return string.Format("PawnTooltipPsychicEntropyStats".Translate(), new object[]
				//	{
				//		Mathf.Round(this.tracker.EntropyValue),
				//		Mathf.Round(this.tracker.MaxEntropy),
				//		this.tracker.RecoveryRate.ToString("0.#"),
				//		Mathf.Round(f)
				//	}) + "\n\n" + "PawnTooltipPsychicEntropyDesc".Translate();
				//}, Gen.HashCombineInt(this.tracker.GetHashCode(), 133858));

				//row2 meter bar fill
				Rect rect7 = rect2;
				rect7.x += 63f;
				rect7.y += 38f;
				rect7.width = 100f;
				rect7.height = 22f;
				if (pawn.needs?.TryGetNeed<Need_Rest>() != null)
					Widgets.FillableBar(rect7, Mathf.Min(pawn.needs.TryGetNeed<Need_Rest>().CurLevel, 1f), SexGizmo.PsyfocusBarTex, SexGizmo.EmptyBarTex, true);

				GUI.color = Color.white;

				//toggles
				if (!MP.IsInMultiplayer)   //TODO: someday write mp synchronizers?
					if (pawn.IsColonistPlayerControlled && pawn.CanChangeDesignationColonist())
					{
						//row1 toggle
						if (pawn.jobs?.curDriver is JobDriver_SexBaseInitiator)
						{
							float num = 32f;
							float num2 = 4f;
							float num3 = rect2.height / 2f - num + num2;
							float num4 = rect2.width - num;
							Rect rect9 = new Rect(rect2.x + num4, rect2.y + num3, num, num);
							if (Widgets.ButtonImage(rect9, !(pawn.jobs?.curDriver as JobDriver_Sex).neverendingsex ? this.LimitedTex : this.UnlimitedTex, true))
							{
								if (pawn.jobs?.curDriver is JobDriver_SexBaseInitiator)
								{
									(pawn.jobs?.curDriver as JobDriver_Sex).neverendingsex = !(pawn.jobs?.curDriver as JobDriver_Sex).neverendingsex;
									if ((pawn.jobs?.curDriver as JobDriver_Sex).neverendingsex)
									{
										SoundDefOf.Tick_Low.PlayOneShotOnCamera(null);
									}
									else
									{
										SoundDefOf.Tick_High.PlayOneShotOnCamera(null);
									}
								}
							}
							TooltipHandler.TipRegionByKey(rect9, "RJW_SexGizmo_SexOverdriveTooltip");
						}

						//row2 toggle
						if (pawn.jobs?.curDriver is JobDriver_SexBaseInitiator)   //TODO: maybe allow for receiver control someday?
						{
							if ((pawn.jobs?.curDriver as JobDriver_Sex).Sexprops != null)
								if ((pawn.jobs?.curDriver as JobDriver_Sex).Sexprops.isRapist)   //TODO: beating interrupts loving, fix someday?
								{
									float num = 32f;
									float num2 = 34f;
									float num3 = rect2.height / 2f - num + num2;
									float num4 = rect2.width - num;
									Rect rect11 = new Rect(rect2.x + num4, rect2.y + num3, num, num);
									if (Widgets.ButtonImage(rect11, AttackTex, true))
									{
										(pawn.jobs?.curDriver as JobDriver_Sex).beatonce = true;
										//SexUtility.Sex_Beatings_Dohit(pawn, ((JobDriver_Sex)pawn.jobs?.curDriver).Partner, ((JobDriver_Sex)pawn.jobs?.curDriver).isRape);
										SoundDefOf.Tick_High.PlayOneShotOnCamera(null);
									}
									TooltipHandler.TipRegionByKey(rect11, "RJW_SexGizmo_HitPawn");
								}
						}
					}
			}

			return new GizmoResult(GizmoState.Clear);
		}

		public override float GetWidth(float maxWidth)
		{
			return 212f;
		}

		private Pawn pawn;

		private Texture2D LimitedTex;

		private Texture2D UnlimitedTex;

		private Texture2D AttackTex;

		private const string LimitedIconPath = "UI/Icons/EntropyLimit/Limited";

		private const string UnlimitedIconPath = "UI/Icons/EntropyLimit/Unlimited";

		private const string AttackIconPath = "UI/Icons/HostilityResponse/Attack";

		private static readonly Color PainBoostColor = new Color(0.2f, 0.65f, 0.35f);

		private static readonly Texture2D EntropyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.46f, 0.34f, 0.35f));

		private static readonly Texture2D OverLimitBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.75f, 0.2f, 0.15f));

		private static readonly Texture2D PsyfocusBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.34f, 0.42f, 0.43f));

		private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.03f, 0.035f, 0.05f));
	}
}


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


code:
using System;
using Verse;

namespace rjw
{
	public static class SexPartTypeExtensions
	{
		public static BodyPartDef GetBodyPartDef(this SexPartType sexPartType)
		{
			return sexPartType switch
			{
				SexPartType.Anus => xxx.anusDef,
				SexPartType.FemaleBreast => xxx.breastsDef,
				SexPartType.FemaleGenital => xxx.genitalsDef,
				SexPartType.MaleBreast => xxx.breastsDef,
				SexPartType.MaleGenital => xxx.genitalsDef,
				_ => throw new ArgumentException($"Unrecognized sexPartType: {sexPartType}")
			};
		}
	}
}


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");
		}
	}
	*/
}