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;
		}
	}
}

license: mit


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;
		}
	}
}


code:
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Extensions;
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.Implementation
{
	public class PartFinderService : IPartFinderService
	{
		public static IPartFinderService Instance { get; private set; }

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

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

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, string partProp)
		{
			return pawn.ListLewdableParts()
				.Where(e => pawn.BlockedParts.Contains(e.PartKind) == false)
				.OfType<RJWLewdablePart>()
				.Where(e => e.Props.Contains(partProp));
		}

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, LewdablePartKind partKind)
		{
			if (pawn.BlockedParts.Contains(partKind))
			{
				return Enumerable.Empty<ILewdablePart>();
			}

			return pawn.ListLewdableParts()
				.Where(e => e.PartKind == partKind);
		}

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalFamily family)
		{
			return pawn.ListLewdableParts()
				.Where(e => pawn.BlockedParts.Contains(e.PartKind) == false)
				.OfType<RJWLewdablePart>()
				.Where(e => e.Part.Def.genitalFamily == family);
		}

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalFamily family, IList<string> partProp)
		{
			var eligibles = pawn.ListLewdableParts()
				.Where(e => pawn.BlockedParts.Contains(e.PartKind) == false)
				.OfType<RJWLewdablePart>()
				.Where(e => e.Part.Def.genitalFamily == family);

			if (partProp == null || partProp.Any() == false)
			{
				return eligibles;
			}

			return eligibles
				.Where(e => e.Props.Any())
				.Where(e => partProp.All(prop => e.Props.Contains(prop)));
		}

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalTag tag)
		{
			return pawn.ListLewdableParts()
				.Where(e => pawn.BlockedParts.Contains(e.PartKind) == false)
				.OfType<RJWLewdablePart>()
				.Where(e => e.Part.Def.genitalTags.Contains(tag));
		}

		public IEnumerable<ILewdablePart> FindUnblockedForPawn(InteractionPawn pawn, GenitalTag tag, IList<string> partProp)
		{
			var eligibles = pawn.ListLewdableParts()
				.Where(e => pawn.BlockedParts.Contains(e.PartKind) == false)
				.OfType<RJWLewdablePart>()
				.Where(e => e.Part.Def.genitalTags.Contains(tag));

			if (partProp == null || partProp.Any() == false)
			{
				return eligibles;
			}

			return eligibles
				.Where(e => e.Props.Any())
				.Where(e => partProp.All(prop => e.Props.Contains(prop)));
		}

	}
}


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

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

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

			_partKindUsageRules = new List<IPartPreferenceRule>()
			{
				new AnimalPartKindUsageRule(),
				new BestialityForZoophilePartKindUsageRule(),
				new BestialityPartKindUsageRule(),
				new BigBreastsPartKindUsageRule(),
				new MainPartKindUsageRule(),
				new PawnAlreadySatisfiedPartKindUsageRule(),
				new QuirksPartKindUsageRule(),
				new RapePartKindUsageRule(),
				new SizeDifferencePartKindUsageRule(),
				new WhoringPartKindUsageRule(),
				new PregnancyApproachPartKindUsageRule(),
			};
		}

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

		private readonly static IList<IPartPreferenceRule> _partKindUsageRules;

		public void DetectPartPreferences(InteractionContext context)
		{
			context.Internals.Dominant.PartPreferences = PartPreferencesForDominant(context);
			context.Internals.Submissive.PartPreferences = BlockedPartsForSubmissive(context);
		}

		private IDictionary<LewdablePartKind, float> PartPreferencesForDominant(InteractionContext context)
		{
			IList<Weighted<LewdablePartKind>> preferences = _partKindUsageRules
					.SelectMany(e => e.ModifiersForDominant(context))
					.ToList();

			return MergeWithDefaultDictionary(context.Internals.Dominant, preferences);
		}

		private IDictionary<LewdablePartKind, float> BlockedPartsForSubmissive(InteractionContext context)
		{
			IList<Weighted<LewdablePartKind>> preferences  = _partKindUsageRules
					.SelectMany(e => e.ModifiersForSubmissive(context))
					.ToList();

			return MergeWithDefaultDictionary(context.Internals.Submissive, preferences);
		}

		private IDictionary<LewdablePartKind, float> MergeWithDefaultDictionary(InteractionPawn pawn, IList<Weighted<LewdablePartKind>> preferences)
		{
			//A dictionary containing every part kind with a value of 1.0f (aka defaults)
			//it'll prevent exceptions to  be thrown if you did preferences[missingKey]
			IDictionary<LewdablePartKind, float> result = Enum.GetValues(typeof(LewdablePartKind))
				.OfType<LewdablePartKind>()
				//We don't want that ...
				.Where(e => e != LewdablePartKind.Unsupported)
				//We don't want these either
				.Where(e => pawn.BlockedParts.Contains(e) == false)
				.ToDictionary(e => e, e => 1.0f);

			//Put the values in the dictionary
			foreach (KeyValuePair<LewdablePartKind, float> element in result.ToList())
			{
				if (preferences.Where(e => e.Element == element.Key).Any())
				{
					result[element.Key] *= preferences
						.Where(e => e.Element == element.Key)
						.Select(e => e.Weight)
						.Aggregate((e, f) => e * f);
				}
			}

			return result;
		}
	}
}


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.Collections.Generic;
using System.Linq;

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

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

			_partFinderService = PartFinderService.Instance;
		}

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

		private readonly static IPartFinderService _partFinderService;

		public IList<ILewdablePart> SelectPartsForPawn(InteractionPawn pawn, InteractionRequirement requirement)
		{
			int required = 1;

			if (requirement.minimumCount.HasValue)
			{
				required = requirement.minimumCount.Value;
			}

			return EligebleParts(pawn, requirement)
				.Where(e => pawn.PartPreferences.ContainsKey(e.PartKind))
				.Select(e => new { Part = e, Preference = pawn.PartPreferences[e.PartKind] })
				.OrderByDescending(e => e.Preference)
				.Take(required)
				.Select(e => e.Part)
				.ToList();
		}

		private IEnumerable<ILewdablePart> EligebleParts(InteractionPawn pawn, InteractionRequirement requirement)
		{
			if (requirement.hand == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Hand))
				{
					yield return part;
				}
			}
			if (requirement.foot == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Foot))
				{
					yield return part;
				}
			}
			if (requirement.mouth == true || requirement.mouthORbeak == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Mouth))
				{
					yield return part;
				}
			}
			if (requirement.beak == true || requirement.mouthORbeak == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Beak))
				{
					yield return part;
				}
			}
			if (requirement.tongue == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tongue))
				{
					yield return part;
				}
			}
			if (requirement.tail == true)
			{
				foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, LewdablePartKind.Tail))
				{
					yield return part;
				}
			}

			if (requirement.families != null && requirement.families.Any())
			{
				foreach (GenitalFamily family in requirement.families)
				{
					foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, family))
					{
						yield return part;
					}
				}
			}
			if (requirement.tags != null && requirement.tags.Any())
			{
				foreach (GenitalTag tag in requirement.tags)
				{
					foreach (var part in _partFinderService.FindUnblockedForPawn(pawn, tag))
					{
						yield return part;
					}
				}
			}
		}
	}
}


code:
using Multiplayer.API;
using rjw.Modules.Genitals.Enums;
using rjw.Modules.Interactions.Contexts;
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 ReverseDetectorService : IReverseDetectorService
	{
		private static ILog _log = LogManager.GetLogger<ReverseDetectorService, InteractionLogProvider>();

		public static IReverseDetectorService Instance { get; private set; }

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

			_random = new Random();
		}

		private static readonly Random _random;

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

		private const float ReverseRapeChanceForFemale = 90 / 100f;
		private const float ReverseRapeChanceForMale = 10 / 100f;
		private const float ReverseRapeChanceForFuta = (ReverseRapeChanceForFemale + ReverseRapeChanceForMale) / 2f;

		private const float ReverseConsensualChanceForFemale = 75 / 100f;
		private const float ReverseConsensualChanceForMale = 25 / 100f;
		private const float ReverseConsensualChanceForFuta = (ReverseConsensualChanceForFemale + ReverseConsensualChanceForMale) / 2f;

		private const float ReverseBestialityChanceForFemale = 90 / 100f;
		private const float ReverseBestialityChanceForMale = 10 / 100f;
		private const float ReverseBestialityChanceForFuta = (ReverseBestialityChanceForFemale + ReverseBestialityChanceForMale) / 2f;

		private const float ReverseAnimalChanceForFemale = 90 / 100f;
		private const float ReverseAnimalChanceForMale = 10 / 100f;
		private const float ReverseAnimalChanceForFuta = (ReverseAnimalChanceForFemale + ReverseAnimalChanceForMale) / 2f;

		private const float ReverseWhoringChanceForFemale = 90 / 100f;
		private const float ReverseWhoringChanceForMale = 10 / 100f;
		private const float ReverseWhoringChanceForFuta = (ReverseWhoringChanceForFemale + ReverseWhoringChanceForMale) / 2f;

		[SyncMethod]
		public bool IsReverse(InteractionContext context)
		{
			//Necrophilia
			if (context.Internals.InteractionType == Enums.InteractionType.Necrophilia)
			{
				context.Internals.IsReverse = false;
			}
			//MechImplant
			if (context.Internals.InteractionType == Enums.InteractionType.Mechanoid)
			{
				context.Internals.IsReverse = false;
			}
			//Masturbation
			if (context.Internals.InteractionType == Enums.InteractionType.Masturbation)
			{
				context.Internals.IsReverse = false;
			}

			Gender initiatorGender = context.Internals.Dominant.Gender;
			Gender partnerGender = context.Internals.Submissive.Gender;

			float roll = (float)_random.NextDouble();

			bool result;

			if (context.Outputs.Generated.InteractionType == Enums.InteractionType.Consensual)
			{
				result = IsReverseRape(initiatorGender, partnerGender, roll, ReverseConsensualChanceForFemale, ReverseConsensualChanceForMale, ReverseConsensualChanceForFuta);
			}
			else
			if (context.Outputs.Generated.InteractionType == Enums.InteractionType.Animal)
			{
				result = IsReverseRape(initiatorGender, partnerGender, roll, ReverseAnimalChanceForFemale, ReverseAnimalChanceForMale, ReverseAnimalChanceForFuta);
			}
			else
			if (context.Outputs.Generated.InteractionType == Enums.InteractionType.Whoring)
			{
				result = IsReverseRape(initiatorGender, partnerGender, roll, ReverseWhoringChanceForFemale, ReverseWhoringChanceForMale, ReverseWhoringChanceForFuta);
			}
			else
			if (context.Outputs.Generated.InteractionType == Enums.InteractionType.Bestiality)
			{
				result = IsReverseRape(initiatorGender, partnerGender, roll, ReverseBestialityChanceForFemale, ReverseBestialityChanceForMale, ReverseBestialityChanceForFuta);
			}
			else
			if (context.Outputs.Generated.InteractionType == Enums.InteractionType.Rape)
			{
				result = IsReverseRape(initiatorGender, partnerGender, roll, ReverseRapeChanceForFemale, ReverseRapeChanceForMale, ReverseRapeChanceForFuta);
			}
			else
			{
				result = false;
			}

			return result;
		}

		private bool IsReverseRape(Gender initiator, Gender partner, float roll, float femaleChance, float maleChance, float futaChance)
		{
			_log.Debug($"{initiator}/{partner} - {roll} -> [f{femaleChance},m{maleChance},fu{futaChance}]");

			switch (initiator)
			{
				case Gender.Male:
					return roll < maleChance;
				case Gender.Female:
					return roll < femaleChance;
				case Gender.Trap:
					return roll < maleChance;
				case Gender.Futa:
				case Gender.FemaleOvi:
					return roll < futaChance;
				case Gender.MaleOvi:
					return roll < maleChance;
				case Gender.Unknown:
					break;
			}

			return false;
		}
	}
}


code:
using Multiplayer.API;
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.Internals.Implementation
{
	public class RulePackService : IRulePackService
	{
		public static IRulePackService Instance { get; private set; }

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

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

		public RulePackDef FindInteractionRulePack(InteractionWithExtension interaction)
		{
			RulePackDef result;

			if (TryFindRulePack(interaction.SelectorExtension, out result))
			{
				return result;
			}

			if (TryFindRulePack(interaction.Extension, out result))
			{
				return result;
			}

			return null;
		}

		[SyncMethod]
		private bool TryFindRulePack(InteractionSelectorExtension extension, out RulePackDef def)
		{
			def = null;

			if (extension.rulepacks == null || extension.rulepacks.Any() == false)
			{
				return false;
			}

			def = extension.rulepacks.RandomElement();

			return def != null;
		}

		[SyncMethod]
		private bool TryFindRulePack(InteractionExtension extension, out RulePackDef def)
		{
			def = null;

			//no defs
			if (extension.rulepack_defs == null || extension.rulepack_defs.Any() == false)
			{
				return false;
			}

			string defname = extension.rulepack_defs.RandomElement();

			//null name ? should not happen
			if (String.IsNullOrWhiteSpace(defname) == true)
			{
				return false;
			}

			def = RulePackDef.Named(defname);

			return def != null;
		}
	}
}


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

namespace rjw.Modules.Interactions.Objects
{
	public class Interaction
	{
		public InteractionPawn Dominant { get; set; }
		public InteractionPawn Submissive { get; set; }

		public InteractionPawn Initiator { get; set; }
		public InteractionPawn Receiver { get; set; }

		public InteractionWithExtension InteractionDef { get; set; }

		public InteractionType InteractionType { get; internal set; }
		public xxx.rjwSextype RjwSexType { get; set; }

		public RulePackDef RulePack { get; set; }

		public IList<ILewdablePart> SelectedDominantParts { get; set; }
		public IList<ILewdablePart> SelectedSubmissiveParts { get; set; }
	}
}


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

namespace rjw.Modules.Interactions.Objects
{
	public class InteractionPawn
	{
		public Pawn Pawn { get; set; }
		public SexablePawnParts Parts { get; set; }

		public Genitals.Enums.Gender Gender { get; set; }

		public IEnumerable<Parts.ILewdablePart> ListLewdableParts()
		{
			foreach (var part in Parts.Penises)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.Penis);
			}

			foreach (var part in Parts.Vaginas)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.Vagina);
			}

			foreach (var part in Parts.Anuses)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.Anus);
			}

			foreach (var part in Parts.FemaleOvipositors)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.FemaleOvipositor);
			}

			foreach (var part in Parts.MaleOvipositors)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.MaleOvipositor);
			}

			foreach (var part in Parts.Breasts)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.Breasts);
			}

			foreach (var part in Parts.Udders)
			{
				yield return new Parts.RJWLewdablePart(part, LewdablePartKind.Udders);
			}

			foreach (var part in Parts.Mouths)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Mouth);
			}

			foreach (var part in Parts.Beaks)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Beak);
			}

			foreach (var part in Parts.Tongues)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Tongue);
			}

			foreach (var part in Parts.Hands)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Hand);
			}

			foreach (var part in Parts.Feet)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Foot);
			}

			foreach (var part in Parts.Tails)
			{
				yield return new Parts.VanillaLewdablePart(Pawn, part, LewdablePartKind.Tail);
			}
		}

		public IList<LewdablePartKind> BlockedParts { get; set; }
		public IDictionary<LewdablePartKind, float> PartPreferences { get; set; }

		public bool HasBigBreasts()
		{
			return Parts.Breasts
				.BigBreasts()
				.Any();
		}
	}
}


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

namespace rjw.Modules.Interactions.Objects
{
	public class InteractionScore
	{
		/// <summary>
		/// The dominant score, calculated using part preference
		/// </summary>
		public float Dominant { get; set; }
		/// <summary>
		/// The submissive score, calculated using part preference
		/// </summary>
		public float Submissive { get; set; }
		/// <summary>
		/// The interaction score, value from the settings
		/// </summary>
		public float Setting { get; set; }

		/// <summary>
		/// When compiling the final score of an interaction, we take into account a weight that can be applied to
		/// the submissive score
		/// for exemple, the submissive's preferences should not be taken into account in a rape
		/// </summary>
		/// <returns>
		/// the final interaction score
		/// </returns>
		//public float GetScore(float submissiveWeight)
		//{
		//	//if it's ignored, pulls toward 1
		//	float finalSubmissiveScore;

		//	{
		//		float invertedWeight = Mathf.Max(0, 1 - submissiveWeight);

		//		//no, i'm not sure that it's right
		//		finalSubmissiveScore = (1 * invertedWeight) + (Submissive * submissiveWeight);
		//	}

		//	return Dominant * finalSubmissiveScore * Setting;
		//}
		public float GetScore(float submissiveWeight)
		{
			// Get weighted average of dom and subs prefs and mutiply by the system weight

			// Fix sub weights of less than zero
			float fixedSubmissiveWeight = Mathf.Max(0, submissiveWeight);
			float dominantWeight = 1f;

			return Setting * (
				(Dominant * dominantWeight + Submissive * fixedSubmissiveWeight) /
				(dominantWeight + fixedSubmissiveWeight)
			);
		}
	}
}


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

namespace rjw.Modules.Interactions.Objects
{
	public class InteractionWithExtension
	{
		public InteractionDef Interaction { get; set; }
		public InteractionSelectorExtension SelectorExtension { get; set; }
		public InteractionExtension Extension { get; set; }

		#region InteractionTag

		public bool HasInteractionTag(InteractionTag tag)
		{
			if (SelectorExtension == null || SelectorExtension.tags == null || SelectorExtension.tags.Any() == false)
			{
				return false;
			}

			return SelectorExtension.tags.Contains(tag);
		}

		#endregion

		#region PartProps

		public bool DominantHasPartProp(string partProp)
		{
			return HasPartProp(SelectorExtension.dominantRequirement, partProp);
		}

		public bool SubmissiveHasPartProp(string partProp)
		{
			return HasPartProp(SelectorExtension.submissiveRequirement, partProp);
		}

		private bool HasPartProp(InteractionRequirement requirement, string partProp)
		{
			if (requirement == null || requirement.partProps == null || requirement.partProps.Any() == false)
			{
				return false;
			}

			return requirement.partProps.Contains(partProp);
		}

		#endregion

		#region Familly

		public bool DominantHasFamily(GenitalFamily family)
		{
			return HasFamily(SelectorExtension.dominantRequirement, family);
		}

		public bool SubmissiveHasFamily(GenitalFamily family)
		{
			return HasFamily(SelectorExtension.submissiveRequirement, family);
		}

		private bool HasFamily(InteractionRequirement requirement, GenitalFamily family)
		{
			if (requirement == null || requirement.families == null || requirement.families.Any() == false)
			{
				return false;
			}

			return requirement.families.Contains(family);
		}

		#endregion

		#region Tag

		public bool DominantHasTag(GenitalTag tag)
		{
			return HasTag(SelectorExtension.dominantRequirement, tag);
		}

		public bool SubmissiveHasTag(GenitalTag tag)
		{
			return HasTag(SelectorExtension.submissiveRequirement, tag);
		}

		private bool HasTag(InteractionRequirement requirement, GenitalTag tag)
		{
			if (requirement == null || requirement.tags == null || requirement.tags.Any() == false)
			{
				return false;
			}

			return requirement.tags.Contains(tag);
		}

		#endregion
	}
}


code:
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.Objects
{
	public class LewdablePartComparer : IEqualityComparer<ILewdablePart>
	{
		public bool Equals(ILewdablePart x, ILewdablePart y)
		{
			RJWLewdablePart rjwPartX = x as RJWLewdablePart;
			RJWLewdablePart rjwPartY = y as RJWLewdablePart;

			VanillaLewdablePart vanillaPartX = x as VanillaLewdablePart;
			VanillaLewdablePart vanillaPartY = y as VanillaLewdablePart;

			//One of them is rjw
			if (rjwPartX != null || rjwPartY != null)
			{
				//Compare the hediffs
				if (rjwPartX?.Part != rjwPartY?.Part)
				{
					return false;
				}
			}

			//One of them is vanilla
			if (vanillaPartX != null || vanillaPartY != null)
			{
				//Compare the BPR
				if (vanillaPartX?.Part != vanillaPartY?.Part)
				{
					return false;
				}
			}

			return true;
		}

		public int GetHashCode(ILewdablePart obj)
		{
			RJWLewdablePart rjwPart = obj as RJWLewdablePart;
			VanillaLewdablePart vanillaPart = obj as VanillaLewdablePart;

			if (rjwPart != null)
			{
				rjwPart.Part.GetHashCode();
			}

			if (vanillaPart != null)
			{
				vanillaPart.Part.GetHashCode();
			}

			return 0;
		}
	}
}


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.Objects.Parts
{
	public interface ILewdablePart
	{
		LewdablePartKind PartKind { get; }

		/// <summary>
		/// The severity of the hediff
		/// </summary>
		float Size { get; }

		IList<string> Props { get; }
	}
}


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

namespace rjw.Modules.Interactions.Objects.Parts
{
	public class RJWLewdablePart : ILewdablePart
	{
		public ISexPartHediff Part { get; private set; }

		public LewdablePartKind PartKind { get; private set; }

		public float Size => Part.AsHediff.Severity;

		private readonly IList<string> _props;
		public IList<string> Props => _props;

		public RJWLewdablePart(ISexPartHediff hediff, LewdablePartKind partKind)
		{
			Part = hediff;
			PartKind = partKind;
			_props = hediff.Def.partTags ?? new();
		}
	}
}


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

namespace rjw.Modules.Interactions.Objects.Parts
{
	public class VanillaLewdablePart : ILewdablePart
	{
		public Pawn Owner { get; private set; }
		public BodyPartRecord Part { get; private set; }

		public LewdablePartKind PartKind { get; private set; }

		public float Size
		{
			get
			{
				//For reference, averge size penis = 0.25f

				switch (PartKind)
				{
					case LewdablePartKind.Hand:
						return 0.3f * Owner.BodySize;
					case LewdablePartKind.Foot:
						return 0.4f * Owner.BodySize;
					case LewdablePartKind.Tail:
						return 0.15f * Owner.BodySize;
					default:
						return 0.25f;
				}
			}
		}

		//No props in vanilla parts ... sad day ...
		public IList<string> Props => new List<string>();

		public VanillaLewdablePart(Pawn owner, BodyPartRecord part, LewdablePartKind partKind)
		{
			Owner = owner;
			Part = part;
			PartKind = partKind;
		}
	}
}


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

namespace rjw.Modules.Interactions.Objects
{
	public class SexablePawnParts
	{
		public IEnumerable<BodyPartRecord> Mouths { get; set; }
		public IEnumerable<BodyPartRecord> Beaks { get; set; }
		public IEnumerable<BodyPartRecord> Tongues { get; set; }
		public IEnumerable<BodyPartRecord> Feet { get; set; }
		public IEnumerable<BodyPartRecord> Hands { get; set; }
		public IEnumerable<BodyPartRecord> Tails { get; set; }

		public bool HasMouth => Mouths == null ? false : Mouths.Any();
		public bool HasHand => Hands == null ? false : Hands.Any();
		public bool HasTail => Tails == null ? false : Tails.Any();

		public IEnumerable<ISexPartHediff> AllParts { get; set; }

		public IEnumerable<ISexPartHediff> Penises { get; set; }
		public IEnumerable<ISexPartHediff> Vaginas { get; set; }
		public IEnumerable<ISexPartHediff> Breasts { get; set; }
		public IEnumerable<ISexPartHediff> Udders { get; set; }
		public IEnumerable<ISexPartHediff> Anuses { get; set; }

		public IEnumerable<ISexPartHediff> FemaleOvipositors { get; set; }
		public IEnumerable<ISexPartHediff> MaleOvipositors { get; set; }
	}
}


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.Rules.InteractionRules
{
	public interface IInteractionRule
	{
		InteractionType InteractionType { get; }

		IEnumerable<InteractionWithExtension> Interactions { get; }

		float SubmissivePreferenceWeight { get; }

		InteractionWithExtension Default { get; }
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class AnimalInteractionRule : IInteractionRule
	{
		static AnimalInteractionRule()
		{
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Animal;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForAnimal();

		public float SubmissivePreferenceWeight
		{
			get
			{
				return 0.0f;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultAnimalSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class BestialityInteractionRule : IInteractionRule
	{
		static BestialityInteractionRule()
		{
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Bestiality;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForBestiality();

		public float SubmissivePreferenceWeight
		{
			get
			{
				return 0.0f;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultBestialitySex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class ConsensualInteractionRule : IInteractionRule
	{
		static ConsensualInteractionRule()
		{
			_random = new Random();
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static Random _random;
		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Consensual;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForConsensual();

		public float SubmissivePreferenceWeight
		{
			get
			{
				//+/- 20%
				float variant = (-1 + (float)_random.NextDouble() * 2) * 0.2f;

				return 1.0f + variant;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultConsensualSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class MasturbationInteractionRule : IInteractionRule
	{
		static MasturbationInteractionRule()
		{
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Masturbation;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForMasturbation();

		public float SubmissivePreferenceWeight
		{
			get
			{
				return 0.0f;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultAnimalSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class MechImplantInteractionRule : IInteractionRule
	{
		static MechImplantInteractionRule()
		{
			_random = new Random();
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static Random _random;
		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Mechanoid;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForMechanoid();

		public float SubmissivePreferenceWeight
		{
			get
			{
				//+/- 20%
				float variant = -1 + (float)_random.NextDouble() * 2;

				return 1.0f + variant;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultMechImplantSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class NecrophiliaInteractionRule : IInteractionRule
	{
		static NecrophiliaInteractionRule()
		{
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Necrophilia;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForNecrophilia();

		public float SubmissivePreferenceWeight
		{
			get
			{
				return 0.0f;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultNecrophiliaSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class RapeInteractionRule : IInteractionRule
	{
		static RapeInteractionRule()
		{
			_random = new Random();
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static Random _random;
		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Rape;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForRape();

		public float SubmissivePreferenceWeight
		{
			get
			{
				//+/- 20%
				float variant = -1 + (float)_random.NextDouble() * 2;

				return 0.0f + variant;
			}
		}

		public InteractionWithExtension Default => InteractionDefOf.DefaultRapeSex;
	}
}


code:
using rjw.Modules.Interactions.Defs;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Internals;
using rjw.Modules.Interactions.Internals.Implementation;
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.Rules.InteractionRules.Implementation
{
	public class WhoringInteractionRule : IInteractionRule
	{
		static WhoringInteractionRule()
		{
			_random = new Random();
			_interactionRepository = InteractionRepository.Instance;
		}

		private readonly static Random _random;
		private readonly static IInteractionRepository _interactionRepository;

		public InteractionType InteractionType => InteractionType.Whoring;

		public IEnumerable<InteractionWithExtension> Interactions => _interactionRepository
			.ListForWhoring();

		public float SubmissivePreferenceWeight
		{
			get
			{
				return (float)_random.NextDouble(); // Full random !
			}
		}
		public InteractionWithExtension Default => InteractionDefOf.DefaultWhoringSex;
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartBlockedRules
{
	public interface IPartBlockedRule
	{
		IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn);
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartBlockedRules.Implementation
{
	public class DeadPartBlockedRule : IPartBlockedRule
	{
		public static IPartBlockedRule Instance { get; private set; }

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

			_pawnStateService = PawnStateService.Instance;
		}

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

		private static readonly IPawnStateService _pawnStateService;

		public IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn)
		{
			yield break;

			//if (_pawnStateService.Detect(pawn.Pawn) == Shared.Enums.PawnState.Dead)
			//{
			//}
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartBlockedRules.Implementation
{
	public class DownedPartBlockedRule : IPartBlockedRule
	{
		public static IPartBlockedRule Instance { get; private set; }

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

			_pawnStateService = PawnStateService.Instance;
		}

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

		private static readonly IPawnStateService _pawnStateService;

		public IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn)
		{
			yield break;

			//if (_pawnStateService.Detect(pawn.Pawn) == Shared.Enums.PawnState.Downed)
			//{
			//}
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartBlockedRules.Implementation
{
	public class MainPartBlockedRule : IPartBlockedRule
	{
		public static IPartBlockedRule Instance { get; private set; }

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

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

		public IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn)
		{
			if (Genital_Helper.anus_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Anus;
			}
			if (Genital_Helper.penis_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Penis;
			}
			if (Genital_Helper.breasts_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Breasts;
			}
			if (Genital_Helper.vagina_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Vagina;
			}
			if (Genital_Helper.hands_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Hand;
			}
			if (Genital_Helper.oral_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Mouth;
				yield return LewdablePartKind.Beak;
				yield return LewdablePartKind.Tongue;
			}
			if (Genital_Helper.genitals_blocked(pawn.Pawn))
			{
				yield return LewdablePartKind.Penis;
				yield return LewdablePartKind.Vagina;
				yield return LewdablePartKind.FemaleOvipositor;
				yield return LewdablePartKind.MaleOvipositor;
			}
		}
	}
}


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.Rules.PartBlockedRules.Implementation
{
	public class PartAvailibilityPartBlockedRule : IPartBlockedRule
	{
		public static IPartBlockedRule Instance { get; private set; }

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

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

		public IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn)
		{
			if (pawn.Parts.Hands.Any() == false)
			{
				yield return LewdablePartKind.Hand;
			}
			if (pawn.Parts.Mouths.Any() == false)
			{
				yield return LewdablePartKind.Mouth;
			}
			if (pawn.Parts.Tails.Any() == false)
			{
				yield return LewdablePartKind.Tail;
			}
			//No feet detection, pawn always have thoose ... guess you can still do a good job with a peg leg !
			//if (pawn.Parts.Feet.Any() == false)
			//{
			//	yield return LewdablePartKind.Foot;
			//}

			if (pawn.Parts.Penises.Any() == false)
			{
				yield return LewdablePartKind.Penis;
			}
			if (pawn.Parts.Vaginas.Any() == false)
			{
				yield return LewdablePartKind.Vagina;
			}
			if (pawn.Parts.FemaleOvipositors.Any() == false)
			{
				yield return LewdablePartKind.FemaleOvipositor;
			}
			if (pawn.Parts.MaleOvipositors.Any() == false)
			{
				yield return LewdablePartKind.MaleOvipositor;
			}
			if (pawn.Parts.Anuses.Any() == false)
			{
				yield return LewdablePartKind.Anus;
			}
			if (pawn.Parts.Breasts.Where(e => e.AsHediff.CurStageIndex > 1).Any() == false)
			{
				yield return LewdablePartKind.Breasts;
			}
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartBlockedRules.Implementation
{
	public class UnconsciousPartBlockedRule : IPartBlockedRule
	{
		public static IPartBlockedRule Instance { get; private set; }

		static UnconsciousPartBlockedRule()
		{
			Instance = new UnconsciousPartBlockedRule();
			_pawnStateService = PawnStateService.Instance;
		}

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

		private static readonly IPawnStateService _pawnStateService;

		public IEnumerable<LewdablePartKind> BlockedParts(InteractionPawn pawn)
		{
			yield break;

			//if (_pawnStateService.Detect(pawn.Pawn) == Shared.Enums.PawnState.Unconscious)
			//{
			//}
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules
{
	public interface IPartPreferenceRule
	{
		IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context);
		IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context);
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class AnimalPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			if (xxx.is_animal(context.Internals.Dominant.Pawn))
			{
				return ForAnimal();
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			if (xxx.is_animal(context.Internals.Submissive.Pawn))
			{
				return ForAnimal();
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		private IEnumerable<Weighted<LewdablePartKind>> ForAnimal()
		{
			yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Vagina);
			yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Penis);
			yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.FemaleOvipositor);
			yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.MaleOvipositor);

			yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Foot);
			yield return new Weighted<LewdablePartKind>(Multipliers.Never, LewdablePartKind.Tail);
			yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Hand);
			yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Hand);
			yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Breasts);

			yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Mouth);
			yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Tongue);
			yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Beak);
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class BestialityForZoophilePartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			//Only bestiality here
			if (context.Internals.InteractionType != InteractionType.Bestiality)
			{
				return Enumerable.Empty<Weighted<LewdablePartKind>>();
			}

			InteractionPawn pawn = context.Internals.Dominant;

			if (xxx.is_zoophile(pawn.Pawn) && xxx.is_animal(pawn.Pawn) == false)
			{
				return ForPawn(pawn, context.Internals.Submissive);
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			//Only bestiality here
			if (context.Internals.InteractionType != InteractionType.Bestiality)
			{
				return Enumerable.Empty<Weighted<LewdablePartKind>>();
			}

			InteractionPawn pawn = context.Internals.Submissive;

			if (xxx.is_zoophile(pawn.Pawn) && xxx.is_animal(pawn.Pawn) == false)
			{
				return ForPawn(pawn, context.Internals.Dominant);
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		private IEnumerable<Weighted<LewdablePartKind>> ForPawn(InteractionPawn pawn, InteractionPawn animal)
		{
			//bonded
			if (pawn.Pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, animal.Pawn))
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryFrequent, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryFrequent, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryFrequent, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryFrequent, LewdablePartKind.MaleOvipositor);

				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Anus);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Breasts);
			}
			else
			//faction animal
			if (pawn.Pawn.Faction == animal.Pawn.Faction)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.MaleOvipositor);

				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Anus);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Foot);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Beak);
			}
			//wild or other faction
			else
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.MaleOvipositor);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Anus);

				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Foot);

				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Beak);
			}
		}
	}
}


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

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class BestialityPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			//Only bestiality here
			if (context.Internals.InteractionType != InteractionType.Bestiality)
			{
				return Enumerable.Empty<Weighted<LewdablePartKind>>();
			}

			InteractionPawn pawn = context.Internals.Dominant;

			if (xxx.is_zoophile(pawn.Pawn) == false && xxx.is_animal(pawn.Pawn) == false && context.Internals.InteractionType == InteractionType.Bestiality)
			{
				return ForPawn(pawn, context.Internals.Submissive, context.Inputs.Initiator == pawn.Pawn);
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			//Only bestiality here
			if (context.Internals.InteractionType != InteractionType.Bestiality)
			{
				return Enumerable.Empty<Weighted<LewdablePartKind>>();
			}

			InteractionPawn pawn = context.Internals.Submissive;

			if (xxx.is_zoophile(pawn.Pawn) == false && xxx.is_animal(pawn.Pawn) == false)
			{
				return ForPawn(pawn, context.Internals.Dominant, context.Inputs.Initiator == pawn.Pawn);
			}

			return Enumerable.Empty<Weighted<LewdablePartKind>>();
		}

		private IEnumerable<Weighted<LewdablePartKind>> ForPawn(InteractionPawn pawn, InteractionPawn animal, bool isInitiator)
		{
			//Well ... the pawn probably would not want anything penetrative ...
			if (isInitiator == false)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.MaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Anus);

				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Foot);

				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Beak);
			}

			//bonded
			if (pawn.Pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, animal.Pawn))
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.MaleOvipositor);

				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Anus);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Foot);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Beak);
			}
			else
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.MaleOvipositor);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Anus);

				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Foot);

				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Beak);
			}
		}
	}
}