code:
using System;
using UnityEngine;
using Verse;

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

		public static bool rape_enabled = false;
		public static bool designated_freewill = true;
		public static bool animal_CP_rape = false;
		public static bool visitor_CP_rape = false;
		public static bool colonist_CP_rape = false;
		public static bool rape_beating = false;
		public static bool gentle_rape_beating = false;

		public static bool cum_filth = true;
		public static bool cum_on_body = true;
		public static float cum_on_body_amount_adjust = 1.0f;
		public static bool cum_overlays = true;
		public static bool sounds_enabled = true;

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

		public static bool nymphos = true;
		public static bool NymphosRaidEasy = false;
		public static bool NymphosRaidHard = false;
		public static bool NymphosRaidHardcore = true;
		public static bool FemaleFuta = false;
		public static bool MaleTrap = false;
		public static float male_nymph_chance = 0.0f;
		public static float futa_nymph_chance = 0.0f;
		public static float futa_natives_chance = 0.0f;
		public static float futa_spacers_chance = 0.5f;

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

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

		public static bool RPG_direct_control = false;
		public static bool RPG_hero_control = false;
		public static bool RPG_hero_control_HC = false;
		public static bool RPG_hero_control_Ironman = false;

		public static bool whoringtab_enabled = true;
		public static bool submit_button_enabled = true;
		public static bool show_RJW_designation_box = true;
		public static ShowParts ShowRjwParts = ShowParts.Show;
		public static bool StackRjwParts = false;
		public static float maxDistancetowalk = 250;

		public static bool WildMode = false;
		public static bool ForbidKidnap = false;
		public static bool override_RJW_designation_checks = false;
		public static bool GenderlessAsFuta = false;
		public static bool DevMode = false;
		public static bool DebugLogJoinInBed = false;
		public static bool DebugWhoring = false;
		public static bool AddTrait_Rapist = true;
		public static bool AddTrait_Masocist = true;
		public static bool AddTrait_Nymphomaniac = true;
		public static bool AddTrait_Necrophiliac = true;
		public static bool AddTrait_Nerves = true;
		public static bool AddTrait_Zoophiliac = true;

		private static Vector2 scrollPosition;
		private static float height_modifier = 100f;

		public enum ShowParts
		{
			Show,
			Known,
			Hide
		};

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

			//30f for top page description and bottom close button
			Rect outRect = new Rect(0f, 30f, inRect.width, inRect.height - 30f);
			//-16 for slider, height_modifier - additional height for options
			Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, inRect.height + height_modifier);

			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.maxOneColumn = true;
			listingStandard.ColumnWidth = viewRect.width / 2.05f;
			listingStandard.BeginScrollView(outRect, ref scrollPosition, ref viewRect);
			listingStandard.Begin(viewRect);
			listingStandard.Gap(4f);
			listingStandard.CheckboxLabeled("animal_on_animal_enabled".Translate(), ref animal_on_animal_enabled, "animal_on_animal_enabled_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("necrophilia_enabled".Translate(), ref necrophilia_enabled, "necrophilia_enabled_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("bestiality_enabled".Translate(), ref bestiality_enabled, "bestiality_enabled_desc".Translate());
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("rape_enabled".Translate(), ref rape_enabled, "rape_enabled_desc".Translate());
			if (rape_enabled)
			{
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("  " + "ColonistCanCP".Translate(), ref colonist_CP_rape, "ColonistCanCP_desc".Translate());
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("  " + "VisitorsCanCP".Translate(), ref visitor_CP_rape, "VisitorsCanCP_desc".Translate());
				listingStandard.Gap(3f);
				if (!bestiality_enabled)
				{
					GUI.contentColor = Color.grey;
					animal_CP_rape = false;
				}
				listingStandard.CheckboxLabeled("  " + "AnimalsCanCP".Translate(), ref animal_CP_rape, "AnimalsCanCP_desc".Translate());
				if (!bestiality_enabled)
					GUI.contentColor = Color.white;
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("  " + "PrisonersBeating".Translate(), ref rape_beating, "PrisonersBeating_desc".Translate());
				listingStandard.Gap(3f);
				listingStandard.CheckboxLabeled("   " + "GentlePrisonersBeating".Translate(), ref gentle_rape_beating, "GentlePrisonersBeating_desc".Translate());
			}
			else
			{
				colonist_CP_rape = false;
				visitor_CP_rape = false;
				animal_CP_rape = false;
				rape_beating = false;
			}
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("STD_enabled".Translate(), ref stds_enabled, "STD_enabled_desc".Translate());
			listingStandard.Gap(5f);
			if (stds_enabled)
				listingStandard.CheckboxLabeled("  " + "STD_FromFloors".Translate(), ref std_floor, "STD_FromFloors_desc".Translate());
			else
				std_floor = false;
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("cum_filth".Translate(), ref cum_filth, "cum_filth_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("cum_on_body".Translate(), ref cum_on_body, "cum_on_body_desc".Translate());
			listingStandard.Gap(5f);
			if (cum_on_body)
			{
				listingStandard.Label("cum_on_body_amount_adjust".Translate() + ": " + Math.Round(cum_on_body_amount_adjust * 100f, 0) + "%", -1f, "cum_on_body_amount_adjust_desc".Translate());
				cum_on_body_amount_adjust = listingStandard.Slider(cum_on_body_amount_adjust, 0.1f, 10.0f);
				listingStandard.CheckboxLabeled("cum_overlays".Translate(), ref cum_overlays, "cum_overlays_desc".Translate());

			}
			else
				cum_overlays = false;
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("sounds_enabled".Translate(), ref sounds_enabled, "sounds_enabled_desc".Translate());
			listingStandard.Gap(10f);
			listingStandard.CheckboxLabeled("RPG_direct_control_name".Translate(), ref RPG_direct_control, "RPG_direct_control_desc".Translate());
			listingStandard.Gap(5f);
			listingStandard.CheckboxLabeled("RPG_hero_control_name".Translate(), ref RPG_hero_control, "RPG_hero_control_desc".Translate());
			listingStandard.Gap(5f);
			if (RPG_hero_control)
			{
				listingStandard.CheckboxLabeled("RPG_hero_control_HC_name".Translate(), ref RPG_hero_control_HC, "RPG_hero_control_HC_desc".Translate());
				listingStandard.Gap(5f);
				listingStandard.CheckboxLabeled("RPG_hero_control_Ironman_name".Translate(), ref RPG_hero_control_Ironman, "RPG_hero_control_Ironman_desc".Translate());
				listingStandard.Gap(5f);
			}
			else
			{
				RPG_hero_control_HC = false;
				RPG_hero_control_Ironman = false;
			}

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

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

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

			listingStandard.EndScrollView(ref viewRect);
			listingStandard.End();
		}

		public override void ExposeData()
		{
			base.ExposeData();
			Scribe_Values.Look(ref animal_on_animal_enabled, "animal_on_animal_enabled");
			Scribe_Values.Look(ref bestiality_enabled, "bestiality_enabled");
			Scribe_Values.Look(ref necrophilia_enabled, "necrophilia_enabled");
			Scribe_Values.Look(ref designated_freewill, "designated_freewill");
			Scribe_Values.Look(ref rape_enabled, "rape_enabled");
			Scribe_Values.Look(ref colonist_CP_rape, "colonist_CP_rape");
			Scribe_Values.Look(ref visitor_CP_rape, "visitor_CP_rape");
			Scribe_Values.Look(ref animal_CP_rape, "animal_CP_rape");
			Scribe_Values.Look(ref rape_beating, "rape_beating");
			Scribe_Values.Look(ref gentle_rape_beating, "gentle_rape_beating");
			Scribe_Values.Look(ref nymphos, "nymphs_join");
			Scribe_Values.Look(ref NymphosRaidEasy, "NymphosRaidEasy");
			Scribe_Values.Look(ref NymphosRaidHard, "NymphosRaidHard");
			Scribe_Values.Look(ref NymphosRaidHardcore, "NymphosRaidHardcore");
			Scribe_Values.Look(ref FemaleFuta, "FemaleFuta");
			Scribe_Values.Look(ref MaleTrap, "MaleTrap");
			Scribe_Values.Look(ref stds_enabled, "STD_enabled");
			Scribe_Values.Look(ref std_floor, "STD_FromFloors");
			Scribe_Values.Look(ref sounds_enabled, "sounds_enabled");
			Scribe_Values.Look(ref cum_filth, "cum_filth");
			Scribe_Values.Look(ref cum_on_body, "cum_on_body");
			Scribe_Values.Look(ref cum_on_body_amount_adjust, "cum_on_body_amount_adjust");
			Scribe_Values.Look(ref cum_overlays, "cum_overlays");
			Scribe_Values.Look(ref sex_minimum_age, "sex_minimum_age");
			Scribe_Values.Look(ref sex_free_for_all_age, "sex_free_for_all");
			Scribe_Values.Look(ref sexneed_decay_rate, "sexneed_decay_rate");
			Scribe_Values.Look(ref nonFutaWomenRaping_MaxVulnerability, "nonFutaWomenRaping_MaxVulnerability");
			Scribe_Values.Look(ref rapee_MinVulnerability_human, "rapee_MinVulnerability_human");
			Scribe_Values.Look(ref male_nymph_chance, "male_nymph_chance");
			Scribe_Values.Look(ref futa_nymph_chance, "futa_nymph_chance");
			Scribe_Values.Look(ref futa_natives_chance, "futa_natives_chance");
			Scribe_Values.Look(ref futa_spacers_chance, "futa_spacers_chance");
			Scribe_Values.Look(ref RPG_direct_control, "RPG_direct_control");
			Scribe_Values.Look(ref RPG_hero_control, "RPG_hero_control");
			Scribe_Values.Look(ref RPG_hero_control_HC, "RPG_hero_control_HC");
			Scribe_Values.Look(ref RPG_hero_control_Ironman, "RPG_hero_control_Ironman");
		}
	}
}


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

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

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

		public override void DoSettingsWindowContents(Rect inRect)
		{
			if (MP.IsInMultiplayer)
				return;

			RJWSettings.DoWindowContents(inRect);
		}
	}

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

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

		public override void DoSettingsWindowContents(Rect inRect)
		{
			if (MP.IsInMultiplayer)
				return;

			RJWDebugSettings.DoWindowContents(inRect);
		}
	}

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

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

		public override void DoSettingsWindowContents(Rect inRect)
		{
			if (MP.IsInMultiplayer)
				return;

			//GUI.BeginGroup(inRect);
			//Rect outRect = new Rect(0f, 0f, inRect.width, inRect.height - 30f);
			//Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, inRect.height + 10f);
			//Widgets.BeginScrollView(outRect, ref scrollPosition, viewRect);

			RJWPregnancySettings.DoWindowContents(inRect);

			//Widgets.EndScrollView();
			//GUI.EndGroup();
		}
	}

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

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

		public override void DoSettingsWindowContents(Rect inRect)
		{
			if (MP.IsInMultiplayer)
				return;

			RJWPreferenceSettings.DoWindowContents(inRect);
		}
	}

	public class RJWHookupSettingsController : Mod
	{
		public RJWHookupSettingsController(ModContentPack content) : base(content)
		{
			GetSettings<RJWHookupSettings>();
		}

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

		public override void DoSettingsWindowContents(Rect inRect)
		{
			if (MP.IsInMultiplayer)
				return;
			RJWHookupSettings.DoWindowContents(inRect);
		}
	}

}

repo_name: Mewtopian/rjw

license: mit


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

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

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

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

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

		public static bool FapEverywhere = true;
		public static bool FapInBed = true;
		public static bool ShowForCP = true;
		public static bool ShowForBreeding = true;

		public static Clothing sex_wear = Clothing.Nude;
		public static RapeAlert rape_alert_sound = RapeAlert.Enabled;
		public static RapeAlert rape_alert = RapeAlert.Enabled;
		public static Rjw_sexuality sexuality_distribution = Rjw_sexuality.Vanilla;

		public static AllowedSex Malesex = AllowedSex.All;
		public static AllowedSex FeMalesex = AllowedSex.All;

		public static int MaxQuirks = 1;

		public enum AllowedSex
		{
			All,
			Homo,
			Nohomo
		};

		public enum Clothing
		{
			Nude,
			Headgear,
			Clothed
		};

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

		public enum Rjw_sexuality
		{
			Vanilla,
			RimJobWorld,
			Psychology,
			SYRIndividuality
		};

		public static void DoWindowContents(Rect inRect)
		{
			Listing_Standard listingStandard = new Listing_Standard();
			listingStandard.ColumnWidth = inRect.width / 3.15f;
			listingStandard.Begin(inRect);
			listingStandard.Gap(4f);
			listingStandard.Label("SexTypeFrequency".Translate());
			listingStandard.Gap(6f);
			listingStandard.Label("  " + "vaginal".Translate() + ": " + Math.Round(vaginal * 100, 0), -1, "vaginal_desc".Translate());
			vaginal = listingStandard.Slider(vaginal, 0.01f, 3.0f);
			listingStandard.Label("  " + "anal".Translate() + ": " + Math.Round(anal * 100, 0), -1, "anal_desc".Translate());
			anal = listingStandard.Slider(anal, 0.01f, 3.0f);
			listingStandard.Label("  " + "double_penetration".Translate() + ": " + Math.Round(double_penetration * 100, 0), -1, "double_penetration_desc".Translate());
			double_penetration = listingStandard.Slider(double_penetration, 0.01f, 3.0f);
			listingStandard.Label("  " + "fellatio".Translate() + ": " + Math.Round(fellatio * 100, 0), -1, "fellatio_desc".Translate());
			fellatio = listingStandard.Slider(fellatio, 0.01f, 3.0f);
			listingStandard.Label("  " + "cunnilingus".Translate() + ": " + Math.Round(cunnilingus * 100, 0), -1, "cunnilingus_desc".Translate());
			cunnilingus = listingStandard.Slider(cunnilingus, 0.01f, 3.0f);
			listingStandard.Label("  " + "rimming".Translate() + ": " + Math.Round(rimming * 100, 0), -1, "rimming_desc".Translate());
			rimming = listingStandard.Slider(rimming, 0.01f, 3.0f);
			listingStandard.Label("  " + "sixtynine".Translate() + ": " + Math.Round(sixtynine * 100, 0), -1, "sixtynine_desc".Translate());
			sixtynine = listingStandard.Slider(sixtynine, 0.01f, 3.0f);
			listingStandard.CheckboxLabeled("FapEverywhere".Translate(), ref FapEverywhere, "FapEverywhere_desc".Translate());
			listingStandard.CheckboxLabeled("FapInBed".Translate(), ref FapInBed, "FapInBed_desc".Translate());

			listingStandard.NewColumn();
			listingStandard.Gap(4f);
			if (listingStandard.ButtonText("Reset".Translate()))
			{
				vaginal = 1.20f;
				anal = 0.80f;
				fellatio = 0.80f;
				cunnilingus = 0.80f;
				rimming = 0.40f;
				double_penetration = 0.60f;
				breastjob = 0.50f;
				handjob = 0.80f;
				mutual_masturbation = 0.70f;
				fingering = 0.50f;
				footjob = 0.30f;
				scissoring = 0.50f;
				fisting = 0.30f;
				sixtynine = 0.69f;
			}
			listingStandard.Gap(6f);
			listingStandard.Label("  " + "breastjob".Translate() + ": " + Math.Round(breastjob * 100, 0), -1, "breastjob_desc".Translate());
			breastjob = listingStandard.Slider(breastjob, 0.01f, 3.0f);
			listingStandard.Label("  " + "handjob".Translate() + ": " + Math.Round(handjob * 100, 0), -1, "handjob_desc".Translate());
			handjob = listingStandard.Slider(handjob, 0.01f, 3.0f);
			listingStandard.Label("  " + "fingering".Translate() + ": " + Math.Round(fingering * 100, 0), -1, "fingering_desc".Translate());
			fingering = listingStandard.Slider(fingering, 0.01f, 3.0f);
			listingStandard.Label("  " + "fisting".Translate() + ": " + Math.Round(fisting * 100, 0), -1, "fisting_desc".Translate());
			fisting = listingStandard.Slider(fisting, 0.01f, 3.0f);
			listingStandard.Label("  " + "mutual_masturbation".Translate() + ": " + Math.Round(mutual_masturbation * 100, 0), -1, "mutual_masturbation_desc".Translate());
			mutual_masturbation = listingStandard.Slider(mutual_masturbation, 0.01f, 3.0f);
			listingStandard.Label("  " + "footjob".Translate() + ": " + Math.Round(footjob * 100, 0), -1, "footjob_desc".Translate());
			footjob = listingStandard.Slider(footjob, 0.01f, 3.0f);
			listingStandard.Label("  " + "scissoring".Translate() + ": " + Math.Round(scissoring * 100, 0), -1, "scissoring_desc".Translate());
			scissoring = listingStandard.Slider(scissoring, 0.01f, 3.0f);

			if (listingStandard.ButtonText("Malesex".Translate() + Malesex.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("AllowedSex.All".Translate(), (() => Malesex = AllowedSex.All), MenuOptionPriority.Default),
				  new FloatMenuOption("AllowedSex.Homo".Translate(), (() => Malesex = AllowedSex.Homo), MenuOptionPriority.Default),
				  new FloatMenuOption("AllowedSex.Nohomo".Translate(), (() => Malesex = AllowedSex.Nohomo), MenuOptionPriority.Default)
				}));
			}
			if (listingStandard.ButtonText("FeMalesex".Translate() + FeMalesex.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("AllowedSex.All".Translate(), (() => FeMalesex = AllowedSex.All), MenuOptionPriority.Default),
				  new FloatMenuOption("AllowedSex.Homo".Translate(), (() => FeMalesex = AllowedSex.Homo), MenuOptionPriority.Default),
				  new FloatMenuOption("AllowedSex.Nohomo".Translate(), (() => FeMalesex = AllowedSex.Nohomo), MenuOptionPriority.Default)
				}));
			}

			listingStandard.NewColumn();
			listingStandard.Gap(4f);
			// TODO: Add translation
			if (listingStandard.ButtonText("SexClothing".Translate() + sex_wear.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("SexClothingNude".Translate(), (() => sex_wear = Clothing.Nude), MenuOptionPriority.Default),
				  new FloatMenuOption("SexClothingHeadwear".Translate(), (() => sex_wear = Clothing.Headgear), MenuOptionPriority.Default),
				  new FloatMenuOption("SexClothingFull".Translate(), (() => sex_wear = Clothing.Clothed), MenuOptionPriority.Default)
				}));
			}
			listingStandard.Gap(4f);
			if (listingStandard.ButtonText("RapeSoundAlert".Translate() + rape_alert_sound.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("RapeSoundAlertAlways".Translate(), (() => rape_alert_sound = RapeAlert.Enabled), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertHumanlike".Translate(), (() => rape_alert_sound = RapeAlert.Humanlikes), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertColonist".Translate(), (() => rape_alert_sound = RapeAlert.Colonists), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeSoundAlertDisabled".Translate(), (() => rape_alert_sound = RapeAlert.Disabled), MenuOptionPriority.Default)
				}));
			}
			if (listingStandard.ButtonText("RapeAlert".Translate() + rape_alert.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
				  new FloatMenuOption("RapeAlertAlways".Translate(), (() => rape_alert = RapeAlert.Enabled), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeAlertHumanlike".Translate(), (() => rape_alert = RapeAlert.Humanlikes), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeAlertColonist".Translate(), (() => rape_alert = RapeAlert.Colonists), MenuOptionPriority.Default),
				  new FloatMenuOption("RapeAlertDisabled".Translate(), (() => rape_alert = RapeAlert.Disabled), MenuOptionPriority.Default)
				}));
			}
			listingStandard.CheckboxLabeled("RapeAlertCP".Translate(), ref ShowForCP, "RapeAlertCP_desc".Translate());
			listingStandard.CheckboxLabeled("RapeAlertBreeding".Translate(), ref ShowForBreeding, "RapeAlertBreeding_desc".Translate());

			listingStandard.Gap(26f);
			listingStandard.Label("SexualitySpread1".Translate(), -1, "SexualitySpread2".Translate());
			if (listingStandard.ButtonText(sexuality_distribution.ToString()))
			{
				Find.WindowStack.Add(new FloatMenu(new List<FloatMenuOption>()
				{
					new FloatMenuOption("Vanilla", () => sexuality_distribution = Rjw_sexuality.Vanilla, MenuOptionPriority.Default),
					//new FloatMenuOption("RimJobWorld", () => sexuality_distribution = Rjw_sexuality.RimJobWorld),
					new FloatMenuOption("SYRIndividuality", () => sexuality_distribution = Rjw_sexuality.SYRIndividuality),
					new FloatMenuOption("Psychology", () => sexuality_distribution = Rjw_sexuality.Psychology)
				}));
			}
			if (sexuality_distribution == Rjw_sexuality.RimJobWorld)
			{
				listingStandard.Label("SexualityAsexual".Translate() + Math.Round((asexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityAsexual_desc".Translate());
				asexual_ratio = listingStandard.Slider(asexual_ratio, 0.0f, 1.0f);
				listingStandard.Label("SexualityPansexual".Translate() + Math.Round((pansexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityPansexual_desc".Translate());
				pansexual_ratio = listingStandard.Slider(pansexual_ratio, 0.0f, 1.0f);
				listingStandard.Label("SexualityHeterosexual".Translate() + Math.Round((heterosexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityHeterosexual_desc".Translate());
				heterosexual_ratio = listingStandard.Slider(heterosexual_ratio, 0.0f, 1.0f);
				listingStandard.Label("SexualityBisexual".Translate() + Math.Round((bisexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityBisexual_desc".Translate());
				bisexual_ratio = listingStandard.Slider(bisexual_ratio, 0.0f, 1.0f);
				listingStandard.Label("SexualityGay".Translate() + Math.Round((homosexual_ratio / GetTotal()) * 100, 1) + "%", -1, "SexualityGay_desc".Translate());
				homosexual_ratio = listingStandard.Slider(homosexual_ratio, 0.0f, 1.0f);
			}
			else
			{

				if (!xxx.IndividualityIsActive && sexuality_distribution == Rjw_sexuality.SYRIndividuality)
					sexuality_distribution = Rjw_sexuality.Vanilla;
				else if (sexuality_distribution == Rjw_sexuality.SYRIndividuality)
					listingStandard.Label("SexualitySpreadIndividuality".Translate());

				if (!xxx.PsychologyIsActive && sexuality_distribution == Rjw_sexuality.Psychology)
					sexuality_distribution = Rjw_sexuality.Vanilla;
				else if (sexuality_distribution == Rjw_sexuality.Psychology)
					listingStandard.Label("SexualitySpreadPsychology".Translate());

				else
					listingStandard.Label("SexualitySpreadVanilla".Translate());
			}
			listingStandard.Label("MaxQuirks".Translate() + ": " + MaxQuirks, -1f, "MaxQuirks_desc".Translate());
			MaxQuirks = (int)listingStandard.Slider(MaxQuirks, 0, 10);

			listingStandard.End();
		}

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

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


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

namespace rjw.Properties
{

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

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

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

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

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

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

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

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

			offset += labelWidth;
			return change;
		}
	}
}


code:
using Verse;

namespace rjw
{
	public class config : Def
	{
		// TODO: Clean these.
		// Commented out old configs that are no longer in use (or have been converted into settings).

		// Feature Toggles
		//public bool comfort_prisoners_enabled;
		//public bool colonists_can_be_comfort_prisoners;
		//public bool cum_enabled;
		//public bool rape_me_sticky_enabled;
		//public bool sounds_enabled;
		//public bool stds_enabled;
		public bool bondage_gear_enabled;
		//public bool nymph_joiners_enabled;
		//public bool whore_beds_enabled;
		//public bool necro_enabled;
		//public bool random_rape_enabled;
		//public bool always_accept_whores;
		//public bool nymphs_always_JoinInBed;
		//public bool zoophis_always_rape;
		//public bool rapists_always_rape;
		//public bool pawns_always_do_fapping;
		//public bool pawns_always_rapeCP;
		//public bool whores_always_findjob;

		// STD config
		public bool std_show_roll_to_catch;

		public float std_min_severity_to_pitch;
		public float std_env_pitch_cleanliness_exaggeration;
		public float std_env_pitch_dirtiness_exaggeration;
		public float std_outdoor_cleanliness;

		// Age Config
		//public int sex_free_for_all_age;
		//public int sex_minimum_age;

		public float minor_pain_threshold;	        // 0.3
		public float significant_pain_threshold;	// 0.6
		public float extreme_pain_threshold;		// 0.95
		public float base_chance_to_hit_prisoner;	// 50
		public int min_ticks_between_hits;			// 500
		public int max_ticks_between_hits;			// 700

		public float max_nymph_fraction;
		public float opp_inf_initial_immunity;
		public float comfort_prisoner_rape_mtbh_mul;
		//public float whore_mtbh_mul;
		public float nymph_spawn_with_std_mul;
		//public float chance_to_rim;

		//public float fertility_endAge_male;
		//public float fertility_endAge_female_humanlike;
		//public float fertility_endAge_female_animal;
	}
}


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

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

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

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

			float personality_factor;
			{
				personality_factor = 1.0f;

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

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

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

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

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

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

			return base_mtb * desire_factor * personality_factor * fun_factor;
		}

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


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

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

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


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

namespace rjw
{
	public class ThinkNode_ChancePerHour_Fappin : ThinkNode_ChancePerHour
	{
		public static float get_fappin_mtb_hours(Pawn p)
		{
			return (xxx.is_nympho(p) ? 0.5f : 1.0f) * rjw_CORE_EXPOSED.LovePartnerRelationUtility.LovinMtbSinglePawnFactor(p);
		}

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

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

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

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

			return -1.0f;
		}
	}
}


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

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

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

			float personality_factor;
			{
				personality_factor = 1.0f;

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

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

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

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

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

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

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

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


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

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

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

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

					personality_factor = 1.0f;

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

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

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

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

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

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

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

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

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


code:
using Verse;
using Verse.AI;

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

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

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

			// No free will while designated for rape.
			if (!RJWSettings.designated_freewill)
				if ((p.IsDesignatedComfort() || p.IsDesignatedBreeding()))
					return false;

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

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

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

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

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

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


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

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

			if (!RJWSettings.rape_enabled)
				return false;

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

			// Designated pawns are not allowed to rape other CP.
			if (!RJWSettings.designated_freewill)
				if ((p.IsDesignatedComfort() || p.IsDesignatedBreeding()))
					return false;

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

			// colonists(humans) cannot rape CP if the setting is disabled.
			if (!RJWSettings.colonist_CP_rape && p.IsColonist && xxx.is_human(p))
				return false;

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

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

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

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

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


code:
using Verse;
using Verse.AI;

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


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the animal can mate(vanilla reproductory sex) with animals.
	/// </summary>
	public class ThinkNode_ConditionalMate : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalMate " + xxx.get_pawnname(p));
			return (xxx.is_animal(p) && RJWSettings.animal_on_animal_enabled);
		}
	}
}


code:
using Verse;
using Verse.AI;

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

			if (!RJWSettings.necrophilia_enabled)
				return false;

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

			// No free will while designated for rape.
			if (!RJWSettings.designated_freewill)
				if ((p.IsDesignatedComfort() || p.IsDesignatedBreeding()))
					return false;

			return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Nymph nothing to do, seek sex-> join in bed
	/// </summary>
	public class ThinkNode_ConditionalNympho : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalNympho " + p);

			if (xxx.is_nympho(p))
				if (p.Faction == null || !p.Faction.IsPlayer)
					return false;
				else
					return true;

			return false;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Rapist, chance to trigger random rape
	/// </summary>
	public class ThinkNode_ConditionalRapist : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			if (!RJWSettings.rape_enabled)
				return false;

			if (xxx.is_animal(p))
				return false;

			if (!xxx.is_rapist(p))
				return false;

			// No free will while designated for rape.
			if (!RJWSettings.designated_freewill)
				if ((p.IsDesignatedComfort() || p.IsDesignatedBreeding()))
					return false;

			if (!xxx.isSingleOrPartnerNotHere(p))
			{
				return false;
			}
			else
				return true;
		}
	}
}


code:
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Called to determine if the pawn can engage in sex.
	/// This should be used as the first conditional for sex-related thinktrees.
	/// </summary>
	public class ThinkNode_ConditionalSexChecks : ThinkNode_Conditional
	{
		protected override bool Satisfied(Pawn p)
		{
			//Log.Message("[RJW]ThinkNode_ConditionalSexChecks " + xxx.get_pawnname(p));
			//if (p.Faction != null && p.Faction.IsPlayer)
			//	Log.Message("[RJW]ThinkNode_ConditionalSexChecks " + xxx.get_pawnname(p) + " is animal: " + xxx.is_animal(p));

			// Downed, Drafted and Awake are checked in core ThinkNode_ConditionalCanDoConstantThinkTreeJobNow.
			if (p.Map == null)
				return false;

			// Setting checks.
			if (xxx.is_human(p) && p.ageTracker.AgeBiologicalYears < RJWSettings.sex_minimum_age)
				return false;
			else if (xxx.is_animal(p) && !RJWSettings.bestiality_enabled && !RJWSettings.animal_on_animal_enabled)
				return false;

			// No sex while starving or badly hurt.
			return ((!p.needs?.food?.Starving) ?? true && xxx.is_healthy_enough(p));
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_FeelingBroken : ThoughtWorker
	{
		public static int Clamp(int value, int min, int max)
		{
			return (value < min) ? min : (value > max) ? max : value;
		}

		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			var brokenstages = p.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken);
			if (brokenstages != null && brokenstages.CurStageIndex != 0)
			{
				if (xxx.is_masochist(p) && brokenstages.CurStageIndex >= 2)
				{
					return ThoughtState.ActiveAtStage(2); // begging for more
				}
				return ThoughtState.ActiveAtStage(Clamp(brokenstages.CurStageIndex - 1, 0, 1));
			}
			return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_NeedSex : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			var sex_need = p.needs.TryGetNeed<Need_Sex>();
			var p_age = p.ageTracker.AgeBiologicalYears;
			if (sex_need != null && (p_age > RJWSettings.sex_minimum_age || (!xxx.is_human(p) && p.ageTracker.CurLifeStage.reproductive)))
			{
				var lev = sex_need.CurLevel;
				if (lev <= sex_need.thresh_frustrated())
					return ThoughtState.ActiveAtStage(0);
				else if (lev <= sex_need.thresh_horny())
					return ThoughtState.ActiveAtStage(1);
				else if (lev >= sex_need.thresh_ahegao())
					return ThoughtState.ActiveAtStage(3);
				else if (lev >= sex_need.thresh_satisfied())
					return ThoughtState.ActiveAtStage(2);
				else
					return ThoughtState.Inactive;
			}
			else
				return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	//This thought system of RW is retarded AF. It needs separate thought handler for each hediff.
	public abstract class ThoughtWorker_SexChange : ThoughtWorker
	{
		public virtual HediffDef hediff_served { get; }
		protected override ThoughtState CurrentStateInternal(Pawn pawn)
		{
			//Log.Message(" "+this.GetType() + " is called for " + pawn +" and hediff" + hediff_served);
			Hediff denial = pawn.health.hediffSet.GetFirstHediffOfDef(hediff_served);
			//Log.Message("Hediff of the class is null " + (hediff_served == null));
			if (denial != null && denial.CurStageIndex!=0)
			{
				//Log.Message("Current denial level is  " + denial.CurStageIndex );
				return ThoughtState.ActiveAtStage(denial.CurStageIndex-1);
			}
			return ThoughtState.Inactive;
		}
	}
	public class ThoughtWorker_MtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2t; } }
	}
	public class ThoughtWorker_MtF:ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2f; }  }
	}
	public class ThoughtWorker_MtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2h; } }
	}
	public class ThoughtWorker_FtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2t; } }
	}
	public class ThoughtWorker_FtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2m; } }
	}
	public class ThoughtWorker_FtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2h; } }
	}
	public class ThoughtWorker_HtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2t; } }
	}
	public class ThoughtWorker_HtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2m; } }
	}
	public class ThoughtWorker_HtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2f; } }
	}
	public class ThoughtWorker_TtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2h; } }
	}
	public class ThoughtWorker_TtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2m; } }
	}
	public class ThoughtWorker_TtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2f; } }
	}
}


code:
using Verse;
using Verse.AI.Group;

namespace rjw
{
	public class Trigger_SexSatisfy : TriggerFilter
	{
		private const int CheckInterval = 120;
		private const int TickTimeout = 900;
		private int currentTick = 0;
		public float targetValue = 0.3f;

		public Trigger_SexSatisfy(float t)
		{
			this.targetValue = t;
			currentTick = 0;
		}

		public override bool AllowActivation(Lord lord, TriggerSignal signal)
		{
			currentTick++;
			if (signal.type == TriggerSignalType.Tick && Find.TickManager.TicksGame % CheckInterval == 0)
			{
				float? avgValue = null;
				foreach (var pawn in lord.ownedPawns)
				{
					/*foreach(Pawn p in lord.Map.mapPawns.PawnsInFaction(Faction.OfPlayer))
					{
					}*/
					Need_Sex n = pawn.needs.TryGetNeed<Need_Sex>();
					//if (n != null && pawn.gender == Gender.Male && !pawn.Downed)
					if(xxx.can_rape(pawn) && xxx.is_healthy_enough(pawn) && xxx.IsTargetPawnOkay(pawn) && Find.TickManager.TicksGame > pawn.mindState.canLovinTick)
					{
						avgValue = (avgValue == null) ? n.CurLevel : (avgValue + n.CurLevel) / 2f;
					}
				}
				//--Log.Message("[ABF]Trigger_SexSatisfy::ActivateOn Checked value :" + avgValue + "/" + targetValue);
				return avgValue == null || avgValue >= targetValue;
			}
			return currentTick >= TickTimeout;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityF : WorkGiver_RJW_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(passive)
	/// </summary>
	public class WorkGiver_BestialityForFemale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + ":: base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn cant be fucked");
				return false;
			}

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (xxx.need_some_sex(pawn) < 2f)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//Log.Message("[RJW]WorkGiver_BestialityForFemale::" + SexAppraiser.would_fuck_animal(pawn, target));
					if (SexAppraiser.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			Building_Bed bed = pawn.ownership.OwnedBed;
			if (bed == null)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn has no bed");
				return false;
			}
			if (!target.CanReach(bed, PathEndMode.OnCell, Danger.Some) || target.Downed)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant reach bed");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			Building_Bed bed = pawn.ownership.OwnedBed;
			return JobMaker.MakeJob(xxx.bestialityForFemale, t, bed, bed.SleepPosOfAssignedPawn(pawn));
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(active)
	/// </summary>
	public class WorkGiver_BestialityForMale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant be fucked");
				return false;
			}

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (xxx.need_some_sex(pawn) < 2f)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//Log.Message("[RJW]WorkGiver_BestialityForMale::" + SexAppraiser.would_fuck_animal(pawn, target));
					if (SexAppraiser.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			if (!xxx.can_fuck(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("cant fuck");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.bestiality, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityM : WorkGiver_RJW_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to cleanup/collect sex fluids
	/// </summary>
	//TODO: add sex fluid collection/cleaning
	public class WorkGiver_CleanSexStuff : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.filth);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap in bed
	/// </summary>
	public class WorkGiver_Masturbate_Bed : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (pawn.Position == t.Position)
			{
				//use quickfap
				return false;
			}
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			Building_Bed target = t as Building_Bed;
			if (!(target is Building_Bed))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}
			if (!pawn.CanReserve(target))
			{
				//if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!target.OwnersForReading.Contains(pawn) && !xxx.is_psychopath(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not my bed");
						return false;
					}
					if (!xxx.is_nympho(pawn))
						if ((xxx.need_some_sex(pawn) < 2f))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (target.CurOccupants.Count() != 0)
					{
						if (target.CurOccupants.Count() == 1 && !target.CurOccupants.Contains(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
						if (target.CurOccupants.Count() > 1)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
					}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (bystander.CanSee(target) && bystander.Position.DistanceToSquared(target.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.Masturbate_Bed, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap
	/// </summary>
	public class WorkGiver_Masturbate_Quick : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			Pawn target = t as Pawn;
			if (target != pawn)
			{
				return false;
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_nympho(pawn))
						if ((xxx.need_some_sex(pawn) < 2f))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (pawn.CanSee(bystander) && pawn.Position.DistanceToSquared(bystander.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}
			//experimental change textures of bed to whore bed
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " path " + t.Graphic.data.texPath);
			//t.Graphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.Graphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " texPath " + t.Graphic.data.texPath);
			//Log.Message("[RJW] bed " + t.GetType().ToString() + " drawSize " + t.Graphic.data.drawSize);
			//t.Draw();
			//t.ExposeData();
			//Scribe_Values.Look(ref t.Graphic.data.texPath, t.Graphic.data.texPath, "Things/Building/Furniture/Bed/DoubleBedWhore", true);

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.Masturbate_Quick, t.Position);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape
	/// </summary>
	public class WorkGiver_Rape : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self rape", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_kind(pawn) || xxx.is_masochist(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape");
						return false;
					}
					if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape a friend");
						return false;
					}
					if (!xxx.can_get_raped(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("cannot rape target");
						return false;
					}
					//fail for:
					//satisfied pawns
					//horny non rapists
					if ((xxx.need_some_sex(pawn) <= 1f)
						|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.can_rape(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("cannot rape");
						return false;
					}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot rape while partner around");
								return false;
							}
					//Log.Message("[RJW]WorkGiver_RapeCP::" + SexAppraiser.would_fuck(pawn, target));
					if (SexAppraiser.would_fuck(pawn, target) < 0.1f)
					{
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}
		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeRandom, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_RapeCP : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!target.IsDesignatedComfort())
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not designated for comfort", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeCP, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape enemy
	/// </summary>
	public class WorkGiver_RapeEnemy : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!pawn.HostileTo(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not hostile", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeEnemy, t);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to have sex with
	/// </summary>
	public class WorkGiver_Sex : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self sex", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					//check initiator
					//fail for:
					//satisfied non nymph pawns
					if (xxx.need_some_sex(pawn) <= 1f && !xxx.is_nympho(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.IsTargetPawnOkay(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot have sex while partner around");
								return false;
							}

					float relations = 0;
					float attraction = 0;
					if (!xxx.is_animal(target))
					{
						relations = pawn.relations.OpinionOf(target);
						if (relations < RJWHookupSettings.MinimumRelationshipToHookup)
						{
							if (!(relations > 0 && xxx.is_nympho(pawn)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"i dont like them:({relations})");
								return false;
							}
						}

						attraction = pawn.relations.SecondaryRomanceChanceFactor(target);
						if (attraction < RJWHookupSettings.MinimumAttractivenessToHookup)
						{
							if (!(attraction > 0 && xxx.is_nympho(pawn)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"i dont find them attractive:({attraction})");
								return false;
							}
						}
					}
					//Log.Message("[RJW]WorkGiver_Sex::" + SexAppraiser.would_fuck(pawn, target));
					if (SexAppraiser.would_fuck(pawn, target) < 0.1f)
					{
						return false;
					}

					if (!xxx.is_animal(target))
					{
						//check partner
						if (xxx.need_some_sex(target) <= 1f && !xxx.is_nympho(target))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("partner not horny enough");
							return false;
						}
						if (!xxx.is_lecher(target) && !xxx.is_psychopath(target) && !xxx.is_nympho(target))
							if (!xxx.isSingleOrPartnerNotHere(target))
								if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
								{
									if (RJWSettings.DevMode) JobFailReason.Is("partner cannot have sex while their partner around");
									return false;
								}

						relations = target.relations.OpinionOf(pawn);
						if (relations <= RJWHookupSettings.MinimumRelationshipToHookup)
						{
							if (!(relations > 0 && xxx.is_nympho(target)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"dont like me:({relations})");
								return false;
							}
						}

						attraction = target.relations.SecondaryRomanceChanceFactor(pawn);
						if (attraction <= RJWHookupSettings.MinimumAttractivenessToHookup)
						{
							if (!(attraction > 0 && xxx.is_nympho(target)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"doesnt find me attractive:({attraction})");
								return false;
							}
						}
					}

					//Log.Message("[RJW]WorkGiver_Sex::" + SexAppraiser.would_fuck(target, pawn));
					if (SexAppraiser.would_fuck(target, pawn) < 0.1f)
					{
						return false;
					}
				}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//TODO:: fix bed stealing during join other pawn
			//Building_Bed bed = pawn.ownership.OwnedBed;
			//if (bed == null)
			//	bed = (t as Pawn).ownership.OwnedBed;

			Building_Bed bed = (t as Pawn).CurrentBed();

			if (bed == null)
				return null;

			//if (pawn.CurrentBed() != (t as Pawn).CurrentBed())
			//	return null;

			return JobMaker.MakeJob(xxx.casual_sex, t as Pawn, bed);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Allow pawn to have sex
	/// dunno if this should be used to allow manual sex start or limit it behind sort of "hero" designator for RP purposes, so player can only control 1 pawn directly?
	/// </summary>
	public class WorkGiver_Sexchecks : WorkGiver_Scanner
	{
		public override int MaxRegionsToScanBeforeGlobalSearch => 4;
		public override PathEndMode PathEndMode => PathEndMode.OnCell;
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Pawn);

		public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			if (!forced)
			//if (!(forced || RJWSettings.WildMode))
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::not player interaction, exit:" + forced);
				return false;
			}
			if (!(RJWSettings.RPG_direct_control || (RJWSettings.RPG_hero_control && pawn.IsDesignatedHero() && pawn.IsHeroOwner())))
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::direct_control disabled or not hero, exit");
				return false;
			}
			Pawn target = t as Pawn;
			if (t is Corpse)
			{
				Corpse corpse = t as Corpse;
				target = corpse.InnerPawn;
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target corpse(" + xxx.get_pawnname(target) + ")");
			}
			else
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target pawn(" + xxx.get_pawnname(target) + ")");
			}

			//Log.Message("1");
			if (t == null || t.Map == null)
			{
				return false;
			}
			//Log.Message("2");
			if (!(xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)))
			{
				//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(pawn) + ") is cannot fuck or be fucked.");
				return false;
			}
			//Log.Message("3");
			if (t is Pawn)
				if (!(xxx.can_fuck(target) || xxx.can_be_fucked(target)))
				{
					//Log.Message("[RJW]WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(target) + ") is cannot fuck or be fucked.");
					return false;
				}
			//Log.Message("4");

			//investigate AoA, someday
			//move this?
			//if (xxx.is_animal(pawn) && xxx.is_animal(target) && !RJWSettings.animal_on_animal_enabled)
			//{
			//	return false;
			//}
			if (!xxx.is_human(pawn) && !(xxx.RoMIsActive && pawn.health.hediffSet.HasHediff(HediffDef.Named("TM_ShapeshiftHD"))))
			{
				return false;
			}

			//Log.Message("5");
			if (!pawn.CanReach(t, PathEndMode, Danger.Some))
			{
				if (RJWSettings.DevMode) JobFailReason.Is(
					pawn.CanReach(t, PathEndMode, Danger.Deadly)
						? "unable to reach target safely" : "target unreachable");
				return false;
			}
			//Log.Message("6");
			if (t.IsForbidden(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target is outside of allowed area");
				return false;
			}
			//Log.Message("7");
			if (!pawn.IsDesignatedHero())
			{
				if (!RJWSettings.WildMode)
				{
					if (pawn.IsDesignatedComfort() || pawn.IsDesignatedBreeding())
					{
						if (RJWSettings.DevMode) JobFailReason.Is("designated pawns cannot initiate sex");
						return false;
					}
					if (!xxx.is_healthy_enough(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not healthy enough for sex");
						return false;
					}
					if (xxx.is_asexual(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to have sex");
						return false;
					}
				}

			}
			else
			{
				if (!pawn.IsHeroOwner())
				{
					//Log.Message("[RJW]WorkGiver_Sexchecks::player interaction for not owned hero, exit");
					return false;
				}
			}

			if (!MoreChecks(pawn, t, forced))
				return false;

			return true;
		}

		public virtual bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public virtual bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Try to solicit pawn to have sex with
	/// </summary>
	public class WorkGiver_Solicit : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self solicit", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			//if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
			//	return false;

			//Log.Message("[RJW]WorkGiver_Sex::" + SexAppraiser.would_fuck(target, pawn));
			//if (SexAppraiser.would_fuck(target, pawn) < 0.1f)
			//{
			//	return false;
			//}

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//TODO:: fix bed stealing during join other pawn
			//Building_Bed bed = pawn.ownership.OwnedBed;
			//if (bed == null)
			//	bed = (t as Pawn).ownership.OwnedBed;

			Building_Bed bed = (pawn as Pawn).ownership.OwnedBed;

			if (bed == null)
				return null;

			//if (pawn.CurrentBed() != (t as Pawn).CurrentBed())
			//	return null;

			return JobMaker.MakeJob(xxx.whore_inviting_visitors, t as Pawn, bed);
		}
	}
}


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

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a corpse
	/// </summary>
	public class WorkGiver_ViolateCorpse : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Corpse);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//Log.Message("[RJW]" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.necrophilia_enabled) return false;

			//Pawn target = (t as Corpse).InnerPawn;
			if (!pawn.CanReserve(t, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!pawn.IsDesignatedHero())
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_necrophiliac(pawn) && xxx.need_some_sex(pawn) < 2f)
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.is_necrophiliac(pawn))
						if ((t as Corpse).CurRotDrawMode != RotDrawMode.Fresh)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("refuse to rape rotten");
							return false;
						}
						else if (xxx.need_some_sex(pawn) < 3f)
							{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
								return false;
							}
					//Log.Message("[RJW]WorkGiver_ViolateCorpse::" + SexAppraiser.would_fuck(pawn, t as Corpse));
					if (SexAppraiser.would_fuck(pawn, t as Corpse) > 0.1f)
					{
						return false;
					}
				}
			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeCorpse, t as Corpse);
		}
	}
}