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

namespace rjw
{
	//public static class bondage_gear_tradeability
	//{
	//	public static void init()
	//	{
	//		// Allows bondage gear to be selled by traders
	//		if (xxx.config.bondage_gear_enabled)
	//		{
	//			foreach (var def in DefDatabase<bondage_gear_def>.AllDefs)
	//				def.tradeability = Tradeability.Sellable;
	//		}
	//		// Forbids bondage gear to be selled by traders
	//		else
	//		{
	//			foreach (var def in DefDatabase<bondage_gear_def>.AllDefs)
	//				def.tradeability = Tradeability.None;
	//		}
	//	}
	//}

	public static class bondage_gear_extensions
	{
		public static bool has_lock(this Apparel app)
		{
			return (app.TryGetComp<CompHoloCryptoStamped>() != null);
		}

		public static bool is_wearing_locked_apparel(this Pawn p)
		{
			if (p.apparel != null)
				foreach (var app in p.apparel.WornApparel)
					if (app.has_lock())
						return true;
			return false;
		}

		// Tries to get p started on the job of using an item on either another pawn or on themself (if "other" is null).
		// Of course in order for this method to work, the item's useJob has to be able to handle use on another pawn. This
		// is true for the holokey and bondage gear in RJW but not the items in the core game
		public static void start_job(this CompUsable usa, Pawn p, LocalTargetInfo tar)
		{
			if (p.CanReserveAndReach(usa.parent, PathEndMode.Touch, Danger.Some) &&
				((tar == null) || p.CanReserveAndReach(tar, PathEndMode.Touch, Danger.Some)))
			{
				var comfor = usa.parent.GetComp<CompForbiddable>();
				if (comfor != null)
					comfor.Forbidden = false;
				var job = JobMaker.MakeJob(((CompProperties_Usable)usa.props).useJob, usa.parent, tar);
				p.jobs.TryTakeOrderedJob(job);
			}
		}

		// Creates a menu option to use an item. "tar" is expected to be a pawn, corpse or null if it doesn't apply (in which
		// case the pawn will presumably use the item on themself). "required_work" can also be null.
		public static FloatMenuOption make_option(this CompUsable usa, string label, Pawn p, LocalTargetInfo tar, WorkTypeDef required_work)
		{
			if ((tar != null) && (!p.CanReserve(tar)))
			{
				string key = "Reserved";
				string text = TranslatorFormattedStringExtensions.Translate(key);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else if ((tar != null) && (!p.CanReach(tar, PathEndMode.Touch, Danger.Some)))
			{
				string key = "NoPath";
				string text = TranslatorFormattedStringExtensions.Translate(key);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else if ((required_work != null) && p.WorkTagIsDisabled(required_work.workTags))
			{
				string key = "CannotPrioritizeWorkTypeDisabled";
				string text = TranslatorFormattedStringExtensions.Translate(key, required_work.gerundLabel);
				return new FloatMenuOption(label + " (" + text + ")", null, MenuOptionPriority.DisabledOption);
			}
			else
				return new FloatMenuOption(
					label,
					delegate
					{
						usa.start_job(p, tar);
					},
					MenuOptionPriority.Default);
		}
	}

	public class bondage_gear_def : ThingDef
	{
		public Type soul_type;
		public HediffDef equipped_hediff = null;
		public bool gives_bound_moodlet = false;
		public bool gives_gagged_moodlet = false;
		public bool blocks_hands = false;
		public bool blocks_oral = false;
		public bool blocks_penis = false;
		public bool blocks_vagina = false;
		public bool blocks_anus = false;
		public bool blocks_breasts = false;
		private bondage_gear_soul soul_ins = null;

		public List<BodyPartDef> HediffTargetBodyPartDefs;      //field for optional list of targeted parts for hediff applying
		public List<BodyPartGroupDef> BoundBodyPartGroupDefs;   //field for optional list of groups restrained of verbcasting

		public bondage_gear_soul soul
		{
			get
			{
				if (soul_ins == null)
					soul_ins = (bondage_gear_soul)Activator.CreateInstance(soul_type);
				return soul_ins;
			}
		}
	}

	public class bondage_gear_soul
	{
		// Adds the bondage gear's associated HediffDef and spawns a matching holokey
		public virtual void on_wear(Pawn wearer, Apparel gear)
		{
			var def = (bondage_gear_def)gear.def;

			if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs != null)
			{
				foreach (BodyPartDef partDef in def.HediffTargetBodyPartDefs)                           //getting BodyPartDef, for example "Arm"
				{
					foreach (BodyPartRecord partRec in wearer.RaceProps.body.GetPartsWithDef(partDef))  //applying hediff to every single arm found on pawn
					{
						wearer.health.AddHediff(def.equipped_hediff, partRec);
					}
				}
			}
			else if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs == null)               //backward compatibility/simplified gear define without HediffTargetBodyPartDefs
			{                                                                                //Hediff applyed to whole body
				wearer.health.AddHediff(def.equipped_hediff);
			}

			var gear_stamp = gear.TryGetComp<CompHoloCryptoStamped>();
			if (gear_stamp != null)
			{
				var key = ThingMaker.MakeThing(ThingDef.Named("Holokey"));
				var key_stamp = key.TryGetComp<CompHoloCryptoStamped>();
				key_stamp.copy_stamp_from(gear_stamp);
				if (wearer.Map != null)
					GenSpawn.Spawn(key, wearer.Position, wearer.Map);
				else
					wearer.inventory.TryAddItemNotForSale(key);
			}
		}

		// Removes the gear's HediffDef
		public virtual void on_remove(Apparel gear, Pawn former_wearer)
		{
			var def = (bondage_gear_def)gear.def;
			if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs != null)
			{                                                                                //getting all Hediffs according with equipped_hediff def
				List<Hediff> hediffs = former_wearer.health.hediffSet.hediffs.Where(x => x.def == def.equipped_hediff).ToList();
				foreach (Hediff hedToRemove in hediffs)
				{
					if (def.HediffTargetBodyPartDefs.Contains(hedToRemove.Part.def))            //removing if applyed by this bondage_gear
						former_wearer.health.RemoveHediff(hedToRemove);                         //assuming there can be several different bondages
				}                                                                               //with the same equipped_hediff def
			}
			else if (def.equipped_hediff != null && def.HediffTargetBodyPartDefs == null)       //backward compatibility/simplified gear define without HediffTargetBodyPartDefs
			{
				var hed = former_wearer.health.hediffSet.GetFirstHediffOfDef(def.equipped_hediff);
				if (hed != null)
					former_wearer.health.RemoveHediff(hed);
			}
		}
	}

	// Give bondage gear an extremely low score when it's not being worn so pawns never equip it on themselves and give
	// it an extremely high score when it is being worn so pawns never try to take it off to equip something "better".
	public class bondage_gear : Apparel
	{
		// made this method universal for any bondage_gear, won't affect anything if gear's BoundBodyPartGroupDefs is empty or null
		public override bool AllowVerbCast(Verb verb)
		{
			if ((this.def as bondage_gear_def).BoundBodyPartGroupDefs != null &&
				verb.tool != null &&
				(this.def as bondage_gear_def).BoundBodyPartGroupDefs.Contains(verb.tool.linkedBodyPartsGroup))
			{
				return false;
			}
			return true;
		}

		//needed for save compatibility only
		public override void ExposeData()
		{
			base.ExposeData();
			//if (Scribe.mode == LoadSaveMode.PostLoadInit)
			//	CheckHediffs();
		}

		//save compatibility insurance, will prevent Armbinder hediff with 0part efficiency on whole body
		private void CheckHediffs()
		{
			var def = (bondage_gear_def)this.def;
			if (this.Wearer == null || def.equipped_hediff == null) return;

			bool changedHediff = false;
			void ApplyHediffDirect(HediffDef hedDef, BodyPartRecord partRec)
			{
				Hediff hediff = (Hediff)HediffMaker.MakeHediff(hedDef, Wearer);
				if (partRec != null)
					hediff.Part = partRec;
				Wearer.health.hediffSet.AddDirect(hediff);
				changedHediff = true;
			}

			void RemoveHediffDirect(Hediff hed)
			{
				Wearer.health.hediffSet.hediffs.Remove(hed);
				changedHediff = true;
			}

			List<bondage_gear> wornBondageGear = Wearer.apparel.WornApparel.Where(x => x is bondage_gear).Cast<bondage_gear>().ToList();
			List<Hediff> hediffs = new List<Hediff>();
			foreach (Hediff h in this.Wearer.health.hediffSet.hediffs) hediffs.Add(h);

			//checking current hediffs defined by bondage_gear for being on defined place, cleaning up the misplaced
			bool equippedHediff;
			bool onPlace;
			foreach (Hediff hed in hediffs)
			{
				equippedHediff = false;
				onPlace = false;
				foreach (bondage_gear gear in wornBondageGear)
				{
					if (hed.def == (gear.def as bondage_gear_def).equipped_hediff)
					{
						//if hediff from bondage_gear and on it's defined place then don't touch it else remove
						//assuming there can be several different bondages with the same equipped_hediff def and different hediff target parts, don't know why
						equippedHediff = true;

						if ((hed.Part != null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null))
						{
							//pass
						}
						else if ((hed.Part == null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null))
						{
							onPlace = true;
							break;
						}
						else if (hed.Part != null && (gear.def as bondage_gear_def).HediffTargetBodyPartDefs.Contains(hed.Part.def))
						{
							onPlace = true;
							break;
						}
					}
				}
				if (equippedHediff && !onPlace)
				{
					ModLog.Message("Removing Hediff " + hed.Label + " from " + Wearer + (hed.Part == null ? "'s body" : "'s " + hed.Part));
					RemoveHediffDirect(hed);
				}
			}

			// now iterating every gear for having all hediffs in place, adding missing
			foreach (bondage_gear gear in wornBondageGear)
			{
				if ((gear.def as bondage_gear_def).equipped_hediff == null) continue;
				if ((gear.def as bondage_gear_def).HediffTargetBodyPartDefs == null)                        //handling gear without HediffTargetBodyPartDefs
				{

					Hediff hed = Wearer.health.hediffSet.hediffs.Find(x => (x.def == (gear.def as bondage_gear_def).equipped_hediff && x.Part == null));//checking hediff defined by gear on whole body
					if (hed == null)                                                                        //if no legit hediff, adding
					{
						ModLog.Message("Adding missing Hediff " + (gear.def as bondage_gear_def).equipped_hediff.label + " to " + Wearer + "'s body");
						ApplyHediffDirect((gear.def as bondage_gear_def).equipped_hediff, null);
					}
				}
				else                                                                                //handling gear with defined HediffTargetBodyPartDefs
				{
					foreach (BodyPartDef partDef in (gear.def as bondage_gear_def).HediffTargetBodyPartDefs)    //getting every partDef
					{
						foreach (BodyPartRecord partRec in Wearer.RaceProps.body.GetPartsWithDef(partDef))      //checking all parts of def for applyed hediff
						{
							Hediff hed = Wearer.health.hediffSet.hediffs.Find(x => (x.def == (gear.def as bondage_gear_def).equipped_hediff && x.Part == partRec));//checking hediff defined by gear on defined place
							if (hed == null)                                                                    //if hediff missing, adding
							{
								ModLog.Message("Adding missing Hediff " + (gear.def as bondage_gear_def).equipped_hediff.label + " to " + Wearer + "'s " + partRec.Label);
								ApplyHediffDirect((gear.def as bondage_gear_def).equipped_hediff, partRec);
							}
						}
					}
				}
				//possibility of several different items with THE SAME equipped_hediff def ON THE SAME PART is not considered, that's sick
			}
			if (changedHediff)
			{
				//Possible error in current toil on Notify_HediffChanged() if capacity.Manipulation is involved so making it in the end.
				//Probably harmless, shouldn't happen again after corrected hediffs will be saved
				Wearer.health.Notify_HediffChanged(null);
			}
		}
	}

	public class armbinder : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class yoke : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class Restraints : bondage_gear
	{
		// Prevents pawns in armbinders from melee attacking
		//public override bool AllowVerbCast (IntVec3 root, TargetInfo targ)
		//{
		//	return false;
		//}
	}

	public class force_off_gear_def : RecipeDef
	{
		public ThingDef removes_apparel;
		public BodyPartDef failure_affects;
		public List<BodyPartDef> destroys_one_of = null;
		public List<BodyPartDef> major_burns_on = null;
		public List<BodyPartDef> minor_burns_on = null;
	}
}


code:
namespace rjw.Modules.Genitals.Enums
{
	public enum Gender
	{
		Male,
		Female,
		Trap,
		Futa,

		MaleOvi,
		FemaleOvi,

		Unknown
	}
}

repo_name: aelina/rjw

license: mit


code:
namespace rjw.Modules.Genitals.Enums
{
	public enum LewdablePartKind
	{
		Unsupported,

		Penis,
		Vagina,
		Anus,
		FemaleOvipositor,
		MaleOvipositor,
		Breasts,
		Udders,	// Unsupported

		Hand,
		Foot,
		Tail,
		Mouth,
		Beak,
		Tongue
	}
}


code:
using rjw.Modules.Genitals.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System.Linq;

namespace rjw.Modules.Genitals.Helpers
{
	public class GenderHelper
	{
		private static ILog _log = LogManager.GetLogger<GenderHelper>();

		public static Gender GetGender(InteractionPawn pawn)
		{
			bool hasVagina = pawn.Parts.Vaginas.Any();
			bool hasPenis = pawn.Parts.Penises.Any();
			bool hasOviFemale = pawn.Parts.FemaleOvipositors.Any();
			bool hasOviMale = pawn.Parts.MaleOvipositors.Any();
			bool hasBreasts = pawn.HasBigBreasts();

			Gender result = Gender.Unknown;

			if (hasVagina && !hasPenis)
			{
				result = Gender.Female;
			}
			else
			if (hasPenis && hasVagina)
			{
				result = Gender.Futa;
			}
			else
			if (hasPenis && hasBreasts)
			{
				result = Gender.Trap;
			}
			else
			if (hasPenis)
			{
				result = Gender.Male;
			}
			else
			if (hasOviMale)
			{
				result = Gender.MaleOvi;
			}
			else
			if (hasOviFemale)
			{
				result = Gender.FemaleOvi;
			}
			else
			if (pawn.Pawn.gender == Verse.Gender.Male)
			{
				result = Gender.Male;
			}
			else
			if (pawn.Pawn.gender == Verse.Gender.Female)
			{
				result = Gender.Female;
			}

			_log.Debug($"{pawn.Pawn.GetName()} detected as {result}");

			return result;
		}
	}
}


code:
namespace rjw.Modules.Interactions.Contexts
{
	public class InteractionContext
	{
		public InteractionInputs Inputs { get; set; }
		public InteractionInternals Internals { get; set; }
		public InteractionOutputs Outputs { get; set; }

		public InteractionContext() { }
		public InteractionContext(InteractionInputs inputs)
		{
			Inputs = inputs;
			Internals = new InteractionInternals();
			Outputs = new InteractionOutputs();
		}
	}
}


code:
using Verse;

namespace rjw.Modules.Interactions.Contexts
{
	public class InteractionInputs
	{
		public Pawn Initiator { get; set; }
		public Pawn Partner { get; set; }

		public bool IsRape { get; set; }
		public bool IsWhoring { get; set; }
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;

namespace rjw.Modules.Interactions.Contexts
{
	/// <summary>
	/// All the stuff stored in there shouldn't be used by other modules ... hence the "internal"
	/// </summary>
	public class InteractionInternals
	{
		public InteractionPawn Dominant { get; set; }
		public InteractionPawn Submissive { get; set; }

		public InteractionType InteractionType { get; set; }

		public bool IsReverse { get; set; }

		public InteractionWithExtension Selected { get; set; }

		public InteractionInternals()
		{
		}
	}
}


code:
using rjw.Modules.Interactions.Objects;

namespace rjw.Modules.Interactions.Contexts
{
	public class InteractionOutputs
	{
		public Interaction Generated { get; set; }

		public InteractionOutputs()
		{
			Generated = new Interaction();
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw.Modules.Interactions.Contexts
{
	public class SpecificInteractionInputs
	{
		public Pawn Initiator { get; set; }
		public Pawn Partner { get; set; }

		public InteractionDef Interaction { get; set; }
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Defs.DefFragment;
using rjw.Modules.Interactions.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;

namespace rjw.Modules.Interactions.DefModExtensions
{
	public class InteractionSelectorExtension : DefModExtension
	{
		public List<RulePackDef> rulepacks = new List<RulePackDef>();
		public List<InteractionTag> tags = new List<InteractionTag>();
		public InteractionRequirement dominantRequirement;
		public InteractionRequirement submissiveRequirement;
		public string customRequirementHandler;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Genitals.Enums;
using rjw.Modules.Shared.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Defs.DefFragment
{
	public class InteractionRequirement
	{
		//Default = false
		public bool hand = false;
		public bool tail = false;
		public bool foot = false;
		public bool mouth = false;
		public bool beak = false;
		public bool mouthORbeak = false;
		public bool oral = false;
		public bool tongue = false;

		///Default = 1
		public Nullable<int> minimumCount = 1;

		///Default = 1
		///@ed86 - nullables are good for you ! don't use default values :b
		public Nullable<float> minimumSeverity;

		//Default = empty
		public List<string> partProps = new List<string>();

		//Default = empty
		public List<GenitalTag> tags = new List<GenitalTag>();

		//Default = empty
		public List<GenitalFamily> families = new List<GenitalFamily>();

		//Default = PawnState.Healthy
		public List<PawnState> pawnStates = new List<PawnState>();

		//overrides - edging, spanking?
		//public bool canOrgasm = true; // true - orgasm satisfy need, false - ruin orgasm/ dont satisfy need
		//public bool canCum = true; // orgasm with cum effects -semen, pregnancy etc
		//public float Sensetivity = 1.0f;
	}
}


code:
using RimWorld;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Interactions.Defs
{
	public class InteractionDefOf
	{
		public static IList<InteractionWithExtension> Interactions { get; set; }

		public static InteractionWithExtension DefaultConsensualSex => Interactions
			.Where(e => e.Interaction.defName == "Sex_Vaginal")
			.FirstOrDefault();
		public static InteractionWithExtension DefaultWhoringSex => Interactions
			.Where(e => e.Interaction.defName == "Sex_Vaginal")
			.FirstOrDefault();

		public static InteractionWithExtension DefaultRapeSex => Interactions
			.Where(e => e.Interaction.defName == "Rape_Vaginal")
			.FirstOrDefault();

		public static InteractionWithExtension DefaultMechImplantSex => Interactions
			.Where(e => e.Interaction.defName == "Rape_MechImplant")
			.FirstOrDefault();

		public static InteractionWithExtension DefaultNecrophiliaSex => Interactions
			.Where(e => e.Interaction.defName == "Rape_Vaginal")
			.FirstOrDefault();

		public static InteractionWithExtension DefaultBestialitySex => Interactions
			.Where(e => e.Interaction.defName == "Bestiality_Vaginal")
			.FirstOrDefault();

		public static InteractionWithExtension DefaultAnimalSex => Interactions
			.Where(e => e.Interaction.defName == "Bestiality_Vaginal")
			.FirstOrDefault();

		static InteractionDefOf()
		{
			Interactions = DefDatabase<InteractionDef>.AllDefs
				.Where(def => def.HasModExtension<InteractionSelectorExtension>())
				.Where(def => def.HasModExtension<InteractionExtension>())
				.Select(def => new InteractionWithExtension
				{
					Interaction = def,
					Extension = def.GetModExtension<InteractionExtension>(),
					SelectorExtension = def.GetModExtension<InteractionSelectorExtension>()
				})
				.Where(defWithExtensions => ParseHelper.FromString<xxx.rjwSextype>(defWithExtensions.Extension.rjwSextype) != xxx.rjwSextype.None)
				.ToList();
		}
	}
}


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

namespace rjw.Modules.Interactions.Enums
{
	public enum InteractionTag
	{
		Reverse,

		Fertilization,
		EggFertilization,
		EggLaying,

		Masturbation,
		Rape,
		Whoring,
		Consensual,
		Bestiality,
		Necrophilia,
		Animal,
		MechImplant
	}
}


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

namespace rjw.Modules.Interactions.Enums
{
	public enum InteractionType
	{
		Masturbation,
		Consensual,
		Whoring,
		Rape,
		Bestiality,
		Animal,
		Necrophilia,
		Mechanoid
	}
}


code:
namespace rjw.Modules.Interactions.Enums
{
	public enum LewdablePartKind
	{
		Unsupported,

		Penis,
		Vagina,
		Anus,
		FemaleOvipositor,
		MaleOvipositor,
		Breasts,
		Udders,	// Unsupported

		Hand,
		Foot,
		Tail,
		Mouth,
		Beak,
		Tongue
	}
}


code:
using KTrie;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class HediffWithExtensionExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<HediffWithExtensionExposable>();

		public Hediff hediff;

		public ISexPartHediff GenitalPart
		{
			get
			{
				return hediff as ISexPartHediff;
			}
		}

		public void ExposeData()
		{
			Scribe_References.Look(ref hediff, nameof(hediff));
		}

		/// <summary>
		/// Dummy method to not break build pending removal of unused submodule
		/// </summary>
		/// <param name="self"></param>
		/// <returns></returns>
		public static ISexPartHediff Convert(HediffWithExtensionExposable self)
		{
			_log.Debug(self.ToString());

			return self.hediff as ISexPartHediff;
		}

		/// <summary>
		/// Dummy method to not break build pending removal of unused submodule
		/// </summary>
		/// <param name="self"></param>
		/// <returns></returns>
		public static HediffWithExtensionExposable Convert(ISexPartHediff self)
		{
			return new HediffWithExtensionExposable()
			{
				hediff = self.AsHediff
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(hediff)} = {hediff?.def.defName}");

			return stringBuilder.ToString();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class InteractionExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<InteractionExposable>();

		private InteractionType interactionType;

		private bool isReverse;

		private InteractionPawnExposable initiator;
		private InteractionPawnExposable receiver;

		private InteractionWithExtensionExposable interactionWithExtension;

		private RulePackDef rulePack;

		private xxx.rjwSextype RjwSexType
		{
			get
			{
				return ParseHelper.FromString<xxx.rjwSextype>(interactionWithExtension.Extension.rjwSextype);
			}
		}

		private List<RJWLewdablePartExposable> selectedDominantParts_RJW;
		private List<VanillaLewdablePartExposable> selectedDominantParts_Vanilla;

		private List<RJWLewdablePartExposable> selectedSubmissiveParts_RJW;
		private List<VanillaLewdablePartExposable> selectedSubmissiveParts_Vanilla;

		public void ExposeData()
		{
			Scribe_Values.Look(ref interactionType, nameof(interactionType));

			Scribe_Values.Look(ref isReverse, nameof(isReverse));

			Scribe_Deep.Look(ref initiator, nameof(initiator));
			Scribe_Deep.Look(ref receiver, nameof(receiver));

			Scribe_Deep.Look(ref interactionWithExtension, nameof(interactionWithExtension));

			Scribe_Defs.Look(ref rulePack, nameof(rulePack));

			Scribe_Collections.Look(ref selectedDominantParts_RJW, nameof(selectedDominantParts_RJW));
			Scribe_Collections.Look(ref selectedDominantParts_Vanilla, nameof(selectedDominantParts_Vanilla));

			Scribe_Collections.Look(ref selectedSubmissiveParts_RJW, nameof(selectedSubmissiveParts_RJW));
			Scribe_Collections.Look(ref selectedSubmissiveParts_Vanilla, nameof(selectedSubmissiveParts_Vanilla));
		}

		public static Interaction Convert(InteractionExposable toCast)
		{
			return new Interaction()
			{
				InteractionType = toCast.interactionType,

				Initiator = InteractionPawnExposable.Convert(toCast.initiator),
				Receiver = InteractionPawnExposable.Convert(toCast.receiver),

				Dominant = InteractionPawnExposable.Convert(toCast.initiator),
				Submissive = InteractionPawnExposable.Convert(toCast.receiver),

				InteractionDef = InteractionWithExtensionExposable.Convert(toCast.interactionWithExtension),

				RjwSexType = toCast.RjwSexType,
				RulePack = toCast.rulePack,

				SelectedDominantParts = Enumerable.Concat(
					toCast.selectedDominantParts_RJW.Select(RJWLewdablePartExposable.Convert).OfType<ILewdablePart>(),
					toCast.selectedDominantParts_Vanilla.Select(VanillaLewdablePartExposable.Convert).OfType<ILewdablePart>()
					).ToList(),

				SelectedSubmissiveParts = Enumerable.Concat(
					toCast.selectedSubmissiveParts_RJW.Select(RJWLewdablePartExposable.Convert).OfType<ILewdablePart>(),
					toCast.selectedSubmissiveParts_Vanilla.Select(VanillaLewdablePartExposable.Convert).OfType<ILewdablePart>()
					).ToList()
			};
		}
		public static InteractionExposable Convert(Interaction toCast)
		{
			return new InteractionExposable()
			{
				interactionType = toCast.InteractionType,

				initiator = InteractionPawnExposable.Convert(toCast.Initiator),
				receiver = InteractionPawnExposable.Convert(toCast.Receiver),

				interactionWithExtension = InteractionWithExtensionExposable.Convert(toCast.InteractionDef),

				rulePack = toCast.RulePack,

				selectedDominantParts_RJW = toCast.SelectedDominantParts
					.OfType<RJWLewdablePart>()
					.Select(RJWLewdablePartExposable.Convert)
					.ToList(),
				selectedDominantParts_Vanilla = toCast.SelectedDominantParts
					.OfType<VanillaLewdablePart>()
					.Select(VanillaLewdablePartExposable.Convert)
					.ToList(),

				selectedSubmissiveParts_RJW = toCast.SelectedDominantParts
					.OfType<RJWLewdablePart>()
					.Select(RJWLewdablePartExposable.Convert)
					.ToList(),
				selectedSubmissiveParts_Vanilla = toCast.SelectedDominantParts
					.OfType<VanillaLewdablePart>()
					.Select(VanillaLewdablePartExposable.Convert)
					.ToList()
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(interactionType)} = {interactionType}");
			stringBuilder.AppendLine($"{nameof(initiator)} = {initiator?.pawn.GetName()}");
			stringBuilder.AppendLine($"{nameof(receiver)} = {receiver?.pawn.GetName()}");

			stringBuilder.AppendLine($"{nameof(interactionWithExtension)} = {interactionWithExtension?.interactionDef.defName}");

			stringBuilder.AppendLine($"{nameof(rulePack)} = {rulePack?.defName}");

			return stringBuilder.ToString();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class InteractionPawnExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<InteractionPawnExposable>();

		public Pawn pawn;

		public List<BodyPartRecord> mouths;
		public List<BodyPartRecord> beaks;
		public List<BodyPartRecord> tongues;
		public List<BodyPartRecord> hands;
		public List<BodyPartRecord> tails;

		public List<HediffWithExtensionExposable> penises;
		public List<HediffWithExtensionExposable> vaginas;
		public List<HediffWithExtensionExposable> breasts;
		public List<HediffWithExtensionExposable> anuses;

		public List<HediffWithExtensionExposable> femaleOvipositors;
		public List<HediffWithExtensionExposable> maleOvipositors;

		public List<LewdablePartKind> blockedParts;
		public Dictionary<LewdablePartKind, float> partPreferences;

		public void ExposeData()
		{
			Scribe_References.Look(ref pawn, nameof(pawn));

			Scribe_Collections.Look(ref beaks, nameof(beaks), LookMode.BodyPart);
			Scribe_Collections.Look(ref mouths, nameof(mouths), LookMode.BodyPart);
			Scribe_Collections.Look(ref tongues, nameof(tongues), LookMode.BodyPart);
			Scribe_Collections.Look(ref hands, nameof(hands), LookMode.BodyPart);
			Scribe_Collections.Look(ref tails, nameof(tails), LookMode.BodyPart);

			Scribe_Collections.Look(ref penises, nameof(penises), LookMode.Deep);
			Scribe_Collections.Look(ref vaginas, nameof(vaginas), LookMode.Deep);
			Scribe_Collections.Look(ref breasts, nameof(breasts), LookMode.Deep);
			Scribe_Collections.Look(ref anuses, nameof(anuses), LookMode.Deep);

			Scribe_Collections.Look(ref femaleOvipositors, nameof(femaleOvipositors), LookMode.Deep);
			Scribe_Collections.Look(ref maleOvipositors, nameof(maleOvipositors), LookMode.Deep);

			Scribe_Collections.Look(ref blockedParts, nameof(blockedParts), LookMode.Value);
			Scribe_Collections.Look(ref partPreferences, nameof(partPreferences), LookMode.Value, LookMode.Value);
		}

		public static InteractionPawn Convert(InteractionPawnExposable toCast)
		{
			return new InteractionPawn()
			{
				Pawn = toCast.pawn,
				Parts = new SexablePawnParts()
				{
					Mouths = toCast.mouths,
					Beaks = toCast.beaks,
					Tongues = toCast.tongues,
					Hands = toCast.hands,
					Tails = toCast.tails,

					Penises = toCast.penises.Select(HediffWithExtensionExposable.Convert).ToList(),
					Vaginas = toCast.vaginas.Select(HediffWithExtensionExposable.Convert).ToList(),
					Breasts = toCast.breasts.Select(HediffWithExtensionExposable.Convert).ToList(),
					Anuses = toCast.anuses.Select(HediffWithExtensionExposable.Convert).ToList(),

					FemaleOvipositors = toCast.femaleOvipositors.Select(HediffWithExtensionExposable.Convert).ToList(),
					MaleOvipositors = toCast.maleOvipositors.Select(HediffWithExtensionExposable.Convert).ToList(),
				},
				BlockedParts = toCast.blockedParts,
				PartPreferences = toCast.partPreferences
			};
		}
		public static InteractionPawnExposable Convert(InteractionPawn toCast)
		{
			return new InteractionPawnExposable()
			{
				pawn = toCast.Pawn,

				mouths = toCast.Parts.Mouths.ToList(),
				beaks = toCast.Parts.Beaks.ToList(),
				tongues = toCast.Parts.Tongues.ToList(),
				hands = toCast.Parts.Hands.ToList(),
				tails = toCast.Parts.Tails.ToList(),

				penises = toCast.Parts.Penises.Select(HediffWithExtensionExposable.Convert).ToList(),
				vaginas = toCast.Parts.Vaginas.Select(HediffWithExtensionExposable.Convert).ToList(),
				breasts = toCast.Parts.Breasts.Select(HediffWithExtensionExposable.Convert).ToList(),
				anuses = toCast.Parts.Anuses.Select(HediffWithExtensionExposable.Convert).ToList(),

				femaleOvipositors = toCast.Parts.FemaleOvipositors.Select(HediffWithExtensionExposable.Convert).ToList(),
				maleOvipositors = toCast.Parts.MaleOvipositors.Select(HediffWithExtensionExposable.Convert).ToList(),

				blockedParts = toCast.BlockedParts.ToList(),
				partPreferences = toCast.PartPreferences.ToDictionary(e => e.Key, e => e.Value)
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(pawn)} = {pawn.GetName()}");

			stringBuilder.AppendLine($"{nameof(blockedParts)} = {String.Join("/", blockedParts)}");
			stringBuilder.AppendLine($"{nameof(partPreferences)} = {String.Join("/", partPreferences.Select(e => $"{e.Key}-{e.Value}"))}");

			return stringBuilder.ToString();
		}
	}
}


code:
using RimWorld;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Logs;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class InteractionWithExtensionExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<InteractionWithExtensionExposable>();

		public InteractionDef interactionDef;

		public InteractionSelectorExtension InteractionSelectorExtension
		{
			get
			{
				return interactionDef.GetModExtension<InteractionSelectorExtension>();
			}
		}
		public InteractionExtension Extension
		{
			get
			{
				return interactionDef.GetModExtension<InteractionExtension>();
			}
		}

		public void ExposeData()
		{
			Scribe_Defs.Look(ref interactionDef, nameof(interactionDef));
		}

		public static InteractionWithExtension Convert(InteractionWithExtensionExposable toCast)
		{
			return new InteractionWithExtension()
			{
				Interaction = toCast.interactionDef,
				Extension = toCast.Extension,
				SelectorExtension = toCast.InteractionSelectorExtension
			};
		}
		public static InteractionWithExtensionExposable Convert(InteractionWithExtension toCast)
		{
			return new InteractionWithExtensionExposable()
			{
				interactionDef = toCast.Interaction
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(interactionDef)} = {interactionDef?.defName}");

			return stringBuilder.ToString();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects.Parts;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class RJWLewdablePartExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<RJWLewdablePartExposable>();

		public HediffWithExtensionExposable hediff;

		public LewdablePartKind partKind;

		public float Size => hediff.hediff.Severity;

		public IList<string> Props => (hediff.hediff.def as HediffDef_SexPart)?.partTags ?? new();

		public void ExposeData()
		{
			Scribe_Deep.Look(ref hediff, nameof(hediff));
			Scribe_Values.Look(ref partKind, nameof(partKind));
		}

		/// <summary>
		/// Dummy method to not break build pending removal of unused submodule
		/// </summary>
		/// <param name="toCast"></param>
		/// <returns></returns>
		public static RJWLewdablePart Convert(RJWLewdablePartExposable toCast)
		{
			return new RJWLewdablePart(
				toCast.hediff.hediff as ISexPartHediff,
				toCast.partKind
				);
		}
		public static RJWLewdablePartExposable Convert(RJWLewdablePart toCast)
		{
			return new RJWLewdablePartExposable()
			{
				hediff = HediffWithExtensionExposable.Convert(toCast.Part),
				partKind = toCast.PartKind
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(partKind)} = {partKind}");

			return stringBuilder.ToString();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects.Parts;
using rjw.Modules.Shared.Logs;
using System.Text;
using Verse;

namespace rjw.Modules.Interactions.Exposable
{
	public class VanillaLewdablePartExposable : IExposable
	{
		private static ILog _log = LogManager.GetLogger<VanillaLewdablePartExposable>();

		public Pawn pawn;

		public BodyPartRecord part;

		public LewdablePartKind partKind;

		public void ExposeData()
		{
			Scribe_References.Look(ref pawn, nameof(pawn));
			Scribe_BodyParts.Look(ref part, nameof(part));
			Scribe_Values.Look(ref partKind, nameof(partKind));
		}

		public static VanillaLewdablePart Convert(VanillaLewdablePartExposable toCast)
		{
			return new VanillaLewdablePart(
					toCast.pawn,
					toCast.part,
					toCast.partKind
				);
		}
		public static VanillaLewdablePartExposable Convert(VanillaLewdablePart toCast)
		{
			return new VanillaLewdablePartExposable()
			{
				pawn = toCast.Owner,
				part = toCast.Part,
				partKind = toCast.PartKind
			};
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();

			stringBuilder.AppendLine($"{nameof(partKind)} = {partKind}");

			return stringBuilder.ToString();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace rjw.Modules.Interactions.Extensions
{
	public static class EnumerableExtensions
	{
		public static IEnumerable<ILewdablePart> FilterSeverity(this IEnumerable<ILewdablePart> self, Nullable<float> severity)
		{
			IEnumerable<ILewdablePart> result = self;

			if (severity.HasValue == true)
			{
				result = Enumerable.Union(
					result.Where(e => e is VanillaLewdablePart),
					result.Where(e => e is RJWLewdablePart)
						.Where(e => (e as RJWLewdablePart).Part.AsHediff.Severity >= severity.Value)
					);
			}

			foreach (ILewdablePart part in result)
			{
				yield return part;
			}
		}

		public static IEnumerable<ILewdablePart> WithPartKind(this IEnumerable<ILewdablePart> self, LewdablePartKind partKind)
		{
			return self
				.Where(e => e.PartKind == partKind);
		}
		public static IEnumerable<ILewdablePart> WithPartTag(this IEnumerable<ILewdablePart> self, GenitalTag tag)
		{
			return self
				.OfType<RJWLewdablePart>()
				.Where(e => e.Part.Def.genitalTags.Contains(tag));
		}
		public static IEnumerable<ILewdablePart> WithPartKindAndTag(this IEnumerable<ILewdablePart> self, LewdablePartKind partKind, GenitalTag tag)
		{
			return self
				.WithPartKind(partKind)
				.WithPartTag(tag);
		}

		public static bool HasPartKind(this IEnumerable<ILewdablePart> self, LewdablePartKind partKind)
		{
			return self
				.WithPartKind(partKind)
				.Any();
		}
		public static bool HasPartTag(this IEnumerable<ILewdablePart> self, GenitalTag tag)
		{
			return self
				.WithPartTag(tag)
				.Any();
		}
		public static bool HasPartKindAndTag(this IEnumerable<ILewdablePart> self, LewdablePartKind partKind, GenitalTag tag)
		{
			return self
				.WithPartKindAndTag(partKind, tag)
				.Any();
		}

		public static IEnumerable<ISexPartHediff> BigBreasts(this IEnumerable<ISexPartHediff> self)
		{
			if (self == null)
			{
				return null;
			}

			return self
				.Where(e => e.Def.genitalFamily == GenitalFamily.Breasts)
				.Where(e => e.AsHediff.CurStageIndex >= 1);
		}

		public static bool HasBigBreasts(this IEnumerable<ISexPartHediff> self)
		{
			if (self == null)
			{
				return false;
			}

			return self
				.BigBreasts()
				.Any();
		}

		public static ISexPartHediff Largest(this IEnumerable<ISexPartHediff> self)
		{
			if (self == null)
			{
				return null;
			}

			return self
				.OrderByDescending(e => e.AsHediff.Severity)
				.FirstOrDefault();
		}
		public static ISexPartHediff Smallest(this IEnumerable<ISexPartHediff> self)
		{
			if (self == null)
			{
				return null;
			}

			return self
				.OrderBy(e => e.AsHediff.Severity)
				.FirstOrDefault();
		}

		public static ISexPartHediff GetBestSizeAppropriate(this IEnumerable<ISexPartHediff> self, ISexPartHediff toFit)
		{
			if (self == null)
			{
				return null;
			}

			return self
				.OrderBy(e => Mathf.Abs(e.AsHediff.Severity - toFit.AsHediff.Severity))
				.FirstOrDefault();
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Extensions
{
	public static class GenitalFamilyExtension
	{
		public static LewdablePartKind ToPartKind(this GenitalFamily self)
		{
			switch (self)
			{
				case GenitalFamily.Vagina:
					return LewdablePartKind.Vagina;
				case GenitalFamily.Penis:
					return LewdablePartKind.Penis;
				case GenitalFamily.Breasts:
					return LewdablePartKind.Breasts;
				case GenitalFamily.Udders:
					return LewdablePartKind.Udders;
				case GenitalFamily.Anus:
					return LewdablePartKind.Anus;
				case GenitalFamily.FemaleOvipositor:
					return LewdablePartKind.FemaleOvipositor;
				case GenitalFamily.MaleOvipositor:
					return LewdablePartKind.MaleOvipositor;
				default:
					return LewdablePartKind.Unsupported;
			}
		}
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Extensions
{
	public static class HediffWithGenitalPartExtensions
	{
		//Vanilla
		public static IEnumerable<ISexPartHediff> Penises(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.Penis);
		public static IEnumerable<ISexPartHediff> Vaginas(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.Vagina);
		public static IEnumerable<ISexPartHediff> Breasts(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.Breasts);
		public static IEnumerable<ISexPartHediff> Udders(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.Udders);
		public static IEnumerable<ISexPartHediff> Anuses(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.Anus);

		//Ovi
		public static IEnumerable<ISexPartHediff> FemaleOvipositors(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.FemaleOvipositor);
		public static IEnumerable<ISexPartHediff> MaleOvipositors(this IEnumerable<ISexPartHediff> self) =>
			self.Where(e => e.Def.genitalFamily == GenitalFamily.MaleOvipositor);
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Extensions
{
	public static class InteractionWithExtensions
	{
		public static IEnumerable<InteractionWithExtension> Reverse(this IEnumerable<InteractionWithExtension> self)
		{
			return self
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Reverse));
		}
		public static IEnumerable<InteractionWithExtension> NonReverse(this IEnumerable<InteractionWithExtension> self)
		{
			return self
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Reverse) == false);
		}
		public static IEnumerable<InteractionWithExtension> FilterReverse(this IEnumerable<InteractionWithExtension> self, bool isReverse)
		{
			return self
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Reverse) == isReverse);
		}
	}
}


code:
using LudeonTK;
using RimWorld;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Extensions;
using System.Collections.Generic;
using System.Linq;
using Verse;

namespace rjw.Modules.Interactions.Extensions
{
	public static class PawnExtensions
	{
		public static SexablePawnParts GetSexablePawnParts(this Pawn self)
		{
			if (self == null)
			{
				return null;
			}

			//We get the genital parts once so we don't parse ALL the hediff all the time
			IList<ISexPartHediff> hediffWithGenitalParts = self.health.hediffSet.hediffs
				.OfType<ISexPartHediff>()
				.ToList();

			return new SexablePawnParts
			{
				Mouths = self.Mouths().WithoutMissing(self),
				Beaks = self.Beaks().WithoutMissing(self),
				Tongues = self.Tongues().WithoutMissing(self),
				Hands = self.Hands().WithoutMissing(self),
				Feet = self.Feet().WithoutMissing(self),
				Tails = self.Tails().WithoutMissing(self),

				AllParts = hediffWithGenitalParts,

				Penises = hediffWithGenitalParts.Penises(),
				Vaginas = hediffWithGenitalParts.Vaginas(),
				Breasts = hediffWithGenitalParts.Breasts(),
				Udders = hediffWithGenitalParts.Udders(),
				Anuses = hediffWithGenitalParts.Anuses(),

				FemaleOvipositors = hediffWithGenitalParts.FemaleOvipositors(),
				MaleOvipositors = hediffWithGenitalParts.MaleOvipositors()
			};
		}

		private static IList<BodyPartRecord> Mouths(this Pawn self)
		{
			//EatingSource = mouth
			return self.RaceProps.body.GetPartsWithTag(RimWorld.BodyPartTagDefOf.EatingSource)
				.Where(part => part.def.defName?.ToLower().Contains("beak") == false)
				.ToList();
		}

		private static IList<BodyPartRecord> Beaks(this Pawn self)
		{
			//EatingSource = mouth
			return self.RaceProps.body.GetPartsWithTag(RimWorld.BodyPartTagDefOf.EatingSource)
				.Where(part => part.def.defName?.ToLower().Contains("beak") == true)
				.ToList();
		}

		private static IList<BodyPartRecord> Tongues(this Pawn self)
		{
			BodyDef body = self.RaceProps.body;
			List<BodyPartRecord> possibleParts = body.GetPartsWithTag(RimWorld.BodyPartTagDefOf.Tongue);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Vanilla animals don't have tongues
			possibleParts = body.GetPartsWithDef(RJWBodyPartDefOf.AnimalJaw);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Fallback for races with custom tongue def
			return body.GetPartsWhereDefNameContains("tongue").ToList();
		}

		private static IList<BodyPartRecord> Tails(this Pawn self)
		{
			BodyDef body = self.RaceProps.body;
			List<BodyPartRecord> possibleParts = body.GetPartsWithDef(RJWBodyPartDefOf.Tail);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Found at least one race with a custom tail def
			return body.GetPartsWhereDefNameContains("tail").ToList();
		}

		private static IList<BodyPartRecord> Hands(this Pawn self)
		{
			BodyDef body = self.RaceProps.body;
			List<BodyPartRecord> possibleParts = body.GetPartsWithDef(BodyPartDefOf.Hand);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Some races just don't model hands
			possibleParts = body.GetPartsWithDef(BodyPartDefOf.Arm);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// At least one race only modelled shoulders
			possibleParts = body.GetPartsWithDef(BodyPartDefOf.Shoulder);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Fallback for races with custom hand def
			return body.GetPartsWhereDefNameContains("hand").ToList();
		}

		private static IList<BodyPartRecord> Feet(this Pawn self)
		{
			BodyDef body = self.RaceProps.body;
			List<BodyPartRecord> possibleParts = body.GetPartsWithDef(RJWBodyPartDefOf.Foot);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Animals have paws instead of feet
			possibleParts = body.GetPartsWithDef(RJWBodyPartDefOf.Paw);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Don't know if there are races that don't model feet, but I'll add this
			possibleParts = body.GetPartsWithDef(RJWBodyPartDefOf.Leg);
			if (possibleParts.Count > 0)
			{
				return possibleParts;
			}

			// Fallback for races with custom foot def
			return body.GetPartsWhereDefNameContains("foot", "paw").ToList();
		}

		/// <summary>
		/// Find all BodyPartRecords where def name contains one of the strings.
		/// <br/>
		/// This method is slower than GetPartsWithTag and much slower than GetPartsWithDef.
		/// </summary>
		/// <param name="name">Strings to search in the def names. Lower case only</param>
		private static IEnumerable<BodyPartRecord> GetPartsWhereDefNameContains(this BodyDef body, params string[] name)
		{
			for (int i = 0; i < body.AllParts.Count; i++)
			{
				if (body.AllParts[i].def.defName?.ToLower().ContainsAny(name) == true)
				{
					yield return body.AllParts[i];
				}
			}
		}

		/// <summary>
		/// Create new list without the missing body parts
		/// </summary>
		/// <param name="self">List of body parts</param>
		/// <param name="owner">Pawn to check against</param>
		/// <returns>New list with filtered values from <paramref name="self"/></returns>
		private static IList<BodyPartRecord> WithoutMissing(this IList<BodyPartRecord> self, Pawn owner)
		{
			// body.GetPartsWithDef returns a ref to the cached list. Bad things will happen if we change it
			List<BodyPartRecord> result = [];

			for (int i = 0; i < self.Count; i++)
			{
				if (!self[i].IsMissingForPawn(owner))
				{
					result.Add(self[i]);
				}
			}

			return result;
		}

		[DebugAction("RimJobWorld", "Dump SexablePawnParts", actionType = DebugActionType.ToolMapForPawns, allowedGameStates = AllowedGameStates.PlayingOnMap)]
		public static void DumpSexablePawnParts(Pawn pawn)
		{
			SexablePawnParts parts = pawn.GetSexablePawnParts();

			ModLog.Message($"Dumping SexablePawnParts for {pawn}");
			ModLog.Message($"Mouths ({parts.Mouths.Count()}):");
			foreach (var part in parts.Mouths)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Beaks ({parts.Beaks.Count()}):");
			foreach (var part in parts.Beaks)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Tongues ({parts.Tongues.Count()}):");
			foreach (var part in parts.Tongues)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Hands ({parts.Hands.Count()}):");
			foreach (var part in parts.Hands)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Feet ({parts.Feet.Count()}):");
			foreach (var part in parts.Feet)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Tails ({parts.Tails.Count()}):");
			foreach (var part in parts.Tails)
			{
				ModLog.Message($"    {part}");
			}

			ModLog.Message($"Penises ({parts.Penises.Count()}):");
			foreach (var part in parts.Penises)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Vaginas ({parts.Vaginas.Count()}):");
			foreach (var part in parts.Vaginas)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Breasts ({parts.Breasts.Count()}):");
			foreach (var part in parts.Breasts)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Udders ({parts.Udders.Count()}):");
			foreach (var part in parts.Udders)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"Anuses ({parts.Anuses.Count()}):");
			foreach (var part in parts.Anuses)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"FemaleOvipositors ({parts.FemaleOvipositors.Count()}):");
			foreach (var part in parts.FemaleOvipositors)
			{
				ModLog.Message($"    {part}");
			}
			ModLog.Message($"MaleOvipositors ({parts.MaleOvipositors.Count()}):");
			foreach (var part in parts.MaleOvipositors)
			{
				ModLog.Message($"    {part}");
			}

			ModLog.Message($"AllParts ({parts.AllParts.Count()}):");
			foreach (var part in parts.AllParts)
			{
				ModLog.Message($"    {part}");
			}
		}
	}
}


code:
using RimWorld;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Helpers
{
	public static class InteractionHelper
	{
		public static InteractionWithExtension GetWithExtension(InteractionDef interactionDef)
		{
			if (interactionDef.HasModExtension<InteractionSelectorExtension>() == false)
			{
				return null;
			}

			if (interactionDef.HasModExtension<InteractionExtension>() == false)
			{
				return null;
			}

			return new InteractionWithExtension
			{
				Interaction = interactionDef,
				Extension = interactionDef.GetModExtension<InteractionExtension>(),
				SelectorExtension = interactionDef.GetModExtension<InteractionSelectorExtension>()
			};
		}
	}
}


code:
using RimWorld;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Objects.Parts;
using Verse;
using rjw.Modules.Interactions.Defs.DefFragment;

namespace rjw.Modules.Interactions.Helpers
{
	public static class PartHelper
	{
		private readonly static Internals.IPartFinderService _partFinderService;
		private readonly static Internals.IBlockedPartDetectorService _blockedPartDetectorService;

		static PartHelper()
		{
			_partFinderService = Internals.Implementation.PartFinderService.Instance;
			_blockedPartDetectorService = Internals.Implementation.BlockedPartDetectorService.Instance;
		}

		public static IList<ILewdablePart> FindParts(Pawn pawn, GenitalFamily family)
		{
			InteractionPawn interactionPawn = ToInteractionPawn(pawn);

			return _partFinderService.FindUnblockedForPawn(interactionPawn, family)
				.ToList();
		}
		public static IList<ILewdablePart> FindParts(Pawn pawn, GenitalFamily family, IList<string> props)
		{
			InteractionPawn interactionPawn = ToInteractionPawn(pawn);

			return _partFinderService.FindUnblockedForPawn(interactionPawn, family, props)
				.ToList();
		}
		public static IList<ILewdablePart> FindParts(Pawn pawn, GenitalTag tag)
		{
			InteractionPawn interactionPawn = ToInteractionPawn(pawn);

			return _partFinderService.FindUnblockedForPawn(interactionPawn, tag)
				.ToList();
		}
		public static IList<ILewdablePart> FindParts(Pawn pawn, GenitalTag tag, IList<string> props)
		{
			InteractionPawn interactionPawn = ToInteractionPawn(pawn);

			return _partFinderService.FindUnblockedForPawn(interactionPawn, tag, props)
				.ToList();
		}

		private static InteractionPawn ToInteractionPawn(Pawn pawn)
		{
			InteractionPawn interactionPawn = new InteractionPawn
			{
				Pawn = pawn,
				Parts = pawn.GetSexablePawnParts()
			};

			interactionPawn.BlockedParts = _blockedPartDetectorService.BlockedPartsForPawn(interactionPawn);

			return interactionPawn;
		}
	}
}


code:
using rjw.Modules.Interactions.Objects;

namespace rjw.Modules.Interactions
{
	public interface ICustomRequirementHandler
	{
		/// <summary>
		/// Will be used to match the field <see cref="InteractionSelectorExtension.customRequirementHandler"/>
		/// </summary>
		string HandlerKey { get; }

		bool FufillRequirements(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive);
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionRequirementService
	{
		bool FufillRequirements(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive);
	}
}


code:
using rjw.Modules.Interactions.Contexts;

namespace rjw.Modules.Interactions
{
	public interface ILewdInteractionService
	{
		InteractionOutputs GenerateInteraction(InteractionInputs inputs);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using Verse;

namespace rjw.Modules.Interactions
{
	public interface ILewdInteractionValidatorService
	{
		bool IsValid(InteractionDef interaction, Pawn dominant, Pawn submissive);
	}
}


code:
using rjw.Modules.Interactions.Contexts;

namespace rjw.Modules.Interactions
{
	public interface ISpecificLewdInteractionService
	{
		InteractionOutputs GenerateSpecificInteraction(SpecificInteractionInputs inputs);
	}
}


code:
using rjw.Modules.Interactions.Defs.DefFragment;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using rjw.Modules.Shared;
using rjw.Modules.Shared.Enums;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Implementation;
using rjw.Modules.Shared.Logs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionRequirementService : IInteractionRequirementService
	{
		private static ILog _log = LogManager.GetLogger<InteractionRequirementService, InteractionLogProvider>();

		public static IInteractionRequirementService Instance { get; private set; }

		public static IList<ICustomRequirementHandler> CustomRequirementHandlers { get; private set; }

		static InteractionRequirementService()
		{
			Instance = new InteractionRequirementService();

			_partFinderService = PartFinderService.Instance;
			_pawnStateService = PawnStateService.Instance;

			CustomRequirementHandlers = new List<ICustomRequirementHandler>();
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionRequirementService() { }

		private static readonly IPartFinderService _partFinderService;
		private static readonly IPawnStateService _pawnStateService;

		/// <summary>
		/// Check if the pawns don't have the lewdable parts required
		/// to partake in this interaction
		/// </summary>
		public bool FufillRequirements(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive)
		{
			_log.Debug($"{interaction.Interaction.defName} checks");

			if (String.IsNullOrWhiteSpace(interaction.SelectorExtension.customRequirementHandler) == false)
			{
				if (TryCustomHandler(interaction, dominant, submissive, out bool result) == false)
				{
					_log.Debug($"{interaction.Interaction.defName} TryCustomHandler fail");
					return false;
				}
			}

			if (CheckRequirement(dominant, interaction.SelectorExtension.dominantRequirement) == false)
			{
				return false;
			}

			_log.Debug($"{submissive.Pawn.GetName()} checks");
			if (CheckRequirement(submissive, interaction.SelectorExtension.submissiveRequirement) == false)
			{
				return false;
			}

			return true;
		}
		private bool CheckRequirement(InteractionPawn pawn, InteractionRequirement requirement)
		{
			int required = 0;
			List<ILewdablePart> availableParts = new List<ILewdablePart>();

			_log.Debug($"{pawn.Pawn.GetName()} checks");

			//pawn state should match
			if (IsPawnstateValid(pawn, requirement) == false)
			{
				return false;
			}

			//need hand
			if (requirement.hand == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Hand), LewdablePartKind.Hand);
				required++;
			}
			//need foot
			if (requirement.foot == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Foot), LewdablePartKind.Foot);
				required++;
			}

			//need mouth
			if (requirement.mouth == true || requirement.mouthORbeak == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Mouth), LewdablePartKind.Mouth);
				required++;
			}
			//need beak
			if (requirement.beak == true || requirement.mouthORbeak == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Beak), LewdablePartKind.Beak);
				required++;
			}
			//need tongue
			if (requirement.tongue == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tongue), LewdablePartKind.Tongue);
				required++;
			}
			//any oral thingy
			if (requirement.oral == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tongue), LewdablePartKind.Tongue);
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Beak), LewdablePartKind.Beak);
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Mouth), LewdablePartKind.Mouth);
				required++;
			}

			//need tail
			if (requirement.tail == true)
			{
				AppendByKind(availableParts, _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tail), LewdablePartKind.Tail);
				required++;
			}

			//need family
			if (requirement.families != null && requirement.families.Any())
			{
				foreach (GenitalFamily family in requirement.families)
				{
					AppendByFamily(availableParts, _partFinderService.FindUnblockedForPawn(pawn, family, requirement.partProps), family);
				}
				required++;
			}
			//need tag
			if (requirement.tags != null && requirement.tags.Any())
			{
				foreach (GenitalTag tag in requirement.tags)
				{
					AppendByTag(availableParts, _partFinderService.FindUnblockedForPawn(pawn, tag, requirement.partProps), tag);
				}
				required++;
			}

			//_log.Debug($"Requirement for {pawn.Pawn.GetName()} Min {requirement.minimumCount} Got {matches}");

			//The interaction have NO requirements
			if (required == 0)
			{
				return true;
			}

			int matches = availableParts
				.FilterSeverity(requirement.minimumSeverity)
				.Count();

			//Now ... all that's left is to check we have enough !
			if (requirement.minimumCount.HasValue)
			{
				return matches >= requirement.minimumCount.Value;
			}

			return matches >= 1;
		}

		private static void AppendByFamily(List<ILewdablePart> parts, IEnumerable<ILewdablePart> toAppend, GenitalFamily familly)
		{
			var array = toAppend.ToArray();

			if (array.Length > 0)
			{
				parts.AddRange(array);
			}
			else
			{
				_log.Debug($"Missing requirement : {familly}");
			}
		}

		private static void AppendByKind(List<ILewdablePart> parts, IEnumerable<ILewdablePart> toAppend, LewdablePartKind kind)
		{
			var array = toAppend.ToArray();

			if (array.Length > 0)
			{
				parts.AddRange(array);
			}
			else
			{
				_log.Debug($"Missing requirement : {kind}");
			}
		}

		private static void AppendByTag(List<ILewdablePart> parts, IEnumerable<ILewdablePart> toAppend, GenitalTag tag)
		{
			var array = toAppend.ToArray();

			if (array.Length > 0)
			{
				parts.AddRange(array);
			}
			else
			{
				_log.Debug($"Missing requirement : {tag}");
			}
		}

		private bool IsPawnstateValid(InteractionPawn pawn, InteractionRequirement requirement)
		{
			PawnState state = _pawnStateService.Detect(pawn.Pawn);

			//By default, the pawn must be healthy
			if (requirement.pawnStates == null || requirement.pawnStates.Any() == false)
			{
				return state == PawnState.Healthy;
			}

			return requirement.pawnStates.Contains(state);
		}

		private bool TryCustomHandler(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive, out bool result)
		{
			ICustomRequirementHandler handler = CustomRequirementHandlers
				.Where(e => e.HandlerKey == interaction.SelectorExtension.customRequirementHandler)
				.FirstOrDefault();

			if (handler == null)
			{
				result = false;
				return false;
			}

			try
			{
				result = handler.FufillRequirements(interaction, dominant, submissive);
			}
			catch(Exception e)
			{
				_log.Error($"Exception occured during call to custom handler {handler.GetType().FullName}. Will use regular requirement as fallback.", e);
				result = false;
				return false;
			}

			return result;
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Linq;

namespace rjw.Modules.Interactions.Implementation
{
	public class LewdInteractionService : ILewdInteractionService
	{
		private static ILog _log = LogManager.GetLogger<LewdInteractionService, InteractionLogProvider>();

		public static ILewdInteractionService Instance { get; private set; }

		static LewdInteractionService()
		{
			Instance = new LewdInteractionService();

			_interactionTypeDetectorService = InteractionTypeDetectorService.Instance;
			_reverseDetectorService = ReverseDetectorService.Instance;
			_interactionSelectorService = InteractionSelectorService.Instance;
			_interactionBuilderService = InteractionBuilderService.Instance;

			_blockedPartDetectorService = BlockedPartDetectorService.Instance;
			_partPreferenceDetectorService = PartPreferenceDetectorService.Instance;
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private LewdInteractionService() { }

		private readonly static IInteractionTypeDetectorService _interactionTypeDetectorService;
		private readonly static IReverseDetectorService _reverseDetectorService;
		private readonly static IInteractionSelectorService _interactionSelectorService;
		private readonly static IInteractionBuilderService _interactionBuilderService;

		private readonly static IBlockedPartDetectorService _blockedPartDetectorService;
		private readonly static IPartPreferenceDetectorService _partPreferenceDetectorService;

		public InteractionOutputs GenerateInteraction(InteractionInputs inputs)
		{
			///TODO : remove the logs once it works
			InteractionContext context = new InteractionContext(inputs);

			_log.Debug($"Generating Interaction for {context.Inputs.Initiator.GetName()} and {context.Inputs.Partner.GetName()}");

			Initialize(context);

			_log.Debug($"Initialized as InteractionType : {context.Internals.InteractionType} IsReverse : {context.Internals.IsReverse}");

			//Detect parts that are unusable / missing
			_blockedPartDetectorService.DetectBlockedParts(context.Internals);

			_log.Debug($"Blocked parts for dominant {context.Internals.Dominant.BlockedParts.Select(e => $"[{e}]").Aggregate(String.Empty, (e,f) => $"{e}-{f}")}");
			_log.Debug($"Blocked parts for submissive {context.Internals.Submissive.BlockedParts.Select(e => $"[{e}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			//Calculate parts usage preferences for Dominant and Submissive
			_partPreferenceDetectorService.DetectPartPreferences(context);

			_log.Debug($"Part preferences for dominant {context.Internals.Dominant.PartPreferences.Select(e => $"[{e.Key}-{e.Value}]").Aggregate(String.Empty, (e,f)=> $"{e}-{f}")}");
			_log.Debug($"Part preferences for submissive {context.Internals.Submissive.PartPreferences.Select(e => $"[{e.Key}-{e.Value}]").Aggregate(String.Empty, (e,f)=> $"{e}-{f}")}");

			context.Internals.Selected = _interactionSelectorService.Select(context);

			_log.Message($"Selected Interaction [{context.Internals.Selected.Interaction.defName}] for {context.Inputs.Initiator.GetName()} and {context.Inputs.Partner.GetName()}");

			_interactionBuilderService.Build(context);

			_log.Debug($"SelectedParts for dominant {context.Outputs.Generated.SelectedDominantParts.Select(e => $"[{e.PartKind}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");
			_log.Debug($"SelectedParts for submissive {context.Outputs.Generated.SelectedSubmissiveParts.Select(e => $"[{e.PartKind}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			return context.Outputs;
		}

		private void Initialize(InteractionContext context)
		{
			context.Internals.InteractionType =
			context.Outputs.Generated.InteractionType =
				_interactionTypeDetectorService.DetectInteractionType(context);

			context.Internals.Dominant = new Objects.InteractionPawn
			{
				Pawn = context.Inputs.Initiator,
				Parts = context.Inputs.Initiator.GetSexablePawnParts()
			};
			context.Internals.Submissive = new Objects.InteractionPawn
			{
				Pawn = context.Inputs.Partner,
				Parts = context.Inputs.Partner.GetSexablePawnParts()
			};

			context.Internals.Dominant.Gender = Genitals.Helpers.GenderHelper.GetGender(context.Internals.Dominant);
			context.Internals.Submissive.Gender = Genitals.Helpers.GenderHelper.GetGender(context.Internals.Submissive);

			context.Internals.IsReverse = _reverseDetectorService.IsReverse(context);
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.DefModExtensions;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Linq;
using RimWorld;
using Verse;

namespace rjw.Modules.Interactions.Implementation
{
	public class LewdInteractionValidatorService : ILewdInteractionValidatorService
	{
		private static ILog _log = LogManager.GetLogger<LewdInteractionService, InteractionLogProvider>();

		public static ILewdInteractionValidatorService Instance { get; private set; }

		static LewdInteractionValidatorService()
		{
			Instance = new LewdInteractionValidatorService();

			_interactionRequirementService = InteractionRequirementService.Instance;
			_blockedPartDetectorService = BlockedPartDetectorService.Instance;
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private LewdInteractionValidatorService() { }

		private readonly static IInteractionRequirementService _interactionRequirementService;

		private readonly static IBlockedPartDetectorService _blockedPartDetectorService;

		public bool IsValid(InteractionDef interaction, Pawn dominant, Pawn submissive)
		{
			InteractionPawn iDominant, iSubmissive;
			InteractionWithExtension iInteraction;

			try
			{
				Assert(interaction);
			}
			catch(NotImplementedException)
			{
				return false;
			}

			iDominant = ConvertPawn(dominant);
			iSubmissive = ConvertPawn(submissive);
			iInteraction = ConvertInteraction(interaction);

			//Detect parts that are unusable / missing
			iDominant.BlockedParts = _blockedPartDetectorService.BlockedPartsForPawn(iDominant);
			iSubmissive.BlockedParts = _blockedPartDetectorService.BlockedPartsForPawn(iSubmissive);

			return _interactionRequirementService.FufillRequirements(iInteraction, iDominant, iSubmissive);
		}

		private void Assert(InteractionDef interaction)
		{
			if (interaction.HasModExtension<InteractionSelectorExtension>() == false)
			{
				string message = $"The interaction {interaction.defName} doesn't have the required {nameof(InteractionSelectorExtension)} extention";

				_log.Error(message);
				throw new NotImplementedException(message);
			}

			if (interaction.HasModExtension<InteractionExtension>() == false)
			{
				string message = $"The interaction {interaction.defName} doesn't have the required {nameof(InteractionExtension)} extention";

				_log.Error(message);
				throw new NotImplementedException(message);
			}
		}

		private InteractionWithExtension ConvertInteraction(InteractionDef interaction)
		{
			return new InteractionWithExtension
			{
				Interaction = interaction,
				Extension = interaction.GetModExtension<InteractionExtension>(),
				SelectorExtension = interaction.GetModExtension<InteractionSelectorExtension>()
			};
		}

		private InteractionPawn ConvertPawn(Pawn pawn)
		{
			return new InteractionPawn
			{
				Pawn = pawn,
				Parts = pawn.GetSexablePawnParts()
			};
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Helpers;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Linq;

namespace rjw.Modules.Interactions.Implementation
{
	public class SpecificLewdInteractionService : ISpecificLewdInteractionService
	{
		private static ILog _log = LogManager.GetLogger<SpecificLewdInteractionService, InteractionLogProvider>();

		public static ISpecificLewdInteractionService Instance { get; private set; }

		static SpecificLewdInteractionService()
		{
			Instance = new SpecificLewdInteractionService();

			_interactionTypeDetectorService = InteractionTypeDetectorService.Instance;
			_interactionBuilderService = InteractionBuilderService.Instance;

			_blockedPartDetectorService = BlockedPartDetectorService.Instance;
			_partPreferenceDetectorService = PartPreferenceDetectorService.Instance;
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private SpecificLewdInteractionService() { }

		private readonly static IInteractionTypeDetectorService _interactionTypeDetectorService;
		private readonly static IInteractionBuilderService _interactionBuilderService;

		private readonly static IBlockedPartDetectorService _blockedPartDetectorService;
		private readonly static IPartPreferenceDetectorService _partPreferenceDetectorService;

		public InteractionOutputs GenerateSpecificInteraction(SpecificInteractionInputs inputs)
		{
			///TODO : remove the logs once it works
			InteractionContext context = new InteractionContext();

			_log.Debug($"Generating Specific Interaction {inputs.Interaction.defName} for {context.Inputs.Initiator?.GetName()} and {context.Inputs.Partner?.GetName()}");

			Initialize(context, inputs);

			//Detect parts that are unusable / missing
			_blockedPartDetectorService.DetectBlockedParts(context.Internals);

			_log.Debug($"Blocked parts for dominant {context.Internals.Dominant.BlockedParts.Select(e => $"[{e}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");
			_log.Debug($"Blocked parts for submissive {context.Internals.Submissive.BlockedParts.Select(e => $"[{e}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			//Calculate parts usage preferences for Dominant and Submissive
			_partPreferenceDetectorService.DetectPartPreferences(context);

			_log.Debug($"Part preferences for dominant {context.Internals.Dominant.PartPreferences.Select(e => $"[{e.Key}-{e.Value}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");
			_log.Debug($"Part preferences for submissive {context.Internals.Submissive.PartPreferences.Select(e => $"[{e.Key}-{e.Value}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			_interactionBuilderService.Build(context);

			_log.Debug($"SelectedParts for dominant {context.Outputs.Generated.SelectedDominantParts.Select(e => $"[{e.PartKind}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");
			_log.Debug($"SelectedParts for submissive {context.Outputs.Generated.SelectedSubmissiveParts.Select(e => $"[{e.PartKind}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			return context.Outputs;
		}

		private void Initialize(InteractionContext context, SpecificInteractionInputs inputs)
		{
			context.Internals.Selected = InteractionHelper.GetWithExtension(inputs.Interaction);
			context.Outputs.Generated = new Objects.Interaction()
			{
				InteractionDef = context.Internals.Selected
			};

			context.Inputs = new InteractionInputs()
			{
				Initiator = inputs.Initiator,
				Partner = inputs.Partner,
				IsRape = context.Outputs.Generated.InteractionDef.HasInteractionTag(Enums.InteractionTag.Rape),
				IsWhoring = context.Outputs.Generated.InteractionDef.HasInteractionTag(Enums.InteractionTag.Whoring)
			};

			context.Internals.InteractionType =
			context.Outputs.Generated.InteractionType =
				_interactionTypeDetectorService.DetectInteractionType(
					context
					);

			context.Internals.Dominant = new Objects.InteractionPawn
			{
				Pawn = context.Inputs.Initiator,
				Parts = context.Inputs.Initiator.GetSexablePawnParts()
			};
			context.Internals.Submissive = new Objects.InteractionPawn
			{
				Pawn = context.Inputs.Partner,
				Parts = context.Inputs.Partner.GetSexablePawnParts()
			};

			context.Internals.Dominant.Gender = Genitals.Helpers.GenderHelper.GetGender(context.Internals.Dominant);
			context.Internals.Submissive.Gender = Genitals.Helpers.GenderHelper.GetGender(context.Internals.Submissive);
		}
	}
}


code:
using rjw.Modules.Shared.Logs;

namespace rjw.Modules.Interactions
{
	public class InteractionLogProvider : ILogProvider
	{
		public bool IsActive => RJWSettings.DebugInteraction;
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System.Collections.Generic;

namespace rjw.Modules.Interactions.Internals
{
	public interface IBlockedPartDetectorService
	{
		IList<LewdablePartKind> BlockedPartsForPawn(InteractionPawn pawn);

		/// <summary>
		/// Detect the blocked parts for both pawn and fill
		/// <see cref="InteractionPawn.BlockedParts"/>
		/// </summary>
		/// <param name="context"></param>
		void DetectBlockedParts(InteractionInternals context);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionBuilderService
	{
		/// <summary>
		/// Fill the output interaction with all the good stuff !
		/// </summary>
		void Build(InteractionContext context);
	}
}


code:
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionRepository
	{
		IEnumerable<InteractionWithExtension> List();

		IEnumerable<InteractionWithExtension> ListForMasturbation();
		IEnumerable<InteractionWithExtension> ListForAnimal();
		IEnumerable<InteractionWithExtension> ListForBestiality();
		IEnumerable<InteractionWithExtension> ListForRape();
		IEnumerable<InteractionWithExtension> ListForConsensual();
		IEnumerable<InteractionWithExtension> ListForMechanoid();
		IEnumerable<InteractionWithExtension> ListForWhoring();
		IEnumerable<InteractionWithExtension> ListForNecrophilia();
	}
}


code:
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionScoringService
	{
		InteractionScore Score(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionSelectorService
	{
		InteractionWithExtension Select(InteractionContext context);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using Verse;

namespace rjw.Modules.Interactions.Internals
{
	public interface IInteractionTypeDetectorService
	{
		InteractionType DetectInteractionType(InteractionContext context);
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IPartFinderService
	{
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, string partProp);
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, LewdablePartKind partKind);
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalFamily family);
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalFamily family, IList<string> partProp);
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalTag tag);
		IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalTag tag, IList<string> partProp);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IPartPreferenceDetectorService
	{
		void DetectPartPreferences(InteractionContext context);
	}
}


code:
using rjw.Modules.Interactions.Defs.DefFragment;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using System.Collections.Generic;

namespace rjw.Modules.Interactions.Internals
{
	public interface IPartSelectorService
	{
		IList<ILewdablePart> SelectPartsForPawn(InteractionPawn pawn, InteractionRequirement requirement);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals
{
	public interface IReverseDetectorService
	{
		bool IsReverse(InteractionContext context);
	}
}


code:
using rjw.Modules.Interactions.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Interactions.Internals
{
	public interface IRulePackService
	{
		RulePackDef FindInteractionRulePack(InteractionWithExtension interaction);
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Rules.PartBlockedRules;
using rjw.Modules.Interactions.Rules.PartBlockedRules.Implementation;
using System.Collections.Generic;
using System.Linq;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class BlockedPartDetectorService : IBlockedPartDetectorService
	{
		public static IBlockedPartDetectorService Instance { get; private set; }

		static BlockedPartDetectorService()
		{
			Instance = new BlockedPartDetectorService();

			_partBlockedRules = new List<IPartBlockedRule>()
			{
				DeadPartBlockedRule.Instance,
				DownedPartBlockedRule.Instance,
				UnconsciousPartBlockedRule.Instance,

				MainPartBlockedRule.Instance,
				PartAvailibilityPartBlockedRule.Instance,
			};
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private BlockedPartDetectorService() { }

		private readonly static IList<IPartBlockedRule> _partBlockedRules;

		public void DetectBlockedParts(InteractionInternals context)
		{
			context.Dominant.BlockedParts = BlockedPartsForPawn(context.Dominant);
			context.Submissive.BlockedParts = BlockedPartsForPawn(context.Submissive);
		}

		public IList<LewdablePartKind> BlockedPartsForPawn(InteractionPawn pawn)
		{
			return _partBlockedRules
				.SelectMany(e => e.BlockedParts(pawn))
				//Eliminate the duplicates
				.Distinct()
				.ToList();
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionBuilderService : IInteractionBuilderService
	{
		public static IInteractionBuilderService Instance { get; private set; }

		static InteractionBuilderService()
		{
			Instance = new InteractionBuilderService();

			_rulePackService = RulePackService.Instance;
			_partSelectorService = PartSelectorService.Instance;
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionBuilderService() { }

		private readonly static IRulePackService _rulePackService;
		private readonly static IPartSelectorService _partSelectorService;

		public void Build(InteractionContext context)
		{
			context.Outputs.Generated.Dominant = context.Internals.Dominant;
			context.Outputs.Generated.Submissive = context.Internals.Submissive;

			//Participants
			if (context.Internals.IsReverse)
			{
				context.Outputs.Generated.Initiator = context.Internals.Submissive;
				context.Outputs.Generated.Receiver = context.Internals.Dominant;

			}
			else
			{
				context.Outputs.Generated.Initiator = context.Internals.Dominant;
				context.Outputs.Generated.Receiver = context.Internals.Submissive;
			}

			context.Outputs.Generated.InteractionDef = context.Internals.Selected;
			context.Outputs.Generated.RjwSexType = ParseHelper.FromString<xxx.rjwSextype>(context.Internals.Selected.Extension.rjwSextype);

			context.Outputs.Generated.RulePack = _rulePackService.FindInteractionRulePack(context.Internals.Selected);

			context.Outputs.Generated.SelectedDominantParts = _partSelectorService.SelectPartsForPawn(
				context.Internals.Dominant,
				context.Internals.Selected.SelectorExtension.dominantRequirement
				);
			context.Outputs.Generated.SelectedSubmissiveParts = _partSelectorService.SelectPartsForPawn(
				context.Internals.Submissive,
				context.Internals.Selected.SelectorExtension.submissiveRequirement
				);
		}
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionRepository : IInteractionRepository
	{
		public static IInteractionRepository Instance { get; private set; }

		static InteractionRepository()
		{
			Instance = new InteractionRepository();
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionRepository() { }

		public IEnumerable<InteractionWithExtension> List()
		{
			return InteractionDefOf.Interactions;
		}

		public IEnumerable<InteractionWithExtension> ListForMasturbation()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Masturbation));
		}

		public IEnumerable<InteractionWithExtension> ListForAnimal()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Animal));
		}

		public IEnumerable<InteractionWithExtension> ListForBestiality()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Bestiality));
		}

		public IEnumerable<InteractionWithExtension> ListForConsensual()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Consensual));
		}

		public IEnumerable<InteractionWithExtension> ListForRape()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Rape));
		}

		public IEnumerable<InteractionWithExtension> ListForWhoring()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Whoring));
		}

		public IEnumerable<InteractionWithExtension> ListForNecrophilia()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.Necrophilia));
		}

		public IEnumerable<InteractionWithExtension> ListForMechanoid()
		{
			return List()
				.Where(interaction => interaction.HasInteractionTag(InteractionTag.MechImplant));
		}

		public IEnumerable<InteractionWithExtension> List(InteractionType interactionType)
		{
			switch (interactionType)
			{
				case InteractionType.Whoring:
					return ListForWhoring();
				case InteractionType.Rape:
					return ListForRape();
				case InteractionType.Bestiality:
					return ListForBestiality();
				case InteractionType.Animal:
					return ListForAnimal();
				case InteractionType.Necrophilia:
					return ListForNecrophilia();
				case InteractionType.Mechanoid:
					return ListForMechanoid();
				case InteractionType.Consensual:
				default:
					return ListForConsensual();
			}
		}
	}
}


code:
using rjw.Modules.Interactions.Defs.DefFragment;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Objects.Parts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionScoringService : IInteractionScoringService
	{
		public static IInteractionScoringService Instance { get; private set; }

		static InteractionScoringService()
		{
			Instance = new InteractionScoringService();

			_partFinderService = PartFinderService.Instance;
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionScoringService() { }

		private static readonly IPartFinderService _partFinderService;

		public InteractionScore Score(InteractionWithExtension interaction, InteractionPawn dominant, InteractionPawn submissive)
		{
			return new InteractionScore()
			{
				Dominant = Score(interaction.SelectorExtension.dominantRequirement, dominant),
				Submissive = Score(interaction.SelectorExtension.submissiveRequirement, submissive),
				Setting = SettingScore(interaction)
			};
		}

		private float Score(InteractionRequirement requirement, InteractionPawn pawn)
		{
			int allowed = 1;

			IList<ILewdablePart> availableParts;
			IEnumerable<(ILewdablePart Part, float Score)> scoredParts;

			//Find the parts !
			availableParts = GetAvailablePartsForInteraction(requirement, pawn);

			//Score the parts !
			scoredParts = availableParts
				.Select(e => (e, pawn.PartPreferences[e.PartKind]));

			//Order the parts !
			scoredParts = scoredParts
				.OrderByDescending(e => e.Score);

			if (requirement.minimumCount.HasValue == true && requirement.minimumCount.Value > 0)
			{
				allowed = requirement.minimumCount.Value;
			}

			return scoredParts
				//Take the allowed part count
				.Take(allowed)
				.Select(e => e.Score)
				//Multiply the parts scores
				.Aggregate(1f, (e, f) => e * f);
		}

		private IList<ILewdablePart> GetAvailablePartsForInteraction(InteractionRequirement requirement, InteractionPawn pawn)
		{
			List<ILewdablePart> result = new List<ILewdablePart>();

			//need hand
			if (requirement.hand == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Hand));
			}
			//need foot
			if (requirement.foot == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Foot));
			}
			//need mouth
			if (requirement.mouth == true || requirement.mouthORbeak == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Mouth));
			}
			//need beak
			if (requirement.beak == true || requirement.mouthORbeak == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Beak));
			}
			//need tongue
			if (requirement.tongue == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tongue));
			}
			//need tail
			if (requirement.tail == true)
			{
				result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tail));
			}

			//need family
			if (requirement.families != null && requirement.families.Any())
			{
				foreach (GenitalFamily family in requirement.families)
				{
					result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, family));
				}
			}
			//need tag
			if (requirement.tags != null && requirement.tags.Any())
			{
				foreach (GenitalTag tag in requirement.tags)
				{
					result.AddRange(_partFinderService.FindUnblockedForPawn(pawn, tag));
				}
			}

			return result;
		}

		private float SettingScore(InteractionWithExtension interaction)
		{
			xxx.rjwSextype type = ParseHelper.FromString<xxx.rjwSextype>(interaction.Extension.rjwSextype);

			switch (type)
			{
				case xxx.rjwSextype.Vaginal:
					return RJWPreferenceSettings.vaginal;
				case xxx.rjwSextype.Anal:
					return RJWPreferenceSettings.anal;
				case xxx.rjwSextype.DoublePenetration:
					return RJWPreferenceSettings.double_penetration;
				case xxx.rjwSextype.Boobjob:
					return RJWPreferenceSettings.breastjob;
				case xxx.rjwSextype.Handjob:
					return RJWPreferenceSettings.handjob;
				case xxx.rjwSextype.Footjob:
					return RJWPreferenceSettings.footjob;
				case xxx.rjwSextype.Fingering:
					return RJWPreferenceSettings.fingering;
				case xxx.rjwSextype.Scissoring:
					return RJWPreferenceSettings.scissoring;
				case xxx.rjwSextype.MutualMasturbation:
					return RJWPreferenceSettings.mutual_masturbation;
				case xxx.rjwSextype.Fisting:
					return RJWPreferenceSettings.fisting;
				case xxx.rjwSextype.Rimming:
					return RJWPreferenceSettings.rimming;
				case xxx.rjwSextype.Fellatio:
					return RJWPreferenceSettings.fellatio;
				case xxx.rjwSextype.Cunnilingus:
					return RJWPreferenceSettings.cunnilingus;
				case xxx.rjwSextype.Sixtynine:
					return RJWPreferenceSettings.sixtynine;
				default:
					return 1;
			}
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Extensions;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Interactions.Rules.InteractionRules;
using rjw.Modules.Interactions.Rules.InteractionRules.Implementation;
using rjw.Modules.Shared;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Helpers;
using rjw.Modules.Shared.Logs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionSelectorService : IInteractionSelectorService
	{
		private static ILog _log = LogManager.GetLogger<InteractionSelectorService, InteractionLogProvider>();

		public static IInteractionSelectorService Instance { get; private set; }

		static InteractionSelectorService()
		{
			Instance = new InteractionSelectorService();

			_interactionCompatibilityService = InteractionRequirementService.Instance;
			_interactionScoringService = InteractionScoringService.Instance;

			_interactionRules = new List<IInteractionRule>()
			{
				new MasturbationInteractionRule(),
				new AnimalInteractionRule(),
				new BestialityInteractionRule(),
				new ConsensualInteractionRule(),
				new RapeInteractionRule(),
				new WhoringInteractionRule(),
				new NecrophiliaInteractionRule(),
				new MechImplantInteractionRule()
			};
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionSelectorService() { }

		private static readonly IInteractionRequirementService _interactionCompatibilityService;
		private static readonly IInteractionScoringService _interactionScoringService;
		private static readonly IList<IInteractionRule> _interactionRules;

		public InteractionWithExtension Select(InteractionContext context)
		{
			IInteractionRule rule = FindRule(context.Internals.InteractionType);

			_log.Debug($"[available] {rule.Interactions.Select(e => $"[{e.Interaction.defName}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			IEnumerable<InteractionWithExtension> interactions = rule.Interactions
				.FilterReverse(context.Internals.IsReverse)
				// Filter the interaction by removing those where the requirements are not met
				.Where(e => _interactionCompatibilityService.FufillRequirements(e, context.Internals.Dominant, context.Internals.Submissive));

			_log.Debug($"[available] {rule.Interactions.Select(e => $"[{e.Interaction.defName}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			//Now we score each remaining interactions
			IList<Weighted<InteractionWithExtension>> scored = interactions
				.Select(e => new Weighted<InteractionWithExtension>(Score(context, e, rule), e))
				.ToList();

			_log.Debug($"[Scores] {scored.Select(e => $"[{e.Element.Interaction.defName}-{e.Weight}]").Aggregate(String.Empty, (e, f) => $"{e}-{f}")}");

			InteractionWithExtension result = RandomHelper.WeightedRandom(scored);

			if (result == null)
			{
				result = rule.Default;
				_log.Warning($"No eligible interaction found for Type {context.Internals.InteractionType}, IsReverse {context.Internals.IsReverse}, Initiator {context.Inputs.Initiator.GetName()}, Partner {context.Inputs.Partner.GetName()}. Using default {result?.Interaction.defName}.");
			}

			return result;
		}

		private float Score(InteractionContext context, InteractionWithExtension interaction, IInteractionRule rule)
		{
			return _interactionScoringService.Score(interaction, context.Internals.Dominant, context.Internals.Submissive)
				.GetScore(rule.SubmissivePreferenceWeight);
		}

		private IInteractionRule FindRule(InteractionType interactionType)
		{
			return _interactionRules
				.Where(e => e.InteractionType == interactionType)
				.FirstOrDefault();
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;

namespace rjw.Modules.Interactions.Internals.Implementation
{
	public class InteractionTypeDetectorService : IInteractionTypeDetectorService
	{
		private static ILog _log = LogManager.GetLogger<InteractionTypeDetectorService, InteractionLogProvider>();

		public static IInteractionTypeDetectorService Instance { get; private set; }

		static InteractionTypeDetectorService()
		{
			Instance = new InteractionTypeDetectorService();
		}

		/// <summary>
		/// Do not instantiate, use <see cref="Instance"/>
		/// </summary>
		private InteractionTypeDetectorService() { }

		public InteractionType DetectInteractionType(InteractionContext context)
		{
			InteractionType result = DetectInteractionType(
				context.Inputs.Initiator,
				context.Inputs.Partner,
				context.Inputs.IsRape,
				context.Inputs.IsWhoring
				);

			if (result == InteractionType.Masturbation)
			{
				context.Inputs.Partner = context.Inputs.Initiator;
			}

			return result;
		}

		private InteractionType DetectInteractionType(Pawn initiator, Pawn partner, bool isRape, bool isWhoring)
		{
			_log.Debug(initiator.GetName());
			_log.Debug(partner.GetName());

			if (initiator == partner || partner == null)
			{
				partner = initiator;

				return InteractionType.Masturbation;
			}

			if (partner.health.Dead == true)
			{
				return InteractionType.Necrophilia;
			}

			if (xxx.is_mechanoid(initiator))
			{
				return InteractionType.Mechanoid;
			}

			//Either one or the other but not both
			if (xxx.is_animal(initiator) ^ xxx.is_animal(partner))
			{
				return InteractionType.Bestiality;
			}

			if (xxx.is_animal(initiator) && xxx.is_animal(partner))
			{
				return InteractionType.Animal;
			}

			if (isWhoring)
			{
				return InteractionType.Whoring;
			}

			if (isRape)
			{
				return InteractionType.Rape;
			}

			return InteractionType.Consensual;
		}
	}
}