code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Harmony;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using Verse.Sound;
using Multiplayer.API;
//using static RimWorld.Planet.CaravanInventoryUtility;
//using RimWorldChildren;

namespace rjw
{
	public static class xxx
	{
		public static readonly BindingFlags ins_public_or_no = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		public static readonly config config = DefDatabase<config>.GetNamed("the_one");

		//HARDCODED MAGIC USED ACROSS DOZENS OF FILES, this is as bad place to put it as any other
		//Should at the very least be encompassed in the related designation type
		public static readonly int max_rapists_per_prisoner = 6;

		public static readonly TraitDef nymphomaniac = TraitDef.Named("Nymphomaniac");
		public static readonly TraitDef rapist = TraitDef.Named("Rapist");
		public static readonly TraitDef masochist = TraitDef.Named("Masochist");
		public static readonly TraitDef necrophiliac = TraitDef.Named("Necrophiliac");
		public static readonly TraitDef zoophile = TraitDef.Named("Zoophile");

		//CombatExtended Traits
		public static HediffDef MuscleSpasms;
		public static bool CombatExtendedIsActive;

		//RomanceDiversified Traits
		public static TraitDef straight;
		public static TraitDef bisexual;
		public static TraitDef asexual;
		public static TraitDef faithful;
		public static TraitDef philanderer;
		public static TraitDef polyamorous;
		public static bool RomanceDiversifiedIsActive; //A dirty way to check if the mod is active

		//Psychology Traits
		public static TraitDef prude;
		public static TraitDef lecher;
		public static TraitDef polygamous;
		public static bool PsychologyIsActive;

		//[SYR] Individuality
		public static bool IndividualityIsActive;

		//Rimworld of Magic
		public static bool RoMIsActive;

		//Consolidated Traits
		public static bool CTIsActive;

		//SimpleSlavery
		public static bool SimpleSlaveryIsActive;

		//Dubs Bad Hygiene
		public static bool DubsBadHygieneIsActive;

		//Alien Framework Traits
		public static TraitDef xenophobia; // Degrees: 1: xenophobe, -1: xenophile
		public static bool AlienFrameworkIsActive;

		//Children&Pregnancy Hediffs
		public static HediffDef babystate;
		public static bool RimWorldChildrenIsActive; //A dirty way to check if the mod is active

		//The Hediff to prevent reproduction
		public static readonly HediffDef sterilized = HediffDef.Named("Sterilized");

		//The Hediff for broken body(resulted from being raped as CP for too many times)
		public static readonly HediffDef feelingBroken = HediffDef.Named("FeelingBroken");

		public static PawnCapacityDef reproduction = DefDatabase<PawnCapacityDef>.GetNamed("Reproduction");

		public static readonly BodyPartDef genitalsDef = DefDatabase<BodyPartDef>.GetNamed("Genitals");
		public static readonly BodyPartDef breastsDef = DefDatabase<BodyPartDef>.GetNamed("Chest");
		public static readonly BodyPartDef anusDef = DefDatabase<BodyPartDef>.GetNamed("Anus");

		public static readonly ThoughtDef saw_rash_1 = DefDatabase<ThoughtDef>.GetNamed("SawDiseasedPrivates1");
		public static readonly ThoughtDef saw_rash_2 = DefDatabase<ThoughtDef>.GetNamed("SawDiseasedPrivates2");
		public static readonly ThoughtDef saw_rash_3 = DefDatabase<ThoughtDef>.GetNamed("SawDiseasedPrivates3");

		public static readonly ThoughtDef got_raped = DefDatabase<ThoughtDef>.GetNamed("GotRaped");
		public static readonly ThoughtDef got_raped_unconscious = DefDatabase<ThoughtDef>.GetNamed("GotRapedUnconscious");
		public static readonly ThoughtDef got_bred = DefDatabase<ThoughtDef>.GetNamed("GotBredByAnimal");
		public static readonly ThoughtDef got_licked = DefDatabase<ThoughtDef>.GetNamed("GotLickedByAnimal");
		public static readonly ThoughtDef got_groped = DefDatabase<ThoughtDef>.GetNamed("GotGropedByAnimal");

		public static readonly ThoughtDef masochist_got_raped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotRaped");
		public static readonly ThoughtDef masochist_got_bred = DefDatabase<ThoughtDef>.GetNamed("MasochistGotBredByAnimal");
		public static readonly ThoughtDef masochist_got_licked = DefDatabase<ThoughtDef>.GetNamed("MasochistGotLickedByAnimal");
		public static readonly ThoughtDef masochist_got_groped = DefDatabase<ThoughtDef>.GetNamed("MasochistGotGropedByAnimal");
		public static readonly ThoughtDef allowed_animal_to_breed = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToBreed");
		public static readonly ThoughtDef allowed_animal_to_lick = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToLick");
		public static readonly ThoughtDef allowed_animal_to_grope = DefDatabase<ThoughtDef>.GetNamed("AllowedAnimalToGrope");
		public static readonly ThoughtDef zoophile_got_bred = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotBredByAnimal");
		public static readonly ThoughtDef zoophile_got_licked = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotLickedByAnimal");
		public static readonly ThoughtDef zoophile_got_groped = DefDatabase<ThoughtDef>.GetNamed("ZoophileGotGropedByAnimal");
		public static readonly ThoughtDef hate_my_rapist = DefDatabase<ThoughtDef>.GetNamed("HateMyRapist");
		public static readonly ThoughtDef kinda_like_my_rapist = DefDatabase<ThoughtDef>.GetNamed("KindaLikeMyRapist");
		public static readonly ThoughtDef allowed_me_to_get_raped = DefDatabase<ThoughtDef>.GetNamed("AllowedMeToGetRaped");
		public static readonly ThoughtDef stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("StoleSomeLovin");
		public static readonly ThoughtDef bloodlust_stole_some_lovin = DefDatabase<ThoughtDef>.GetNamed("BloodlustStoleSomeLovin");
		public static readonly ThoughtDef violated_corpse = DefDatabase<ThoughtDef>.GetNamed("ViolatedCorpse");
		public static readonly ThoughtDef gave_virginity = DefDatabase<ThoughtDef>.GetNamed("FortunateGaveVirginity");
		public static readonly ThoughtDef lost_virginity = DefDatabase<ThoughtDef>.GetNamed("UnfortunateLostVirginity");
		public static readonly ThoughtDef VanillaGotSomeLovin = DefDatabase<ThoughtDef>.GetNamed("GotSomeLovin");
		public static readonly ThoughtDef took_virginity = DefDatabase<ThoughtDef>.GetNamed("TookVirginity");

		public static readonly JobDef fappin = DefDatabase<JobDef>.GetNamed("Fappin");
		public static readonly JobDef quickfap = DefDatabase<JobDef>.GetNamed("QuickFap");
		public static readonly JobDef gettin_loved = DefDatabase<JobDef>.GetNamed("GettinLoved");
		public static readonly JobDef casual_sex = DefDatabase<JobDef>.GetNamed("JoinInBed");
		public static readonly JobDef gettin_raped = DefDatabase<JobDef>.GetNamed("GettinRaped");
		public static readonly JobDef gettin_bred = DefDatabase<JobDef>.GetNamed("GettinBred");
		public static readonly JobDef comfort_prisoner_rapin = DefDatabase<JobDef>.GetNamed("ComfortPrisonerRapin");
		public static readonly JobDef RapeEnemy = DefDatabase<JobDef>.GetNamed("RapeEnemy");
		public static readonly JobDef violate_corpse = DefDatabase<JobDef>.GetNamed("ViolateCorpse");
		public static readonly JobDef bestiality = DefDatabase<JobDef>.GetNamed("Bestiality");
		public static readonly JobDef bestialityForFemale = DefDatabase<JobDef>.GetNamed("BestialityForFemale");
		public static readonly JobDef random_rape = DefDatabase<JobDef>.GetNamed("RandomRape");
		public static readonly JobDef whore_inviting_visitors = DefDatabase<JobDef>.GetNamed("WhoreInvitingVisitors");
		public static readonly JobDef whore_is_serving_visitors = DefDatabase<JobDef>.GetNamed("WhoreIsServingVisitors");
		public static readonly JobDef struggle_in_BondageGear = DefDatabase<JobDef>.GetNamed("StruggleInBondageGear");
		public static readonly JobDef unlock_BondageGear = DefDatabase<JobDef>.GetNamed("UnlockBondageGear");
		public static readonly JobDef give_BondageGear = DefDatabase<JobDef>.GetNamed("GiveBondageGear");
		public static readonly JobDef animalBreed = DefDatabase<JobDef>.GetNamed("Breed");

		public static readonly ThingDef mote_noheart = ThingDef.Named("Mote_NoHeart");

		public static readonly StatDef sex_stat = StatDef.Named("SexAbility");
		public static readonly StatDef vulnerability_stat = StatDef.Named("Vulnerability");
		public static readonly StatDef sex_drive_stat = StatDef.Named("SexFrequency");

		public static readonly RecordDef GetRapedAsComfortPrisoner = DefDatabase<RecordDef>.GetNamed("GetRapedAsComfortPrisoner");
		public static readonly RecordDef CountOfFappin = DefDatabase<RecordDef>.GetNamed("CountOfFappin");
		public static readonly RecordDef CountOfWhore = DefDatabase<RecordDef>.GetNamed("CountOfWhore");
		public static readonly RecordDef EarnedMoneyByWhore = DefDatabase<RecordDef>.GetNamed("EarnedMoneyByWhore");
		public static readonly RecordDef CountOfSex = DefDatabase<RecordDef>.GetNamed("CountOfSex");
		public static readonly RecordDef CountOfSexWithHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfSexWithHumanlikes");
		public static readonly RecordDef CountOfSexWithAnimals = DefDatabase<RecordDef>.GetNamed("CountOfSexWithAnimals");
		public static readonly RecordDef CountOfSexWithInsects = DefDatabase<RecordDef>.GetNamed("CountOfSexWithInsects");
		public static readonly RecordDef CountOfSexWithOthers = DefDatabase<RecordDef>.GetNamed("CountOfSexWithOthers");
		public static readonly RecordDef CountOfSexWithCorpse = DefDatabase<RecordDef>.GetNamed("CountOfSexWithCorpse");
		public static readonly RecordDef CountOfRapedHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfRapedHumanlikes");
		public static readonly RecordDef CountOfBeenRapedByHumanlikes = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByHumanlikes");
		public static readonly RecordDef CountOfRapedAnimals = DefDatabase<RecordDef>.GetNamed("CountOfRapedAnimals");
		public static readonly RecordDef CountOfBeenRapedByAnimals = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByAnimals");
		public static readonly RecordDef CountOfRapedInsects = DefDatabase<RecordDef>.GetNamed("CountOfRapedInsects");
		public static readonly RecordDef CountOfBeenRapedByInsects = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByInsects");
		public static readonly RecordDef CountOfRapedOthers = DefDatabase<RecordDef>.GetNamed("CountOfRapedOthers");
		public static readonly RecordDef CountOfBeenRapedByOthers = DefDatabase<RecordDef>.GetNamed("CountOfBeenRapedByOthers");
		public static readonly RecordDef CountOfBirthHuman = DefDatabase<RecordDef>.GetNamed("CountOfBirthHuman");
		public static readonly RecordDef CountOfBirthAnimal = DefDatabase<RecordDef>.GetNamed("CountOfBirthAnimal");
		public static readonly RecordDef CountOfBirthEgg = DefDatabase<RecordDef>.GetNamed("CountOfBirthEgg");

		public enum rjwSextype { None, Vaginal, Anal, Oral, Masturbation, DoublePenetration, Boobjob, Handjob, Footjob, Fingering, Scissoring, MutualMasturbation, Fisting, MechImplant }

		public static void bootstrap(Map m)
		{
			if (m.GetComponent<MapCom_Injector>() == null)
				m.components.Add(new MapCom_Injector(m));
		}

		//<Summary>Simple method that quickly checks for match from a list.</Summary>
		public static bool ContainsAny(this string haystack, params string[] needles) { return needles.Any(haystack.Contains); }

		public static bool has_traits(Pawn pawn)
		{
			return pawn?.story?.traits != null;
		}

		public static bool has_quirk(Pawn pawn, string quirk)
		{
			return pawn != null && is_human(pawn) && CompRJW.Comp(pawn).quirks.ToString().Contains(quirk);
		}

		[SyncMethod]
		public static string random_pick_a_trait(this Pawn pawn)
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			return has_traits(pawn) ? pawn.story.traits.allTraits.RandomElement().def.defName : null;
		}

		public static bool is_psychopath(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Psychopath);
		}

		public static bool is_ascetic(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Ascetic);
		}

		public static bool is_bloodlust(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Bloodlust);
		}

		public static bool is_brawler(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Brawler);
		}

		public static bool is_kind(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Kind);
		}

		public static bool is_rapist(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(rapist);
		}

		public static bool is_necrophiliac(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(necrophiliac);
		}

		public static bool is_zoophile(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(zoophile);
		}

		public static bool is_masochist(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(TraitDef.Named("Masochist"));
		}

		/// <summary>
		/// Returns true if the given pawn has the nymphomaniac trait.
		/// This may or may not be a nymph pawnKind.
		/// </summary>
		public static bool is_nympho(Pawn pawn)
		{
			return has_traits(pawn) && pawn.story.traits.HasTrait(nymphomaniac);
		}

		public static bool is_slime(Pawn pawn)
		{
			string racename = pawn.kindDef.race.defName.ToLower();
			//if (Prefs.DevMode) Log.Message("[RJW] is_slime " + xxx.get_pawnname(pawn) + " " + racename.Contains("slime"));

			return racename.Contains("slime");
		}

		public static bool is_demon(Pawn pawn)
		{
			string racename = pawn.kindDef.race.defName.ToLower();
			//if (Prefs.DevMode) Log.Message("[RJW] is_demon " + xxx.get_pawnname(pawn) + " " + racename.Contains("demon"));

			return racename.Contains("demon");
		}

		public static bool is_asexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Asexual;
		public static bool is_bisexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Bisexual;
		public static bool is_homosexual(Pawn pawn) => (CompRJW.Comp(pawn).orientation == Orientation.Homosexual || CompRJW.Comp(pawn).orientation == Orientation.MostlyHomosexual);
		public static bool is_heterosexual(Pawn pawn) => (CompRJW.Comp(pawn).orientation == Orientation.Heterosexual || CompRJW.Comp(pawn).orientation == Orientation.MostlyHeterosexual);
		public static bool is_pansexual(Pawn pawn) => CompRJW.Comp(pawn).orientation == Orientation.Pansexual;

		public static bool is_slave(Pawn pawn)
		{
			if (SimpleSlaveryIsActive)
				return pawn?.health.hediffSet.HasHediff(HediffDef.Named("Enslaved")) ?? false;
			else
				return false;
		}

		// A quick check on whether the pawn has the two traits
		// It's used in determine the eligibility of CP raping for the non-futa women
		// Before using it, you should make sure the pawn has traits.
		public static bool is_nympho_or_rapist_or_zoophile(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return (is_rapist(pawn) || is_nympho(pawn) || is_zoophile(pawn));
		}

		//Humanoid Alien Framework traits
		public static bool is_xenophile(Pawn pawn)
		{
			if (!has_traits(pawn) || !AlienFrameworkIsActive) { return false; }
			return pawn.story.traits.DegreeOfTrait(xenophobia) == -1;
		}

		public static bool is_xenophobe(Pawn pawn)
		{
			if (!has_traits(pawn) || !AlienFrameworkIsActive) { return false; }
			return pawn.story.traits.DegreeOfTrait(xenophobia) == 1;
		}

		public static bool is_whore(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return pawn != null && pawn.IsDesignatedService() && (!RomanceDiversifiedIsActive || !pawn.story.traits.HasTrait(asexual));
			//return (pawn != null && pawn.ownership != null && pawn.ownership.OwnedBed is Building_WhoreBed && (!xxx.RomanceDiversifiedIsActive || !pawn.story.traits.HasTrait(xxx.asexual)));
		}

		public static bool is_lecher(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(philanderer) || PsychologyIsActive && pawn.story.traits.HasTrait(lecher);
		}

		public static bool is_prude(Pawn pawn)
		{
			if (!has_traits(pawn)) { return false; }
			return RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(faithful) || PsychologyIsActive && pawn.story.traits.HasTrait(prude);
		}

		public static bool is_animal(Pawn pawn)
		{
			//return !pawn.RaceProps.Humanlike;
			//Edited by nizhuan-jjr:to make Misc.Robots not allowed to have sex. This change makes those robots not counted as animals.
			//return (pawn.RaceProps.Animal || (pawn.RaceProps.intelligence == Intelligence.Animal));
			return pawn?.RaceProps?.Animal ?? false;
		}

		public static bool is_insect(Pawn pawn)
		{
			//Added by Hoge: Insects are also animal. you need check is_insect before is_animal.
			//return pawn.RaceProps.FleshType.defName == "Insectoid";
			//Added by Ed86: its better? isnt it?
			if (pawn == null) return false;

			bool isit = pawn.RaceProps.FleshType == FleshTypeDefOf.Insectoid
						//genetic rim
						|| pawn.RaceProps.FleshType.defName.Contains("GR_Insectoid");
			//Log.Message("is_insect " + get_pawnname(pawn) + " - " + isit);
			return isit;
		}

		public static bool is_mechanoid(Pawn pawn)
		{
			//Added by nizhuan-jjr:to make Misc.Robots not allowed to have sex. Note:Misc.MAI is not a mechanoid.
			if (pawn == null) return false;
			if (AndroidsCompatibility.IsAndroid(pawn)) return false;

			bool isit = pawn.RaceProps.IsMechanoid
						|| pawn.RaceProps.FleshType == FleshTypeDefOf.Mechanoid
						//genetic rim
						|| pawn.RaceProps.FleshType.defName.Contains("GR_Mechanoid")
						//android tiers
						|| pawn.RaceProps.FleshType.defName.Contains("MechanisedInfantry")
						|| pawn.RaceProps.FleshType.defName.Contains("Android");
			//Log.Message("is_mechanoid " + get_pawnname(pawn) + " - " + isit);
			return isit;
		}

		public static bool is_tooluser(Pawn pawn)
		{
			return pawn.RaceProps.ToolUser;
		}

		public static bool is_human(Pawn pawn)
		{
			if (pawn == null) return false;

			return pawn.RaceProps.Humanlike;//||pawn.kindDef.race == ThingDefOf.Human
		}

		public static bool is_female(Pawn pawn)
		{
			return pawn.gender == Gender.Female;
		}
		public static bool is_male(Pawn pawn)
		{
			return pawn.gender == Gender.Male;
		}

		public static bool is_healthy(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.capacities.CanBeAwake &&
				pawn.health.hediffSet.BleedRateTotal <= 0.0f &&
				pawn.health.hediffSet.PainTotal < config.significant_pain_threshold;
		}

		public static bool is_healthy_enough(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.capacities.CanBeAwake &&
				pawn.health.hediffSet.BleedRateTotal <= 0.1f;
		}

		//pawn can initiate action or respond - whoring, etc
		public static bool IsTargetPawnOkay(Pawn pawn)
		{
			return xxx.is_healthy(pawn) && !pawn.Downed && !pawn.Suspended;
		}

		public static bool is_not_dying(Pawn pawn)
		{
			return !pawn.Dead &&
				pawn.health.hediffSet.BleedRateTotal <= 0.3f;
		}

		public static bool is_starved(Pawn pawn)
		{
			return pawn?.needs?.food != null &&
				pawn.needs.food.Starving;
		}
		public static float bleedingRate(Pawn pawn)
		{
			return pawn?.health?.hediffSet?.BleedRateTotal ?? 0f;
		}

		public static bool is_Virgin(Pawn pawn)
		{
			//if (RJWSettings.DevMode) Log.Message("[RJW]xxx::is_Virgin check:" +get_pawnname(pawn));
			if (pawn.relations != null)
				if (pawn.relations.ChildrenCount > 0)
				{
					//if (RJWSettings.DevMode) Log.Message("[RJW]xxx::is_Virgin " + " ChildrenCount " + pawn.relations.ChildrenCount);
					return false;
				}

			if (!(
				pawn.records.GetValue(GetRapedAsComfortPrisoner) == 0 &&
				pawn.records.GetValue(CountOfSex) == 0 &&
				pawn.records.GetValue(CountOfSexWithHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfSexWithAnimals) == 0 &&
				pawn.records.GetValue(CountOfSexWithInsects) == 0 &&
				pawn.records.GetValue(CountOfSexWithOthers) == 0 &&
				pawn.records.GetValue(CountOfSexWithCorpse) == 0 &&
				pawn.records.GetValue(CountOfWhore) == 0 &&
				pawn.records.GetValue(CountOfRapedHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByHumanlikes) == 0 &&
				pawn.records.GetValue(CountOfRapedAnimals) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByAnimals) == 0 &&
				pawn.records.GetValue(CountOfRapedInsects) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByInsects) == 0 &&
				pawn.records.GetValue(CountOfRapedOthers) == 0 &&
				pawn.records.GetValue(CountOfBeenRapedByOthers) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthHuman) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthAnimal) == 0 &&
				pawn.records.GetAsInt(xxx.CountOfBirthEgg) == 0
				))
			{
				//if (RJWSettings.DevMode) Log.Message("[RJW]xxx::is_Virgin " + "records check fail");
				return false;
			}
			return true;
		}

		public static string get_pawnname(Pawn who)
		{
			//Log.Message("[RJW]xxx::get_pawnname is "+ who.KindLabelDefinite());
			//Log.Message("[RJW]xxx::get_pawnname is "+ who.KindLabelIndefinite());
			if (who == null) return "null";

			string name = who.Label;
			if (name != null)
			{
				if (who.Name?.ToStringShort != null)
					name = who.Name.ToStringShort;
			}
			else
				name = "noname";

			return name;
		}

		public static bool is_gettin_rapeNow(Pawn pawn)
		{
			if (pawn?.jobs?.curDriver != null)
			{
				return pawn.jobs.curDriver.GetType() == typeof(JobDriver_GettinRaped);
			}
			return false;
		}

		public static bool can_path_to_target(Pawn pawn, IntVec3 Position)
		{
			//probably less taxing, but ignores walls
			if (RJWSettings.maxDistancetowalk < 100)
				return pawn.Position.DistanceToSquared(Position) < RJWSettings.maxDistancetowalk;

			//probably more taxing, using real path
			bool canit = true;
			PawnPath pawnPath = pawn.Map.pathFinder.FindPath(pawn.Position, Position, pawn, PathEndMode.OnCell);
			if (pawnPath.TotalCost > RJWSettings.maxDistancetowalk)
				canit = false;// too far
			pawnPath.Dispose();
			return canit;
		}

		public static void reduce_rest(Pawn pawn, int x = 1)
		{
			if (pawn.Has(Quirk.Vigorous)) x -= 1;

			Need_Rest need_rest = pawn.needs.TryGetNeed<Need_Rest>();
			if (need_rest == null)
				return;

			need_rest.CurLevel -= need_rest.RestFallPerTick * x;
		}

		public static float need_some_sex(Pawn pawn)
		{
			// 3=> always horny for non humanlikes
			float horniness_degree = 3f;
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex == null) return horniness_degree;
			if (need_sex.CurLevel < need_sex.thresh_frustrated()) horniness_degree = 3f;
			else if (need_sex.CurLevel < need_sex.thresh_horny()) horniness_degree = 2f;
			else if (need_sex.CurLevel < need_sex.thresh_satisfied()) horniness_degree = 1f;
			else horniness_degree = 0f;
			return horniness_degree;
		}

		public static bool is_frustrated(Pawn pawn)
		{
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex == null) return false;
			return need_sex.CurLevel < need_sex.thresh_frustrated();
		}

		public static bool is_horny(Pawn pawn)
		{
			Need_Sex need_sex = pawn.needs.TryGetNeed<Need_Sex>();
			if (need_sex == null) return false;
			return need_sex.CurLevel < need_sex.thresh_horny();
		}

		/// <summary> Checks to see if the pawn has any partners who don't have a Polyamorous/Polygamous trait; aka someone who'd get mad about sleeping around. </summary>
		/// <returns> True if the pawn has at least one romantic partner who does not have a poly trait. False if no partners or all partners are poly. </returns>
		public static bool HasNonPolyPartnerOnCurrentMap(Pawn pawn)
		{
			// If they don't have a partner at all we can bail right away.
			if (!LovePartnerRelationUtility.HasAnyLovePartner(pawn))
				return false;

			// They have a partner and a mod that adds a poly trait, so check each partner to see if they're poly.
			foreach (DirectPawnRelation relation in pawn.relations.DirectRelations)
			{
				if (relation.def != PawnRelationDefOf.Lover &&
					relation.def != PawnRelationDefOf.Fiance &&
					relation.def != PawnRelationDefOf.Spouse)
				{
					continue;
				}

				// Dead partners don't count.  And stasis'd partners will never find out!
				if (relation.otherPawn.Dead || relation.otherPawn.Suspended)
					continue;

				// Neither does anyone on another map because cheating away from home is obviously never ever discovered
				if (pawn.Map == null || relation.otherPawn.Map == null || relation.otherPawn.Map != pawn.Map)
					continue;

				if ((RomanceDiversifiedIsActive && relation.otherPawn.story.traits.HasTrait(polyamorous)) ||
					(PsychologyIsActive && relation.otherPawn.story.traits.HasTrait(polygamous)))
				{
					// We have a partner who has the poly trait!  But they could have multiple partners so keep checking.
					continue;
				}

				// We found a partner who doesn't have a poly trait.
				return true;
			}

			// If we got here then we checked every partner and all of them had a poly trait, so they don't have a non-poly partner.
			return false;
		}

		public static Gender opposite_gender(Gender g)
		{
			switch (g)
			{
				case Gender.Male:
					return Gender.Female;
				case Gender.Female:
					return Gender.Male;
				default:
					return Gender.None;
			}
		}

		public static float get_sex_ability(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(sex_stat, false);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			//this and below should probably be rewriten to do calculations here
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static float get_vulnerability(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(vulnerability_stat, false);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static float get_sex_drive(Pawn pawn)
		{
			try
			{
				return pawn.GetStatValue(sex_drive_stat, false);
			}
			catch (NullReferenceException)
			//not seeded with stats, error for non humanlikes/corpses
			{
				//Log.Warning(e.ToString());
				return 1f;
			}
		}

		public static bool isSingleOrPartnerNotHere(Pawn pawn)
		{
			return LovePartnerRelationUtility.ExistingLovePartner(pawn) == null || LovePartnerRelationUtility.ExistingLovePartner(pawn).Map != pawn.Map;
		}
		//base check
		public static bool can_do_loving(Pawn pawn)
		{
			if (is_mechanoid(pawn))
				return false;

			if (is_human(pawn))
			{
				int age = pawn.ageTracker.AgeBiologicalYears;

				if (age < RJWSettings.sex_minimum_age)
					return false;

				return true;
			}
			if (is_animal(pawn))
			{
				if (!(RJWSettings.bestiality_enabled || RJWSettings.animal_on_animal_enabled))
					return false;

				//CurLifeStageIndex for insects since they are not reproductive
				return (pawn.ageTracker.CurLifeStageIndex >= 2 || pawn.ageTracker.CurLifeStage.reproductive);
			}
			return false;
		}

		// Penetrative organ check.
		public static bool can_fuck(Pawn pawn)
		{
			//this may cause problems with human mechanoids, like misc. bots or other custom race mechanoids
			if (is_mechanoid(pawn))
				return true;

			if (!can_do_loving(pawn))
				return false;

			if (Genital_Helper.penis_blocked(pawn) || (!Genital_Helper.has_penis(pawn) && !Genital_Helper.has_penis_infertile(pawn) && !Genital_Helper.has_ovipositorF(pawn))) return false;

			return true;
		}

		// Orifice check.
		public static bool can_be_fucked(Pawn pawn)
		{
			if (is_mechanoid(pawn))
				return false;

			if (!can_do_loving(pawn))
				return false;

			if (Genital_Helper.has_anus(pawn) && !Genital_Helper.anus_blocked(pawn))
				return true;
			if (Genital_Helper.has_vagina(pawn) && !Genital_Helper.vagina_blocked(pawn))
				return true;
			if (Genital_Helper.has_mouth(pawn) && !Genital_Helper.oral_blocked(pawn))
				return true;
			//not sure about below, when female rape male, need to check all code so meh
			//if ((Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn) || Genital_Helper.has_ovipositorF(pawn)) && !Genital_Helper.penis_blocked(pawn))
			//	return true;
			//if (Genital_Helper.has_breasts(pawn) && !Genital_Helper.breasts_blocked(pawn))
			//	return true;
			//if (pawn.health.hediffSet.GetNotMissingParts().Any(part => part.IsInGroup(BodyPartGroupDefOf.RightHand) || part.IsInGroup(BodyPartGroupDefOf.LeftHand)) && !Genital_Helper.hands_blocked(pawn))
			//	return true;

			return false;
		}

		public static bool can_rape(Pawn pawn)
		{
			if (!RJWSettings.rape_enabled) return false;

			if (is_mechanoid(pawn))
				return true;

			if (!(can_fuck(pawn) ||
				(!is_male(pawn) && get_vulnerability(pawn) < RJWSettings.nonFutaWomenRaping_MaxVulnerability && can_be_fucked(pawn))))
				return false;

			if (is_human(pawn))
			{
				if (RJWSettings.WildMode)
					return true;

				return need_some_sex(pawn) > 0;
			}

			return true;
		}

		public static bool can_get_raped(Pawn pawn)
		{
			if (!RJWSettings.rape_enabled) return false;

			if (!can_be_fucked(pawn))
				return false;

			if (is_human(pawn))
			{
				if (RJWSettings.WildMode)
					return true;

				if (!(RJWSettings.rapee_MinVulnerability_human > 0 && get_vulnerability(pawn) > RJWSettings.rapee_MinVulnerability_human))
					return false;
			}

			return true;
		}

		//Takes the nutrition away from the one penetrating and injects it to the one on the receiving end
		//As with everything in the mod, this could be greatly extended, current focus though is to prevent starvation of those caught in a huge horde of rappers (that may happen with some mods)
		public static void TransferNutrition(Pawn pawn, Pawn partner, rjwSextype sextype)
		{
			//Log.Message("xxx::TransferNutrition:: " + xxx.get_pawnname(pawn) + " => " + xxx.get_pawnname(partner));
			if (partner?.needs == null)
			{
				//Log.Message("xxx::TransferNutrition() failed due to lack of transfer equipment or pawn ");
				return;
			}

			if (sextype == xxx.rjwSextype.Oral && Genital_Helper.has_penis(pawn))
			{
				Need_Food need = pawn.needs.TryGetNeed<Need_Food>();
				if (need == null)
				{
					//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " doesn't track nutrition in itself, probably shouldn't feed the others");
					return;
				}
				float nutrition_amount = Math.Min(need.MaxLevel / 15f, need.CurLevel); //body size is taken into account implicitly by need.MaxLevel
				pawn.needs.food.CurLevel = need.CurLevel - nutrition_amount;
				//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " sent " + nutrition_amount + " of nutrition");

				if (partner?.needs?.TryGetNeed<Need_Food>() != null)
				{
					//Log.Message("xxx::TransferNutrition() " +  xxx.get_pawnname(partner) + " can receive");
					partner.needs.food.CurLevel += nutrition_amount;
				}

				if (xxx.DubsBadHygieneIsActive)
				{
					Need DBHThirst = partner.needs.AllNeeds.Find(x => x.def == DefDatabase<NeedDef>.GetNamed("DBHThirst"));
					if (DBHThirst != null)
					{
						//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(partner) + " decreasing thirst");
						partner.needs.TryGetNeed(DBHThirst.def).CurLevel += 0.1f;
					}
				}
			}
			if (xxx.RoMIsActive && (sextype == xxx.rjwSextype.Oral || sextype == xxx.rjwSextype.Vaginal || sextype == xxx.rjwSextype.Anal || sextype == xxx.rjwSextype.DoublePenetration))
			{
				if (has_traits(partner))
					if ((partner.story.traits.HasTrait(TraitDef.Named("Succubus")) || partner.story.traits.HasTrait(TraitDef.Named("Warlock"))))
					{
						Need TM_Mana = partner.needs.AllNeeds.Find(x => x.def == DefDatabase<NeedDef>.GetNamed("TM_Mana"));
						if (TM_Mana != null)
						{
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(partner) + " increase mana");
							partner.needs.TryGetNeed(TM_Mana.def).CurLevel += 0.1f;
						}

						Need_Rest need = pawn.needs.TryGetNeed<Need_Rest>();
						if (need != null)
						{
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(partner) + " increase rest");
							partner.needs.TryGetNeed(need.def).CurLevel += 0.25f;
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " decrease rest");
							pawn.needs.TryGetNeed(need.def).CurLevel -= 0.25f;
						}
					}

				if (has_traits(pawn))
					if ((pawn.story.traits.HasTrait(TraitDef.Named("Succubus")) || pawn.story.traits.HasTrait(TraitDef.Named("Warlock"))))
					{
						Need TM_Mana = pawn.needs.AllNeeds.Find(x => x.def == DefDatabase<NeedDef>.GetNamed("TM_Mana"));
						if (TM_Mana != null)
						{
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " increase mana");
							pawn.needs.TryGetNeed(TM_Mana.def).CurLevel += 0.1f;
						}

						Need_Rest need = pawn.needs.TryGetNeed<Need_Rest>();
						if (need != null)
						{
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " increase rest");
							pawn.needs.TryGetNeed(need.def).CurLevel += 0.25f;
							//Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(partner) + " decrease rest");
							partner.needs.TryGetNeed(need.def).CurLevel -= 0.25f;
						}
					}
			}
		}

		public static bool bed_has_at_least_two_occupants(Building_Bed bed)
		{
			return bed.CurOccupants.Count() >= 2;
		}

		public static bool in_same_bed(Pawn pawn, Pawn partner)
		{
			if (pawn.InBed() && partner.InBed())
			{
				if (pawn.CurrentBed() == partner.CurrentBed())
					return true;
			}
			return false;
		}

		public static bool is_laying_down_alone(Pawn pawn)
		{
			if (pawn == null || !pawn.InBed()) return false;

			if (pawn.CurrentBed() != null)
				return !bed_has_at_least_two_occupants(pawn.CurrentBed());
			return true;
		}

		// Overrides the current clothing. Defaults to nude, with option to keep headgear on.
		public static void DrawNude(Pawn pawn, bool keep_hat_on = false)
		{
			if (!is_human(pawn)) return;
			if (RJWPreferenceSettings.sex_wear == RJWPreferenceSettings.Clothing.Clothed) return;

			pawn.Drawer.renderer.graphics.ClearCache();
			pawn.Drawer.renderer.graphics.apparelGraphics.Clear();
			if (RJWPreferenceSettings.sex_wear == RJWPreferenceSettings.Clothing.Headgear || keep_hat_on)
			{
				foreach (Apparel current in pawn.apparel.WornApparel.Where(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead) || x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead)))
				{
					ApparelGraphicRecord item;
					if (ApparelGraphicRecordGetter.TryGetGraphicApparel(current, pawn.story.bodyType, out item))
					{
						pawn.Drawer.renderer.graphics.apparelGraphics.Add(item);
					}
				}
			}
			pawn.Draw();
		}

		public static void sexTick(Pawn pawn, Pawn partner, bool enablerotation = true, bool pawnnude = true, bool partnernude = true)
		{
			if (enablerotation)
			{
				pawn.rotationTracker.Face(partner.DrawPos);
				partner.rotationTracker.Face(pawn.DrawPos);
			}

			if (RJWSettings.sounds_enabled)
			{
				SoundDef.Named("Sex").PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));
			}

			pawn.Drawer.Notify_MeleeAttackOn(partner);
			if (enablerotation)
				pawn.rotationTracker.FaceCell(partner.Position);

			// Endytophiles prefer clothed sex, everyone else gets nude.
			if (pawnnude && !has_quirk(pawn, "Endytophile"))
			{
				DrawNude(pawn);
			}

			if (partnernude && !has_quirk(pawn, "Endytophile"))
			{
				DrawNude(partner);
			}
		}

		//violent - mark true when pawn rape partner
		//Note: violent is not reliable, since either pawn could be the rapist. Check jobdrivers instead, they're still active since this is called before ending the job.
		public static void think_about_sex(Pawn pawn, Pawn partner, bool isReceiving, bool violent = false, rjwSextype sextype = rjwSextype.None, bool isCoreLovin = false, bool whoring = false)
		{
			// Partner should never be null, but just in case something gets changed elsewhere..
			if (partner == null)
			{
				Log.Message("xxx::think-after_sex( ERROR: " + get_pawnname(pawn) + " has no partner. This should not be called from solo acts. Sextype: " + sextype);
				return;
			}

			// Both pawns are now checked individually, instead of giving thoughts to the partner.
			//Can just return if the currently checked pawn is dead or can't have thoughts, which simplifies the checks.
			if (pawn.Dead || !is_human(pawn))
				return;

			//--Log.Message("xxx::think_about_sex( " + xxx.get_pawnname(pawn) + ", " + xxx.get_pawnname(partner) + ", " + violent + " ) called");
			//--Log.Message("xxx::think_about_sex( " + xxx.get_pawnname(pawn) + ", " + xxx.get_pawnname(partner) + ", " + violent + " ) - setting pawn thoughts");

			//unconscious pawns has no thoughts
			//and if they has sex, its probably rape, since they have no choice
			//			pawn.Awake();
			if (!pawn.health.capacities.CanBeAwake)
			{
				ThoughtDef pawn_thought = is_masochist(pawn) ? masochist_got_raped : got_raped_unconscious;
				pawn.needs.mood.thoughts.memories.TryGainMemory(pawn_thought);
				return;
			}

			// Thoughts for animal-on-colonist.
			if (is_animal(partner) && isReceiving)
			{
				if (!is_zoophile(pawn) && !violent)
				{
					if (sextype == rjwSextype.Oral)
						pawn.needs.mood.thoughts.memories.TryGainMemory(allowed_animal_to_lick);
					else if (sextype == rjwSextype.Anal || sextype == rjwSextype.Vaginal)
						pawn.needs.mood.thoughts.memories.TryGainMemory(allowed_animal_to_breed);
					else //Other rarely seen sex types, such as fingering (by primates, monster girls, etc)
						pawn.needs.mood.thoughts.memories.TryGainMemory(allowed_animal_to_grope);
				}
				else
				{
					if (!is_zoophile(pawn))
					{
						if (sextype == rjwSextype.Oral)
						{
							pawn.needs.mood.thoughts.memories.TryGainMemory(is_masochist(pawn) ? masochist_got_licked : got_licked);
						}
						else if (sextype == rjwSextype.Anal || sextype == rjwSextype.Vaginal)
						{
							pawn.needs.mood.thoughts.memories.TryGainMemory(is_masochist(pawn) ? masochist_got_bred : got_bred);
						}
						else //Other types
						{
							pawn.needs.mood.thoughts.memories.TryGainMemory(is_masochist(pawn) ? masochist_got_groped : got_groped);
						}
					}
					else
					{
						if (sextype == rjwSextype.Oral)
							pawn.needs.mood.thoughts.memories.TryGainMemory(zoophile_got_licked);
						else if (sextype == rjwSextype.Anal || sextype == rjwSextype.Vaginal)
							pawn.needs.mood.thoughts.memories.TryGainMemory(zoophile_got_bred);
						else //Other types
							pawn.needs.mood.thoughts.memories.TryGainMemory(zoophile_got_groped);
					}
				}

				if (!partner.Dead && is_zoophile(pawn) && pawn.CurJob.def != gettin_raped && partner.Faction == null && pawn.Faction == Faction.OfPlayer)
				{
					InteractionDef intDef = !partner.AnimalOrWildMan() ? InteractionDefOf.RecruitAttempt : InteractionDefOf.TameAttempt;
					pawn.interactions.TryInteractWith(partner, intDef);
				}
			}

			// Edited by nizhuan-jjr:The two types of stole_sone_lovin are violent due to the description, so I make sure the thought would only trigger after violent behaviors.
			// Edited by hoge: !is_animal is include mech. mech has no mood.
			// Edited by Zaltys: Since this is checked for both pawns, checking violent doesn't work.
			if (partner.Dead || partner.CurJob.def == gettin_raped)
			{ // Rapist
				ThoughtDef pawn_thought = is_rapist(pawn) || is_bloodlust(pawn) ? bloodlust_stole_some_lovin : stole_some_lovin;
				pawn.needs.mood.thoughts.memories.TryGainMemory(pawn_thought);

				if ((is_necrophiliac(pawn) || is_psychopath(pawn)) && partner.Dead)
				{
					pawn.needs.mood.thoughts.memories.TryGainMemory(violated_corpse);
				}
			}
			else if (pawn.CurJob.def == gettin_raped) // Rape by animals handled earlier.
			{ // Raped
				if (is_human(partner))
				{
					ThoughtDef pawn_thought = is_masochist(pawn) ? masochist_got_raped : got_raped;
					pawn.needs.mood.thoughts.memories.TryGainMemory(pawn_thought);

					ThoughtDef pawn_thought_about_rapist = is_masochist(pawn) ? kinda_like_my_rapist : hate_my_rapist;
					pawn.needs.mood.thoughts.memories.TryGainMemory(pawn_thought_about_rapist, partner);
				}

				if (pawn.Faction != null && pawn.Map != null && !is_masochist(pawn) && !(is_animal(partner) && is_zoophile(pawn)))
				{
					foreach (Pawn bystander in pawn.Map.mapPawns.SpawnedPawnsInFaction(pawn.Faction).Where(x => !is_animal(x) && x != pawn && x != partner && !x.Downed && !x.Suspended))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (pawn.CanSee(bystander) && pawn.Position.DistanceToSquared(bystander.Position) < 15)
						{
							pawn.needs.mood.thoughts.memories.TryGainMemory(allowed_me_to_get_raped, bystander);
						}
					}
				}
			}
			else if (is_human(partner))
			{
				if (!isCoreLovin && !whoring)
				{
					// human partner and not part of rape or necrophilia.  add the vanilla GotSomeLovin thought
					pawn.needs.mood.thoughts.memories.TryGainMemory(VanillaGotSomeLovin, partner);
				}
			}

			//--Log.Message("xxx::think_about_sex( " + xxx.get_pawnname(pawn) + ", " + xxx.get_pawnname(partner) + ", " + violent + " ) - setting disease thoughts");

			ThinkAboutDiseases(pawn, partner);
		}

		private static void ThinkAboutDiseases(Pawn pawn, Pawn partner)
		{
			// Dead and non-humans have no diseases (yet).
			if (partner.Dead || !is_human(partner)) return;

			// check for visible diseases
			// Add negative relation for visible diseases on the genitals
			int pawn_rash_severity = std.genital_rash_severity(pawn) - std.genital_rash_severity(partner);
			ThoughtDef pawn_thought_about_rash;
			if (pawn_rash_severity == 1) pawn_thought_about_rash = saw_rash_1;
			else if (pawn_rash_severity == 2) pawn_thought_about_rash = saw_rash_2;
			else if (pawn_rash_severity >= 3) pawn_thought_about_rash = saw_rash_3;
			else return;
			Thought_Memory memory = (Thought_Memory)ThoughtMaker.MakeThought(pawn_thought_about_rash);
			partner.needs.mood.thoughts.memories.TryGainMemory(memory, pawn);
		}

		// <summary>Updates records for whoring.</summary>
		public static void UpdateRecords(Pawn pawn, int price)
		{
			pawn.records.AddTo(EarnedMoneyByWhore, price);
			pawn.records.Increment(CountOfWhore);
			//this is added by normal outcome
			//pawn.records.Increment(CountOfSex);
		}

		// <summary>Updates records. "Pawn" should be initiator, and "partner" should be the target.</summary>
		public static void UpdateRecords(Pawn pawn, Pawn partner, rjwSextype sextype, bool isRape = false, bool isLoveSex = false)
		{
			if (!pawn.Dead)
				UpdateRecordsInternal(pawn, partner, isRape, isLoveSex, true, sextype);

			if (partner == null || partner.Dead)
				return;

			UpdateRecordsInternal(partner, pawn, isRape, isLoveSex, false, sextype);
		}

		private static void UpdateRecordsInternal(Pawn pawn, Pawn partner, bool isRape, bool isLoveSex, bool pawnIsRaper, rjwSextype sextype)
		{
			if (pawn == null) return;
			if (pawn.health.Dead) return;

			if (sextype == rjwSextype.Masturbation)
			{
				pawn.records.Increment(CountOfFappin);
				return;
			}

			bool isVirginSex = is_Virgin(pawn); //need copy value before count increase.
			ThoughtDef currentThought = null;

			pawn.records.Increment(CountOfSex);

			if (!isRape)
			{
				if (is_human(partner))
				{
					pawn.records.Increment(partner.health.Dead ? CountOfSexWithCorpse : CountOfSexWithHumanlikes);
					currentThought = isLoveSex ? gave_virginity : null;
				}
				else if (is_insect(partner))
				{
					pawn.records.Increment(CountOfSexWithInsects);
				}
				else if (is_animal(partner))
				{
					pawn.records.Increment(CountOfSexWithAnimals);
					currentThought = is_zoophile(pawn) ? gave_virginity : null;
				}
				else
				{
					pawn.records.Increment(CountOfSexWithOthers);
				}
			}
			else
			{
				if (!pawnIsRaper)
				{
					currentThought = is_masochist(pawn) ? gave_virginity : lost_virginity;
				}

				if (is_human(partner))
				{
					pawn.records.Increment(pawnIsRaper ? partner.health.Dead ? CountOfSexWithCorpse : CountOfRapedHumanlikes : CountOfBeenRapedByHumanlikes);
					if (pawnIsRaper && (is_rapist(pawn) || is_bloodlust(pawn)))
						currentThought = gave_virginity;
				}
				else if (is_insect(partner))
				{
					pawn.records.Increment(CountOfSexWithInsects);
					pawn.records.Increment(pawnIsRaper ? CountOfRapedInsects : CountOfBeenRapedByInsects);
				}
				else if (is_animal(partner))
				{
					pawn.records.Increment(CountOfSexWithAnimals);
					pawn.records.Increment(pawnIsRaper ? CountOfRapedAnimals : CountOfBeenRapedByAnimals);
					if (is_zoophile(pawn)) currentThought = gave_virginity;
				}
				else
				{
					pawn.records.Increment(CountOfSexWithOthers);
					pawn.records.Increment(pawnIsRaper ? CountOfRapedOthers : CountOfBeenRapedByOthers);
				}
			}

			//TODO: someday only loose virginity only during vaginal sex
			//if (isVirginSex) //&& (sextype == rjwSextype.Vaginal || sextype == rjwSextype.DoublePenetration))
			//{
			//	Log.Message(xxx.get_pawnname(pawn) + " | " + xxx.get_pawnname(partner) + " | " + currentThought);
			//	Log.Message("1");
			//	if (!is_animal(partner))//passive
			//	{
			//		if (currentThought != null)
			//			partner.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//	Log.Message("2");
			//	if (!is_animal(pawn))//active
			//	{
			//		currentThought = took_virginity;
			//		pawn.needs.mood.thoughts.memories.TryGainMemory(currentThought);
			//	}
			//}
		}

		//============↓======Section of processing the whoring system===============↓=============

		public static Building_Bed FindBed(Pawn pawn)
		{
			if (pawn.ownership.OwnedBed != null && pawn.ownership.OwnedBed.MaxAssignedPawnsCount > 0)
			{
				return pawn.ownership.OwnedBed;
			}
			return null;
		}

		public static bool CanUse(Pawn pawn, Building_Bed bed)
		{
			bool flag = pawn.CanReserveAndReach(bed, PathEndMode.InteractionCell, Danger.Unspecified) && !bed.IsForbidden(pawn) && bed.AssignedPawns.Contains(pawn);
			return flag;
		}

		public static void FailOnWhorebedNoLongerUsable(this Toil toil, TargetIndex bedIndex, Building_Bed bed)
		{
			if (toil == null)
			{
				throw new ArgumentNullException(nameof(toil));
			}

			toil.FailOnDespawnedOrNull(bedIndex);
			toil.FailOn(bed.IsBurning);
			toil.FailOn(() => HealthAIUtility.ShouldSeekMedicalRestUrgent(toil.actor));
			toil.FailOn(() => toil.actor.IsColonist && !toil.actor.CurJob.ignoreForbidden && !toil.actor.Downed && bed.IsForbidden(toil.actor));
		}

		public static IntVec3 SleepPosOfAssignedPawn(this Building_Bed bed, Pawn pawn)
		{
			if (!bed.AssignedPawns.Contains(pawn))
			{
				Log.Error("[RJW]xxx::SleepPosOfAssignedPawn - pawn is not an owner of the bed;returning bed.position");
				return bed.Position;
			}

			int slotIndex = 0;
			for (byte i = 0; i < bed.owners.Count; i++)
			{
				if (bed.owners[i] == pawn)
				{
					slotIndex = i;
				}
			}
			return bed.GetSleepingSlotPos(slotIndex);
		}

		//============↓======Section of processing the broken body system===============↓=============
		public static bool BodyIsBroken(Pawn pawn)
		{
			return pawn.health.hediffSet.HasHediff(feelingBroken);
		}

		[SyncMethod]
		public static bool BadlyBroken(Pawn pawn)
		{
			if (!BodyIsBroken(pawn))
				return false;

			int stage = pawn.health.hediffSet.GetFirstHediffOfDef(feelingBroken).CurStageIndex;
			if (stage >= 3)
			{
				//when broken make character masochist
				//todo remove/replace social/needs dubuffs
				if (RJWSettings.AddTrait_Masocist && !is_masochist(pawn))
				{
					var chance = 0.05f;
					if (Rand.Chance(chance))
					{
						if (!is_rapist(pawn))
						{
							pawn.story.traits.GainTrait(new Trait(masochist));
							//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, not masochist, adding masochist trait");
						}
						else
						{
							pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(rapist));
							pawn.story.traits.GainTrait(new Trait(masochist));
							//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, switch rapist -> masochist");
						}
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_raped);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(got_licked);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(hate_my_rapist);
						pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(allowed_me_to_get_raped);
					}
				}
				if (pawn.IsPrisonerOfColony)
				{
					pawn.guest.resistance = Mathf.Max(pawn.guest.resistance - 1f, 0f);
					//Log.Message(xxx.get_pawnname(pawn) + " BadlyBroken, reduce prisoner resistance");
				}
			}
			return stage > 1;
		}
		//add variant for eggs?
		public static void processBrokenPawn(Pawn pawn)
		{
			// Called after rape/breed
			if (pawn is null)
				return;

			if (is_human(pawn) && !pawn.Dead && pawn.records != null)
			{
				if (has_traits(pawn))
				{
					if (xxx.is_slime(pawn))
							return;

					if (!BodyIsBroken(pawn))
						pawn.health.AddHediff(feelingBroken);
					else
					{
						float num = feelingBroken.initialSeverity;
						int feelingBrokenStage = pawn.health.hediffSet.GetFirstHediffOfDef(feelingBroken).CurStageIndex;

						if (xxx.RoMIsActive)
							if (pawn.story.traits.HasTrait(TraitDef.Named("Succubus")))
								num *= 0.25f;

						if (pawn.story.traits.HasTrait(TraitDefOf.Tough))
						{
							num *= 0.5f;
						}
						if (pawn.story.traits.HasTrait(TraitDef.Named("Wimp")))
						{
							num *= 2.0f;
						}
						if (pawn.story.traits.HasTrait(TraitDefOf.Nerves))
						{
							int td = pawn.story.traits.DegreeOfTrait(TraitDefOf.Nerves);
							if (RJWSettings.AddTrait_Nerves && feelingBrokenStage >= 2 && td > -1)
							{
								pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(TraitDefOf.Nerves));
								pawn.story.traits.GainTrait(new Trait(TraitDefOf.Nerves, -1));
							}
							if (RJWSettings.AddTrait_Nerves && feelingBrokenStage >= 3 && td > -2)
							{
								pawn.story.traits.allTraits.Remove(pawn.story.traits.GetTrait(TraitDefOf.Nerves));
								pawn.story.traits.GainTrait(new Trait(TraitDefOf.Nerves, -2));
							}
							switch (td)
							{
								case -2:
									num *= 2.0f;
									break;
								case -1:
									num *= 1.5f;
									break;
								case 1:
									num *= 0.5f;
									break;
								case 2:
									num *= 0.25f;
									break;
							}
						}
						else if (RJWSettings.AddTrait_Nerves && feelingBrokenStage > 1)
						{
							pawn.story.traits.GainTrait(new Trait(TraitDefOf.Nerves, -1));
						}
						pawn.health.hediffSet.GetFirstHediffOfDef(feelingBroken).Severity += num;
					}
					BadlyBroken(pawn);
				}
			}
		}

		public static void ExtraSatisfyForBrokenPawn(Pawn pawn)
		{
			if (!BodyIsBroken(pawn) || pawn.needs?.joy is null)
				return;
			float pawn_satisfaction = 0.2f;
			//Log.Message("Current stage " + pawn.health.hediffSet.GetFirstHediffOfDef(feelingBroken).CurStageIndex);
			switch (pawn.health.hediffSet.GetFirstHediffOfDef(feelingBroken).CurStageIndex)
			{
				case 0:
					break;

				case 1:
					pawn.needs.TryGetNeed<Need_Sex>().CurLevel += pawn_satisfaction;
					pawn.needs.joy.CurLevel += pawn_satisfaction * 0.50f;   // convert half of satisfaction to joy
					break;

				case 2:
					pawn_satisfaction *= 2f;
					pawn.needs.TryGetNeed<Need_Sex>().CurLevel += pawn_satisfaction;
					pawn.needs.joy.CurLevel += pawn_satisfaction * 0.50f;   // convert half of satisfaction to joy
					break;
			}
		}

		//============↑======Section of processing the broken body system===============↑=============
	}
}


code:
using Verse;
using System.Linq;

namespace rjw
{
	[StaticConstructorOnStartup]
	public static class AddComp
	{
		static AddComp()
		{
			AddRJWComp();
		}

		/// <summary>
		/// This automatically adds the comp to all races on startup.
		/// </summary>
		public static void AddRJWComp()
		{
			foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs.Where(thingDef =>
					thingDef.race != null))
			{
				thingDef.comps.Add(new CompProperties_RJW());
				//Log.Message("AddRJWComp to race " + thingDef.label);
			}
		}
	}
}

repo_name: Eggs/rjw

license: mit


code:
using Verse;

namespace rjw
{
	public class CompProperties_RJW : CompProperties
	{
		public CompProperties_RJW()
		{
			compClass = typeof(CompRJW);
		}
	}
}


code:
using Psychology;
using SyrTraits;
using System.Text;
using Verse;
using RimWorld;
using Multiplayer.API;

namespace rjw
{
	public class CompRJW : ThingComp
	{
		/// <summary>
		/// Core comp for genitalia and sexuality tracking.
		/// </summary>
		public CompRJW()
		{
			orientation = Orientation.None;
			quirks = new StringBuilder();
		}

		public CompProperties_RJW Props => (CompProperties_RJW)props;
		public Orientation orientation;
		public StringBuilder quirks;
		public string quirksave; // Not the most elegant way to do this, but it minimizes the save bloat.
		public int NextHookupTick;

		// This automatically checks that genitalia has been added to all freshly spawned pawns.
		public override void PostSpawnSetup(bool respawningAfterLoad)
		{
			base.PostSpawnSetup(respawningAfterLoad);

			if (!(parent is Pawn pawn))
				return;

			if (pawn.kindDef.race.defName.Contains("AIRobot") // No genitalia/sexuality for roombas.
				|| pawn.kindDef.race.defName.Contains("AIPawn") // ...nor MAI.
				|| pawn.kindDef.race.defName.Contains("RPP_Bot")
				) return;

			// No genitalia
			//if (!pawn.RaceProps.body.AllParts.Exists(x => x.def == DefDatabase<BodyPartDef>.GetNamed("Genitals")))
			//	return;

			if (Comp(pawn).orientation == Orientation.None)
			{
				Sexualize(pawn);
				Sexualize(pawn, true);
			}

			//Log.Message("PostSpawnSetup for " + pawn?.Name);
		}

		public override void PostExposeData()
		{
			base.PostExposeData();
			if (!(parent is Pawn))
				return;

			// Saves the data.
			Scribe_Values.Look(ref orientation, "RJW_Orientation");
			Scribe_Values.Look(ref quirksave, "RJW_Quirks");
			Scribe_Values.Look(ref NextHookupTick, "RJW_NextHookupTick");

			//Log.Message("PostExposeData for " + pawn?.Name);

			// Restore quirk data from the truncated save version.
			quirks = new StringBuilder(quirksave);
		}

		public static CompRJW Comp(Pawn pawn)
		{
			// Call CompRJW.Comp(pawn).<method> to check sexuality, etc.
			return pawn.GetComp<CompRJW>();
		}

		// The main method for adding genitalia and orientation.
		public void CopyPsychologySexuality(Pawn pawn)
		{
			try
			{
				int kinsey = pawn.TryGetComp<CompPsychology>().Sexuality.kinseyRating;

				SetCompForKinsey(pawn, kinsey);
			}
			catch
			{
				Log.Warning("CopyPsychologySexuality " + pawn?.Name + ", def: " + pawn?.def?.defName + ", kindDef: " + pawn?.kindDef?.race.defName);
			}
		}

		public void SetCompForKinsey(Pawn pawn, int kinsey)
		{
			Orientation originalOrientation = Comp(pawn).orientation;

			if (kinsey == 0)
				Comp(pawn).orientation = Orientation.Heterosexual;
			else if (kinsey == 1)
				Comp(pawn).orientation = Orientation.MostlyHeterosexual;
			else if (kinsey == 2)
				Comp(pawn).orientation = Orientation.LeaningHeterosexual;
			else if (kinsey == 3)
				Comp(pawn).orientation = Orientation.Bisexual;
			else if (kinsey == 4)
				Comp(pawn).orientation = Orientation.LeaningHomosexual;
			else if (kinsey == 5)
				Comp(pawn).orientation = Orientation.MostlyHomosexual;
			else if (kinsey == 6)
				Comp(pawn).orientation = Orientation.Homosexual;
			/*else
				Log.Error("RJW::ERRROR - unknown kinsey scale value: " + kinsey);/*

			/*if (Comp(pawn).orientation != originalOrientation)
				Log.Message("RJW + Psychology: Inherited pawn " + xxx.get_pawnname(pawn) + " sexuality from Psychology - " + Comp(pawn).orientation);*/
		}

		public void CopyIndividualitySexuality(Pawn pawn)
		{
			try
			{
				CompIndividuality.Sexuality individualitySexuality = pawn.TryGetComp<CompIndividuality>().sexuality;
				Orientation originalOrientation = Comp(pawn).orientation;

				if (individualitySexuality == CompIndividuality.Sexuality.Straight && Comp(pawn).orientation != Orientation.Heterosexual)
					Comp(pawn).orientation = Orientation.Heterosexual;
				else if (individualitySexuality == CompIndividuality.Sexuality.Bisexual && Comp(pawn).orientation != Orientation.Bisexual)
					Comp(pawn).orientation = Orientation.Bisexual;
				else if (individualitySexuality == CompIndividuality.Sexuality.Gay && Comp(pawn).orientation != Orientation.Homosexual)
					Comp(pawn).orientation = Orientation.Homosexual;

				/*if (Comp(pawn).orientation != originalOrientation)
					Log.Message("RJW + [SYR]Individuality: Inherited pawn " + xxx.get_pawnname(pawn) + " sexuality from Individuality - " + Comp(pawn).orientation);*/
			}
			catch
			{
				Log.Warning("CopyIndividualitySexuality " + pawn?.Name + ", def: " + pawn?.def?.defName + ", kindDef: " + pawn?.kindDef?.race.defName);
			}
		}

		[SyncMethod]
		public static void RRTraitCheck(Pawn pawn)
		{
			try
			{
				if (pawn.story.traits.HasTrait(TraitDefOf.Gay))
					Comp(pawn).orientation = Orientation.Homosexual;
				else if (xxx.RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(xxx.straight))
					Comp(pawn).orientation = Orientation.Heterosexual;
				else if (xxx.RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(xxx.bisexual))
					Comp(pawn).orientation = Orientation.Bisexual;
				else if (xxx.RomanceDiversifiedIsActive && pawn.story.traits.HasTrait(xxx.asexual))
					Comp(pawn).orientation = Orientation.Asexual;
			}
			catch
			{
				Log.Warning("RRTraitCheck " + pawn?.Name + ", def: " + pawn?.def?.defName + ", kindDef: " + pawn?.kindDef?.race.defName);
			}
		}

		// The main method for adding genitalia and orientation.
		public void Sexualize(Pawn pawn, bool reroll = false)
		{
			if (reroll)
			{
				Comp(pawn).orientation = Orientation.None;

				if (xxx.has_quirk(pawn, "Fertile"))
				{
					Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("IncreasedFertility"));
					if (fertility != null )
						pawn.health.RemoveHediff(fertility);
				}
				if (xxx.has_quirk(pawn, "Infertile"))
				{
					Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("DecreasedFertility"));
					if (fertility != null)
						pawn.health.RemoveHediff(fertility);
				}
				quirks = new StringBuilder();
			}
			else if (Comp(pawn).orientation != Orientation.None)
				return;

			//roll random RJW orientation
			Comp(pawn).orientation = xxx.is_animal(pawn) ? RollAnimalOrientation() : RollOrientation();

			if (xxx.has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Gay))
				Comp(pawn).orientation = Orientation.Homosexual;

			//Asexual nymp re-roll
			if (xxx.is_nympho(pawn))
				while (Comp(pawn).orientation == Orientation.Asexual)
				{
					Comp(pawn).orientation = RollOrientation();
				}

			//Log.Message("Sexualizing pawn " + pawn?.Name + ", def: " + pawn?.def?.defName);

			if (!reroll)
				Sexualizer.sexualize_pawn(pawn);
			//Log.Message("Orientation for pawn " + pawn?.Name + " is " + orientation);

			if (xxx.has_traits(pawn) && Genital_Helper.has_genitals(pawn) && !(pawn.kindDef.race.defName.ToLower().Contains("droid") && !AndroidsCompatibility.IsAndroid(pawn)))
			{
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.RationalRomance)
					RRTraitCheck(pawn);
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
					CopyPsychologySexuality(pawn);
				if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.SYRIndividuality)
					CopyIndividualitySexuality(pawn);
			}
			else if ((pawn.kindDef.race.defName.ToLower().Contains("droid") && !AndroidsCompatibility.IsAndroid(pawn)) || !Genital_Helper.has_genitals(pawn))
			{
			    // Droids with no genitalia are set as asexual.
			    // If player later adds genitalia to the droid, the droid 'sexuality' gets rerolled.
				Comp(pawn).orientation = Orientation.Asexual;
			}

			QuirkAdder.Generate(pawn);

			if (quirks.Length == 0)
			{
				quirks.Append("None");
				quirksave = quirks.ToString();
			}
		}

		public static void CheckForMismatch(Pawn pawn)
		{
			if (pawn == null || !xxx.has_traits(pawn)) return;
			if (pawn.story.traits.HasTrait(TraitDefOf.Gay) && Comp(pawn).orientation != Orientation.Homosexual
				|| pawn.story.traits.HasTrait(xxx.straight) && Comp(pawn).orientation != Orientation.Heterosexual
				|| pawn.story.traits.HasTrait(xxx.bisexual) && Comp(pawn).orientation != Orientation.Bisexual
				|| pawn.story.traits.HasTrait(xxx.asexual) && Comp(pawn).orientation != Orientation.Asexual)
			{
				RRTraitCheck(pawn);
			}
		}

		[SyncMethod]
		public static bool CheckPreference(Pawn pawn, Pawn partner)
		{
			if (xxx.RomanceDiversifiedIsActive || (xxx.has_traits(pawn) && pawn.story.traits.HasTrait(TraitDefOf.Gay)))
				CheckForMismatch(pawn);

			//if (xxx.is_mechanoid(pawn))
			//	return false;

			Orientation ori = Orientation.None;
			try
			{
				ori = Comp(pawn).orientation;
			}
			catch
			{
				//Log.Message("[RJW]Error, pawn:" + pawn + " doesnt have orientation comp, modded race?");
				return false;
			}

			if (ori == Orientation.Pansexual || ori == Orientation.Bisexual)
				return true;

			if (ori == Orientation.Asexual)
				return false;

			bool isHetero = (Genital_Helper.has_vagina(pawn) && (Genital_Helper.has_penis(partner) || Genital_Helper.has_penis_infertile(partner))) ||
						  (Genital_Helper.has_vagina(partner) && (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)));

			bool isHomo = (Genital_Helper.has_vagina(pawn) && Genital_Helper.has_vagina(partner)) ||
						   ((Genital_Helper.has_penis(partner) || Genital_Helper.has_penis_infertile(partner)) && (Genital_Helper.has_penis(pawn) || Genital_Helper.has_penis_infertile(pawn)));

			if (isHetero && isHomo)
			{
				// Oh you crazy futas.  We could probably do a check against the pawn's gender, but eh.  They've got so many parts available, they'll find something to do.
				return true;
			}

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			switch (ori)
			{
				case Orientation.Heterosexual:
					return !isHomo;
				case Orientation.MostlyHeterosexual:
					return (!isHomo || Rand.Chance(0.2f));
				case Orientation.LeaningHeterosexual:
					return (!isHomo || Rand.Chance(0.6f));
				case Orientation.LeaningHomosexual:
					return (!isHetero || Rand.Chance(0.6f));
				case Orientation.MostlyHomosexual:
					return (!isHetero || Rand.Chance(0.2f));
				case Orientation.Homosexual:
					return !isHetero;
				default:
					Log.Error("RJW::ERROR - tried to check preference for undetermined sexuality.");
					return false;
			}
		}

		[SyncMethod]
		public Orientation RollOrientation()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float random = Rand.Range(0f, 1f);
			float checkpoint = RJWPreferenceSettings.asexual_ratio / RJWPreferenceSettings.GetTotal();

			float checkpoint_pan = checkpoint + (RJWPreferenceSettings.pansexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_het = checkpoint_pan + (RJWPreferenceSettings.heterosexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_bi = checkpoint_het + (RJWPreferenceSettings.bisexual_ratio / RJWPreferenceSettings.GetTotal());
			float checkpoint_gay = checkpoint_bi + (RJWPreferenceSettings.homosexual_ratio / RJWPreferenceSettings.GetTotal());

			if (random < checkpoint)
				return Orientation.Asexual;
			else if (random < checkpoint_pan)
				return Orientation.Pansexual;
			else if (random < checkpoint_het)
				return Orientation.Heterosexual;
			else if (random < checkpoint_het + ((checkpoint_bi - checkpoint_het) * 0.33f))
				return Orientation.MostlyHeterosexual;
			else if (random < checkpoint_het + ((checkpoint_bi - checkpoint_het) * 0.66f))
				return Orientation.LeaningHeterosexual;
			else if (random < checkpoint_bi)
				return Orientation.Bisexual;
			else if (random < checkpoint_bi + ((checkpoint_gay - checkpoint_bi) * 0.33f))
				return Orientation.LeaningHomosexual;
			else if (random < checkpoint_bi + ((checkpoint_gay - checkpoint_bi) * 0.66f))
				return Orientation.MostlyHomosexual;
			else
				return Orientation.Homosexual;
		}

		// Simpler system for animals, with most of them being heterosexual.
		// Don't want to disturb player breeding projects by adding too many gay animals.
		[SyncMethod]
		public Orientation RollAnimalOrientation()
		{
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float random = Rand.Range(0f, 1f);

			if (random < 0.03f)
				return Orientation.Asexual;
			else if (random < 0.85f)
				return Orientation.Heterosexual;
			else if (random < 0.96f)
				return Orientation.Bisexual;
			else
				return Orientation.Homosexual;
		}
	}
}


code:
using System.Text;
using Verse;

namespace rjw
{
	public class CompHediffBodyPart : HediffComp
	{
		/// <summary>
		/// Comp for rjw Thing parts.
		/// </summary>

		public string Size = "";			//eventually replace with below, maybe
		//public float Size;
		//public float Lenghth;
		//public float Width;
		public string Eggs = "";			//for ovi eggs, maybe
		public string FluidType = "";	//cummies/milk - insectjelly/honey etc
		public string FluidAmmount = ""; //ammount of Milk/Ejaculation/Wetness

		public override string CompLabelInBracketsExtra
		{
			get
			{
				if (Size != "")
					return Size;

				return null;
			}
		}

		public override void CompExposeData()
		{
			base.CompExposeData();
			Scribe_Values.Look(ref Size, "Size", "", true);
			Scribe_Values.Look(ref FluidType, "FluidType", "", true);
			Scribe_Values.Look(ref FluidAmmount, "FluidAmmount", "", true);
			Scribe_Values.Look(ref Eggs, "Eggs", "", true);
		}

		public override void CompPostMake()
		{
			//Log.Message("[RJW] CompPostMake " + xxx.get_pawnname(Pawn) + " " + parent.def.defName);
			if (parent.def.defName.ToLower().Contains("breasts") || parent.def.defName.ToLower().Contains("udder"))
			{
				FluidType = "Milk";
				FluidAmmount = "Average";
				Size = "Average";
			}
			if (parent.def.defName.ToLower().Contains("penis") || parent.def.defName.ToLower().Contains("vagina"))
			{
				FluidType = "Cum";
				if (parent.def.defName.ToLower().Contains("penis"))
					FluidAmmount = "Average";
				if (parent.def.defName.ToLower().Contains("vagina"))
					FluidAmmount = "Average";

				Size = "Average";
			}
			if (parent.def.defName.ToLower().Contains("anus"))
			{
				FluidType = "Dry";
				FluidAmmount = "";
				Size = "Average";
			}
			if (parent.def.defName.ToLower().Contains("ovi"))
			{
				Eggs = Pawn.kindDef.race.label;
			}
		}

		public override string CompTipStringExtra
		{
			get
			{
				//Log.Message("[RJW] CompTipStringExtra " + xxx.get_pawnname(Pawn) + " " + parent.def.defName);
				StringBuilder stringBuilder = new StringBuilder();
				//if (parent.def.defName.ToLower().Contains("breasts") || parent.def.defName.ToLower().Contains("udder"))
				//{
				//	if (FluidType != "")
				//		stringBuilder.AppendLine("Milk: " + FluidType);
				//	if (FluidAmmount != "")
				//		stringBuilder.AppendLine("Amount: " + FluidAmmount);
				//	if (Size != "")
				//		stringBuilder.AppendLine("Size: " + Size);//convert to Cups
				//}
				//if (parent.def.defName.ToLower().Contains("penis") || parent.def.defName.ToLower().Contains("vagina"))
				//{
				//	if (FluidType != "")
				//		stringBuilder.AppendLine("Cum: " + FluidType);
				//	if (FluidAmmount != "")
				//	{
				//		if (parent.def.defName.ToLower().Contains("penis"))
				//			stringBuilder.AppendLine("Ejaculation: " + FluidAmmount);
				//		if (parent.def.defName.ToLower().Contains("vagina"))
				//			stringBuilder.AppendLine("Wetness: " + FluidAmmount);
				//	}
				//	if (Size != "")
				//		stringBuilder.AppendLine("Size: " + Size);//convert to LxW
				//}
				//if (parent.def.defName.ToLower().Contains("anus"))
				//{
				//	if (FluidType != "")
				//	{
				//		stringBuilder.AppendLine("Lube: " + FluidType);
				//		if (parent.def.defName.ToLower().Contains("anus"))
				//			stringBuilder.AppendLine("Wetness: " + FluidAmmount);
				//	}
				//	if (Size != "")
				//		stringBuilder.AppendLine("Size: " + Size);//convert to LxW someday
				//}
				if (parent.def.defName.ToLower().Contains("ovi"))
				{
					if (Eggs != "")
						stringBuilder.AppendLine("Eggs: " + Eggs);
				}

				return stringBuilder.ToString();
			}
		}
	}
}


code:
using System.Text;
using Verse;

namespace rjw
{
	public class CompThingBodyPart : ThingComp
	{
		/// <summary>
		/// Comp for rjw Thing parts.
		/// </summary>

		public string Size = "";			//eventually replace with below, maybe
		//public float Size;
		//public float Lenghth;
		//public float Width;
		public string Eggs = "";			//for ovi eggs, maybe
		public string FluidType = "";	//cummies/milk - insectjelly/honey etc
		public string FluidAmmount = ""; //ammount of Milk/Ejaculation/Wetness

		//public override string CompInspectStringExtra()
		//{
		//	return Translator.Translate("Size") + ": " + Size;
		//}

		//public override string TransformLabel(string label)
		//{
		//	if (Size != "")
		//		return label + " (" + Size + ")";

		//	return label;
		//}

		public override string GetDescriptionPart()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(base.GetDescriptionPart());
			if (parent.def.defName.ToLower().Contains("breasts") || parent.def.defName.ToLower().Contains("udder"))
			{
				//if (FluidType == "")
				//{
				//	FluidType = "Milk";
				//	FluidAmmount = "none";
				//}
				//stringBuilder.AppendLine("Milk: " + FluidType);
				//stringBuilder.AppendLine("Amount: " + FluidAmmount);

				if (Size == "")
				{
					Size = "Average";
				}
				stringBuilder.AppendLine("Size: " + Size);//convert to Cups
			}
			if (parent.def.defName.ToLower().Contains("penis") || parent.def.defName.ToLower().Contains("vagina"))
			{
				//if (FluidType == "")
				//{
				//	FluidType = "Cum";
				//	FluidAmmount = "Average";
				//}
				//stringBuilder.AppendLine("Cum: " + FluidType);
				//if (parent.def.defName.ToLower().Contains("penis"))
				//	stringBuilder.AppendLine("Ejaculation: " + FluidAmmount);
				//if (parent.def.defName.ToLower().Contains("vagina"))
				//	stringBuilder.AppendLine("Wetness: " + FluidAmmount);

				if (Size == "")
				{
					Size = "Average";
				}
				stringBuilder.AppendLine("Size: " + Size);//convert to LxW
			}
			if (parent.def.defName.ToLower().Contains("anus"))
			{
				//if (FluidType == "")
				//{
				//	FluidType = "Dry";
				//	FluidAmmount = "Dry";
				//}
				if (Size == "")
				{
					Size = "Average";
				}
				stringBuilder.AppendLine("Size: " + Size);//convert to LxW
			}
			if (parent.def.defName.ToLower().Contains("ovi"))
				if (Eggs != "")
					stringBuilder.AppendLine("Eggs: " + Eggs);

			return stringBuilder.ToString();
		}
	}
}


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

namespace rjw
{
	public enum Orientation
	{
		None,
		Asexual,
		Pansexual,
		Heterosexual,
		MostlyHeterosexual,
		LeaningHeterosexual,
		Bisexual,
		LeaningHomosexual,
		MostlyHomosexual,
		Homosexual
	}
}


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

namespace rjw
{
	public class Quirk
	{
		public static List<Quirk> All = new List<Quirk>();

		public static readonly Quirk Breeder = new Quirk(
			"Breeder",
			"BreederQuirk");
		public static readonly Quirk Endytophile = new Quirk(
			"Endytophile",
			"EndytophileQuirk",
			(pawn, partner) => !partner.apparel.PsychologicallyNude,
			sexProps => !sexProps.Pawn.apparel.PsychologicallyNude
			);
		public static readonly Quirk Exhibitionist = new Quirk(
			"Exhibitionist",
			"ExhibitionistQuirk",
			null,
			SatisfiesExhibitionist
			);
		public static readonly Quirk Fertile = new Quirk(
			"Fertile",
			"FertileQuirk");
		public static readonly Quirk Gerontophile = new Quirk(
			"Gerontophile",
			"GerontophileQuirk",
			(pawn, partner) => SexUtility.ScaleToHumanAge(partner) > 55,
			sexProps => sexProps.HasPartner && SexUtility.ScaleToHumanAge(sexProps.Partner) > 55
			);
		public static readonly Quirk ImpregnationFetish = new Quirk(
			"Impregnation fetish",
			"ImpregnationFetishQuirk",
			(pawn, partner) =>
				PregnancyHelper.CanImpregnate(pawn, partner)
				|| PregnancyHelper.CanImpregnate(partner, pawn),
			sexProps =>
				sexProps.HasPartner
				&& (PregnancyHelper.CanImpregnate(sexProps.Pawn, sexProps.Partner, sexProps.SexType)
					|| PregnancyHelper.CanImpregnate(sexProps.Partner, sexProps.Pawn, sexProps.SexType))
			);
		public static readonly Quirk Incubator = new Quirk(
			"Incubator",
			"IncubatorQuirk");
		public static readonly Quirk Infertile = new Quirk(
			"Infertile",
			"InfertileQuirk");
		public static readonly Quirk Messy = new Quirk(
			"Messy",
			"MessyQuirk");
		public static readonly Quirk Podophile = new Quirk(
			"Podophile",
			"PodophileQuirk",
			null,
			sexProps => sexProps.HasPartner && sexProps.SexType == xxx.rjwSextype.Footjob
			);
		public static readonly Quirk PregnancyFetish = new Quirk(
			"Pregnancy fetish",
			"PregnancyFetishQuirk",
			(pawn, partner) => partner.IsVisiblyPregnant(),
			sexProps => sexProps.HasPartner && sexProps.Partner.IsVisiblyPregnant()
			);
		public static readonly Quirk Sapiosexual = new Quirk(
			"Sapiosexual",
			"SapiosexualQuirk",
			(pawn, partner) => SatisfiesSapiosexual(partner),
			null
			);
		public static readonly Quirk Somnophile = new Quirk(
			"Somnophile",
			"SomnophileQuirk",
			(pawn, partner) => !partner.Awake(),
			sexProps => sexProps.HasPartner && !sexProps.Partner.Awake()
			);
		public static readonly Quirk Teratophile = new Quirk(
			"Teratophile",
			"TeratophileQuirk",
			(pawn, partner) => SatisfiesTeratophile(partner),
			null
			);
		public static readonly Quirk Vigorous = new Quirk(
			"Vigorous",
			"VigorousQuirk");

		// There might be too many of these I dunno.
		// People have expressed "special interest" in some of them so I thought
		// it would be cool to have them in the game but since people are weird you end up with a lot of fetishes.
		public static readonly Quirk ChitinLover = MakeTagBasedQuirk("Chitin lover", "ChitinLoverQuirk", RaceTag.Chitin);
		public static readonly Quirk DemonLover = MakeTagBasedQuirk("Demon lover", "DemonLoverQuirk", RaceTag.Demon);
		public static readonly Quirk FeatherLover = MakeTagBasedQuirk("Feather lover", "FeatherLoverQuirk", RaceTag.Feathers);
		public static readonly Quirk FurLover = MakeTagBasedQuirk("Fur lover", "FurLoverQuirk", RaceTag.Fur);
		public static readonly Quirk PlantLover = MakeTagBasedQuirk("Plant lover", "PlantLoverQuirk", RaceTag.Plant);
		public static readonly Quirk RobotLover = MakeTagBasedQuirk("Robot lover", "RobotLoverQuirk", RaceTag.Robot);
		public static readonly Quirk ScaleLover = MakeTagBasedQuirk("Scale lover", "ScaleLoverQuirk", RaceTag.Scales);
		public static readonly Quirk SkinLover = MakeTagBasedQuirk("Skin lover", "SkinLoverQuirk", RaceTag.Skin);
		public static readonly Quirk SlimeLover = MakeTagBasedQuirk("Slime lover", "SlimeLoverQuirk", RaceTag.Slime);

		public string Key { get; }
		public string LocaliztionKey { get; }
		// Hack, quirk generation logic should be in the quirk not based on flags on the quirk.
		public RaceTag RaceTag { get; }
		readonly Func<Pawn, Pawn, bool> PawnSatisfiesFunc;
		readonly Func<SexProps, bool> SexSatisfiesFunc;
		readonly Action<Pawn> AfterAddFunc;

		static Quirk MakeTagBasedQuirk(string key, string localizationKey, RaceTag tag)
		{
			return new Quirk(
				key,
				localizationKey,
				(pawn, partner) => partner.Has(tag),
				sexProps => sexProps.HasPartner && sexProps.Partner.Has(tag),
				null,
				tag);
		}

		Quirk(
			string key,
			string localizationKey,
			Func<Pawn, Pawn, bool> pawnSatisfies = null,
			Func<SexProps, bool> sexSatisfies = null,
			Action<Pawn> afterAdd = null,
			RaceTag raceTag = null)
		{
			Key = key;
			LocaliztionKey = localizationKey;
			PawnSatisfiesFunc = pawnSatisfies;
			SexSatisfiesFunc = sexSatisfies;
			AfterAddFunc = afterAdd;
			RaceTag = raceTag;
			All.Add(this);
		}

		public bool IsSatisfiedBy(Pawn pawn, Pawn partner)
		{
			return pawn != null
				&& partner != null
				&& pawn.Has(this)
				&& PawnSatisfiesFunc != null
				&& PawnSatisfiesFunc(pawn, partner);
		}

		public static int CountSatisfiedQuirks(Pawn pawn, Pawn partner, xxx.rjwSextype sexType, bool violent)
		{
			var props = new SexProps(pawn, partner, sexType, violent);
			var satisfies = All.Where(quirk =>
				quirk.SexSatisfiesFunc != null
				&& pawn.Has(quirk)
				&& quirk.SexSatisfiesFunc(props));
			return satisfies.Count();
		}

		public void DoAfterAdd(Pawn pawn)
		{
			AfterAddFunc?.Invoke(pawn);
		}

		static bool SatisfiesExhibitionist(SexProps sexProps)
		{
			var zoo = xxx.is_zoophile(sexProps.Pawn);
			return sexProps.Pawn.Map.mapPawns.AllPawnsSpawned.Any(x =>
				x != sexProps.Pawn
				&& x != sexProps.Partner
				&& !x.Dead
				&& (zoo || !xxx.is_animal(x))
				&& x.CanSee(sexProps.Pawn));
		}

		static bool SatisfiesSapiosexual(Pawn partner)
		{
			if (!xxx.has_traits(partner)) return false;
			return partner.story.traits.HasTrait(TraitDefOf.TooSmart)
				|| (xxx.CTIsActive && partner.story.traits.HasTrait(TraitDef.Named("RCT_Savant")))
				|| (xxx.IndividualityIsActive && partner.story.traits.HasTrait(TraitDef.Named("SYR_Scientist")))
				|| (xxx.CTIsActive && partner.story.traits.HasTrait(TraitDef.Named("RCT_Inventor")))
				|| partner.story.traits.HasTrait(TraitDefOf.GreatMemory)
				|| partner.story.traits.HasTrait(TraitDefOf.Transhumanist);
		}

		static bool SatisfiesTeratophile(Pawn partner)
		{
			if (partner.story == null) return false;
			var story = partner.story;
			return RelationsUtility.IsDisfigured(partner)
				|| story.bodyType == BodyTypeDefOf.Fat
				|| story.traits.HasTrait(TraitDefOf.CreepyBreathing)
				|| (story.traits.HasTrait(TraitDefOf.Beauty) && story.traits.DegreeOfTrait(TraitDefOf.Beauty) < 0);
		}

		public static void AddThought(Pawn pawn)
		{
			var thoughtDef = DefDatabase<ThoughtDef>.GetNamed("ThatsMyFetish");
			pawn.needs.mood.thoughts.memories.TryGainMemory(thoughtDef);
		}
	}
}


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

namespace rjw
{
	public static class QuirkAdder
	{
		public static void Add(Pawn pawn, Quirk quirk)
		{
			if (!pawn.Has(quirk))
			{
				var hasFertility = pawn.RaceHasFertility();
				if (quirk == Quirk.Fertile && (!hasFertility || CompRJW.Comp(pawn).quirks.ToString().Contains(Quirk.Infertile.Key)))
				{
					return;
				}
				if (quirk == Quirk.Infertile && (!hasFertility || CompRJW.Comp(pawn).quirks.ToString().Contains(Quirk.Fertile.Key)))
				{
					return;
				}
				// No fair having a fetish for your own race.
				// But tags don't conflict so having a fetish for robot plant dragons is fine.
				if (quirk.RaceTag != null && pawn.Has(quirk.RaceTag))
				{
					return;
				}
				if (quirk == Quirk.Fertile)
				{
					var fertility = HediffDef.Named("IncreasedFertility");
					if (fertility != null)
						pawn.health.AddHediff(fertility);
				}
				if (quirk == Quirk.Infertile)
				{
					var infertility = HediffDef.Named("DecreasedFertility");
					if (infertility != null)
						pawn.health.AddHediff(infertility);
				}

				CompRJW.Comp(pawn).quirks.AppendWithComma(quirk.Key);
				CompRJW.Comp(pawn).quirksave = CompRJW.Comp(pawn).quirks.ToString();
				quirk.DoAfterAdd(pawn);
			}
		}

		public static void Clear(Pawn pawn)
		{
			Hediff fertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("IncreasedFertility"));
			if (fertility != null)
				pawn.health.RemoveHediff(fertility);
			Hediff infertility = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("DecreasedFertility"));
			if (infertility != null)
				pawn.health.RemoveHediff(infertility);

			CompRJW.Comp(pawn).quirks = new StringBuilder();

			if (CompRJW.Comp(pawn).quirks.Length == 0)
				CompRJW.Comp(pawn).quirks.Append("None");

			CompRJW.Comp(pawn).quirksave = CompRJW.Comp(pawn).quirks.ToString();
		}

		public static void Generate(Pawn pawn)
		{
			if (!pawn.RaceHasSexNeed())
			{
				return;
			}
			else if (pawn.IsAnimal())
			{
				GenerateForAnimal(pawn);
			}
			else
			{
				GenerateForHumanlike(pawn);
			}
		}

		[SyncMethod]
		static void GenerateForHumanlike(Pawn pawn)
		{
			var count = Rand.RangeInclusive(0, RJWPreferenceSettings.MaxQuirks);
			var list = Quirk.All.ToList();
			list.Shuffle();

			// Some quirks may be hard for a given pawn to indulge in.
			// For example a female homosexual will have a hard time satisfying an impregnation fetish.
			// But rimworld is a weird place and you never know what the pawn will be capable of in the future.
			// We still don't want straight up contradictory results like fertile + infertile.
			var hasFertility = pawn.RaceHasFertility();
			var actual = new List<Quirk>();
			foreach (var quirk in list)
			{
				if (count == 0)
				{
					break;
				}

				// These special cases are sort of hacked in.
				// In theory there should be a general way for the quirk itself to decide when it applies.
				if (quirk == Quirk.Fertile && (!hasFertility || actual.Contains(Quirk.Infertile)))
				{
					continue;
				}
				if (quirk == Quirk.Infertile && (!hasFertility || actual.Contains(Quirk.Fertile)))
				{
					continue;
				}
				// Have to earn these.
				if (quirk == Quirk.Breeder || quirk == Quirk.Incubator)
				{
					continue;
				}
				// No fair having a fetish for your own race.
				// But tags don't conflict so having a fetish for robot plant dragons is fine.
				if (quirk.RaceTag != null && pawn.Has(quirk.RaceTag))
				{
					continue;
				}

				count--;
				actual.Add(quirk);
			}

			foreach (var quirk in actual)
			{
				pawn.Add(quirk);
			}
		}

		[SyncMethod]
		static void GenerateForAnimal(Pawn pawn)
		{
			if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Messy);
			}

			if (!pawn.RaceHasFertility())
			{
				return;
			}

			if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Fertile);
			}
			else if (Rand.Chance(0.1f))
			{
				pawn.Add(Quirk.Infertile);
			}
		}
	}
}


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

namespace rjw
{
	public class SexProps
	{
		public Pawn Pawn { get; }
		public Pawn Partner { get; }
		public xxx.rjwSextype SexType { get; }
		public bool Violent { get; }

		public bool HasPartner => Partner != null;

		public SexProps(Pawn pawn, Pawn partner, xxx.rjwSextype sexType, bool violent)
		{
			Pawn = pawn;
			Partner = partner;
			SexType = sexType;
			Violent = violent;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	[DefOf]
	public static class RJW_RecipeDefOf
	{
		public static RecipeDef AttachPenis;
		public static RecipeDef InstallPenis;
		public static RecipeDef RemovePenis;
	}
}


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

namespace rjw
{
	public static class PawnDesignations_Breedee
	{
		public static bool UpdateCanDesignateBreeding(this Pawn pawn)
		{
			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = false;

			//cant have penetrative sex
			if (!xxx.can_be_fucked(pawn))
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = false;

			if (RJWSettings.bestiality_enabled && xxx.is_human(pawn))
			{
				if (!pawn.IsDesignatedHero())
				{
					if ((xxx.is_zoophile(pawn) || RJWSettings.override_RJW_designation_checks) && pawn.IsColonist)
						return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = true;
				}
				else if (pawn.IsHeroOwner())
					return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = true;

				if (pawn.IsPrisonerOfColony || xxx.is_slave(pawn))
					return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = true;
			}

			if (RJWSettings.animal_on_animal_enabled && xxx.is_animal(pawn)
				&& pawn.Faction == Faction.OfPlayer)
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = true;

			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding = false;
		}
		public static bool CanDesignateBreeding(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreeding;
		}
		public static void ToggleBreeding(this Pawn pawn)
		{
			pawn.UpdateCanDesignateBreeding();
			if (pawn.CanDesignateBreeding())
			{
				if (!pawn.IsDesignatedBreeding())
					DesignateBreeding(pawn);
				else
					UnDesignateBreeding(pawn);
			}
		}
		public static bool IsDesignatedBreeding(this Pawn pawn)
		{
			if (SaveStorage.DataStore.GetPawnData(pawn).Breeding)
			{
				if (!xxx.is_animal(pawn))
				{
					if (!RJWSettings.bestiality_enabled)
						UnDesignateBreeding(pawn);

					else if (!pawn.IsDesignatedHero())
						if (!(xxx.is_zoophile(pawn) || pawn.IsPrisonerOfColony || xxx.is_slave(pawn)))
							if (!(RJWSettings.WildMode || RJWSettings.override_RJW_designation_checks))
								UnDesignateBreeding(pawn);
				}
				else
				{
					if (!RJWSettings.animal_on_animal_enabled)
						UnDesignateBreeding(pawn);

					else if (!pawn.Faction.IsPlayer)
						UnDesignateBreeding(pawn);
				}
				if (pawn.Dead)
					pawn.UnDesignateBreeding();
			}

			return SaveStorage.DataStore.GetPawnData(pawn).Breeding;
		}
		[SyncMethod]
		public static void DesignateBreeding(this Pawn pawn)
		{
			DesignatorsData.rjwBreeding.AddDistinct(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Breeding = true;
		}
		[SyncMethod]
		public static void UnDesignateBreeding(this Pawn pawn)
		{
			DesignatorsData.rjwBreeding.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Breeding = false;
		}
	}
}


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

namespace rjw
{
	public static class PawnDesignations_Breeder
	{
		public static bool UpdateCanDesignateBreedingAnimal(this Pawn pawn)
		{
			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreedingAnimal = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreedingAnimal = false;

			//Log.Message("CanDesignateAnimal for " + xxx.get_pawnname(pawn) + " " + SaveStorage.bestiality_enabled);
			//Log.Message("checking animal props " + (pawn.Faction?.IsPlayer.ToString()?? "tynanfag") + xxx.is_animal(pawn) + xxx.can_rape(pawn));
			if ((RJWSettings.bestiality_enabled || RJWSettings.animal_on_animal_enabled)
				&& xxx.is_animal(pawn)
				&& xxx.can_fuck(pawn)
				&& pawn.Faction == Faction.OfPlayer)
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreedingAnimal = true;

			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreedingAnimal = false;
		}
		public static bool CanDesignateBreedingAnimal(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateBreedingAnimal;
		}
		public static void ToggleBreedingAnimal(this Pawn pawn)
		{
			pawn.UpdateCanDesignateBreedingAnimal();
			if (pawn.CanDesignateBreedingAnimal())
			{
				if (!pawn.IsDesignatedBreedingAnimal())
					DesignateBreedingAnimal(pawn);
				else
					UnDesignateBreedingAnimal(pawn);
			}
		}
		public static bool IsDesignatedBreedingAnimal(this Pawn pawn)
		{
			if (SaveStorage.DataStore.GetPawnData(pawn).BreedingAnimal)
			{
				if (!pawn.Faction.IsPlayer)
					UnDesignateBreedingAnimal(pawn);

				if (pawn.Dead)
					pawn.UnDesignateBreedingAnimal();
			}

			return SaveStorage.DataStore.GetPawnData(pawn).BreedingAnimal;
		}
		[SyncMethod]
		public static void DesignateBreedingAnimal(this Pawn pawn)
		{
			DesignatorsData.rjwBreedingAnimal.AddDistinct(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).BreedingAnimal = true;
		}
		[SyncMethod]
		public static void UnDesignateBreedingAnimal(this Pawn pawn)
		{
			DesignatorsData.rjwBreedingAnimal.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).BreedingAnimal = false;
		}
	}
}


code:
using Verse;
using Multiplayer.API;

namespace rjw
{
	public static class PawnDesignations_Comfort
	{
		public static bool UpdateCanDesignateComfort(this Pawn pawn)
		{
			//rape disabled
			if (!RJWSettings.rape_enabled)
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = false;

			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = false;

			//cant sex
			if (!(xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)))
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = false;

			if (!pawn.IsDesignatedHero())
			{
				if ((xxx.is_masochist(pawn) || RJWSettings.override_RJW_designation_checks) && pawn.IsColonist)
					return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = true;
			}
			else if (pawn.IsHeroOwner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = true;

			if (pawn.IsPrisonerOfColony || xxx.is_slave(pawn))
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = true;

			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort = false;
		}
		public static bool CanDesignateComfort(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateComfort;
		}
		public static void ToggleComfort(this Pawn pawn)
		{
			pawn.UpdateCanDesignateComfort();
			if (pawn.CanDesignateComfort())
			{
				if (!pawn.IsDesignatedComfort())
					DesignateComfort(pawn);
				else
					UnDesignateComfort(pawn);
			}
		}
		public static bool IsDesignatedComfort(this Pawn pawn)
		{
			if (SaveStorage.DataStore.GetPawnData(pawn).Comfort)
			{
				if (!pawn.IsDesignatedHero())
				{
					if (!pawn.IsPrisonerOfColony)
						if (!(xxx.is_masochist(pawn) || xxx.is_slave(pawn)))
						{
							if (!pawn.IsColonist)
								UnDesignateComfort(pawn);
							else if (!(RJWSettings.WildMode || RJWSettings.override_RJW_designation_checks))
								UnDesignateComfort(pawn);
						}
				}

				if (pawn.Dead)
					pawn.UnDesignateComfort();
			}

			return SaveStorage.DataStore.GetPawnData(pawn).Comfort;
		}
		[SyncMethod]
		public static void DesignateComfort(this Pawn pawn)
		{
			DesignatorsData.rjwComfort.AddDistinct(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Comfort = true;
		}
		[SyncMethod]
		public static void UnDesignateComfort(this Pawn pawn)
		{
			DesignatorsData.rjwComfort.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Comfort = false;
		}
	}
}


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

namespace rjw
{
	public static class PawnDesignations_Hero
	{
		public static bool UpdateCanDesignateHero(this Pawn pawn)
		{
			if ((RJWSettings.RPG_hero_control)
				&& xxx.is_human(pawn)
				&& pawn.IsColonist)
			{
				if (!pawn.IsDesignatedHero())
				{
					foreach (Pawn item in DesignatorsData.rjwHero)
					{
						if (item.IsHeroOwner())
						{
							if (RJWSettings.RPG_hero_control_Ironman && !SaveStorage.DataStore.GetPawnData(item).Ironman)
								SetHeroIronman(item);
							if (item.Dead && !SaveStorage.DataStore.GetPawnData(item).Ironman)
							{
								UnDesignateHero(item);
								//Log.Warning("CanDesignateHero:: "  + MP.PlayerName + " hero is dead remove hero tag from " + item.Name);
							}
							else
							{
								//Log.Warning("CanDesignateHero:: "  + MP.PlayerName + " already has hero - " + item.Name);
								return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateHero = false;
							}

						}
						else
							continue;
					}
					return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateHero = true;
				}
			}
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateHero = false;
		}
		public static bool CanDesignateHero(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateHero;
		}
		public static void ToggleHero(this Pawn pawn)
		{
			pawn.UpdateCanDesignateHero();
			if (pawn.CanDesignateHero() && Find.Selector.NumSelected == 1)
			{
				if (!pawn.IsDesignatedHero())
					DesignateHero(pawn);
			}
		}
		public static bool IsDesignatedHero(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).Hero;
		}
		public static void DesignateHero(this Pawn pawn)
		{
			MyMethod(pawn, MP.PlayerName);
		}
		[SyncMethod]
		public static void UnDesignateHero(this Pawn pawn)
		{
			DesignatorsData.rjwHero.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Hero = false;
		}
		public static bool IsHeroOwner(this Pawn pawn)
		{
			if (!MP.enabled)
				return SaveStorage.DataStore.GetPawnData(pawn).HeroOwner == "Player" || SaveStorage.DataStore.GetPawnData(pawn).HeroOwner == null || SaveStorage.DataStore.GetPawnData(pawn).HeroOwner == "";
			else
				return SaveStorage.DataStore.GetPawnData(pawn).HeroOwner == MP.PlayerName;
		}
		[SyncMethod]
		static void MyMethod(Pawn pawn, string theName)
		{
			if (!MP.enabled)
				theName = "Player";
			SaveStorage.DataStore.GetPawnData(pawn).Hero = true;
			SaveStorage.DataStore.GetPawnData(pawn).HeroOwner = theName;
			SaveStorage.DataStore.GetPawnData(pawn).Ironman = RJWSettings.RPG_hero_control_Ironman;
			DesignatorsData.rjwHero.AddDistinct(pawn);
			string text = pawn.Name + " is now hero of " + theName;
			Messages.Message(text, pawn, MessageTypeDefOf.NeutralEvent);
			//Log.Message(MP.PlayerName + "  set " + pawn.Name + " to hero:" + SaveStorage.DataStore.GetPawnData(pawn).Hero);

			pawn.UpdateCanChangeDesignationPrisoner();
			pawn.UpdateCanChangeDesignationColonist();
			pawn.UpdateCanDesignateService();
			pawn.UpdateCanDesignateComfort();
			pawn.UpdateCanDesignateBreedingAnimal();
			pawn.UpdateCanDesignateBreeding();
			pawn.UpdateCanDesignateHero();
		}
		[SyncMethod]
		public static void SetHeroIronman(this Pawn pawn)
		{
			SaveStorage.DataStore.GetPawnData(pawn).Ironman = true;
		}
	}
}


code:
using Verse;
using Multiplayer.API;

namespace rjw
{
	public static class PawnDesignations_Milking
	{
		public static bool UpdateCanDesignateMilking(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateMilking = false;
		}
		public static bool CanDesignateMilking(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateMilking = false;
		}
		public static void ToggleMilking(this Pawn pawn)
		{
			if (pawn.CanDesignateMilking())
			{
				if (!pawn.IsDesignatedMilking())
					DesignateMilking(pawn);
				else
					UnDesignateMilking(pawn);
			}
		}
		public static bool IsDesignatedMilking(this Pawn pawn)
		{
			if (SaveStorage.DataStore.GetPawnData(pawn).Milking)
			{
				if (!pawn.IsDesignatedHero())
					if (!(pawn.IsColonist || pawn.IsPrisonerOfColony || xxx.is_slave(pawn)))
						UnDesignateMilking(pawn);

				if (pawn.Dead)
					pawn.UnDesignateMilking();
			}

			return SaveStorage.DataStore.GetPawnData(pawn).Milking;
		}
		[SyncMethod]
		public static void DesignateMilking(this Pawn pawn)
		{
			DesignatorsData.rjwMilking.AddDistinct(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Milking = true;
		}
		[SyncMethod]
		public static void UnDesignateMilking(this Pawn pawn)
		{
			DesignatorsData.rjwMilking.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Milking = false;
		}
	}
}


code:
using Verse;
using Multiplayer.API;

namespace rjw
{
	public static class PawnDesignations_Service
	{
		public static bool UpdateCanDesignateService(this Pawn pawn)
		{
			//no permission to change designation for NON prisoner hero/ other player
			if (!pawn.CanChangeDesignationPrisoner() && !pawn.CanChangeDesignationColonist())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = false;

			//no permission to change designation for prisoner hero/ self
			if (!pawn.CanChangeDesignationPrisoner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = false;

			//cant sex
			if (!(xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)))
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = false;

			if (!pawn.IsDesignatedHero())
			{
				if (pawn.IsColonist)
					return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = true;
			}
			else if (pawn.IsHeroOwner())
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = true;

			if (pawn.IsPrisonerOfColony || xxx.is_slave(pawn))
				return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = true;

			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService = false;
		}
		public static bool CanDesignateService(this Pawn pawn)
		{
			return SaveStorage.DataStore.GetPawnData(pawn).CanDesignateService;
		}
		public static void ToggleService(this Pawn pawn)
		{
			pawn.UpdateCanDesignateService();
			if (pawn.CanDesignateService())
			{
				if (!pawn.IsDesignatedService())
					DesignateService(pawn);
				else
					UnDesignateService(pawn);
			}
		}
		public static bool IsDesignatedService(this Pawn pawn)
		{
			if (SaveStorage.DataStore.GetPawnData(pawn).Service)
			{
				if (!pawn.IsDesignatedHero())
					if (!(pawn.IsColonist || pawn.IsPrisonerOfColony || xxx.is_slave(pawn)))
						UnDesignateService(pawn);

				if (pawn.Dead)
					pawn.UnDesignateService();
			}

			return SaveStorage.DataStore.GetPawnData(pawn).Service;
		}
		[SyncMethod]
		public static void DesignateService(this Pawn pawn)
		{
			DesignatorsData.rjwService.AddDistinct(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Service = true;
		}
		[SyncMethod]
		public static void UnDesignateService(this Pawn pawn)
		{
			DesignatorsData.rjwService.Remove(pawn);
			SaveStorage.DataStore.GetPawnData(pawn).Service = false;
		}
	}
}