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

license: mit


code:
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;

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

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

		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 rjw.Modules.Interactions.DefModExtensions;
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 HediffWithExtension
	{
		public Hediff Hediff { get; set; }
		public GenitalPartExtension GenitalPart { get; set; }
		public PartProps PartProps { get; set; }
	}
}


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?.Hediff != rjwPartY?.Hediff)
				{
					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.Hediff.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;

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

		public LewdablePartKind PartKind { get; private set; }

		public float Size => Hediff.Hediff.Severity;

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

		public RJWLewdablePart(HediffWithExtension hediff, LewdablePartKind partKind)
		{
			Hediff = hediff;
			PartKind = partKind;
			_props = hediff.PartProps?.props ?? 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<HediffWithExtension> AllParts { get; set; }

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

		public IEnumerable<HediffWithExtension> FemaleOvipositors { get; set; }
		public IEnumerable<HediffWithExtension> 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;

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.Hediff.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);
			}
		}
	}
}


code:
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 BigBreastsPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			return Modifiers(context.Internals.Dominant);
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			return Modifiers(context.Internals.Submissive);
		}

		public IEnumerable<Weighted<LewdablePartKind>> Modifiers(InteractionPawn pawn)
		{
			if (pawn.HasBigBreasts())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Foot);
				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 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 MainPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			return Modifiers(context.Internals.Dominant);
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			return Modifiers(context.Internals.Submissive);
		}

		private IEnumerable<Weighted<LewdablePartKind>> Modifiers(InteractionPawn pawn)
		{
			bool hasMainPart = false;

			if (pawn.Parts.Penises.Any())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.Penis);
				hasMainPart = true;
			}
			if (pawn.Parts.Vaginas.Any())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.Vagina);
				hasMainPart = true;
			}
			if (pawn.Parts.FemaleOvipositors.Any())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.FemaleOvipositor);
				hasMainPart = true;
			}
			if (pawn.Parts.MaleOvipositors.Any())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Frequent, LewdablePartKind.MaleOvipositor);
				hasMainPart = true;
			}

			//Since the pawn has a "main" part, we lower the rest
			if (hasMainPart == true)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Anus);
				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.Tail);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Breasts);

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


code:
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 PawnAlreadySatisfiedPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			return Modifiers(context.Internals.Dominant);
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			return Modifiers(context.Internals.Submissive);
		}

		public IEnumerable<Weighted<LewdablePartKind>> Modifiers(InteractionPawn pawn)
		{
			Need_Sex need = pawn.Pawn.needs?.TryGetNeed<Need_Sex>();

			if (need != null && need.CurLevel >= need.thresh_neutral())
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Anus);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.MaleOvipositor);
			}
		}
	}
}


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

using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Shared;
using rjw.Modules.Interactions.Objects;
using System.Linq;

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class PregnancyApproachPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
            return ModifiersForEither(context.Internals.Dominant, context.Internals.Submissive);
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
            return ModifiersForEither(context.Internals.Submissive, context.Internals.Dominant);
		}

        public IEnumerable<Weighted<LewdablePartKind>> ModifiersForEither(InteractionPawn OwO, InteractionPawn UwU)
        {
            if (OwO.Pawn.relations == null || UwU.Pawn.relations == null)
            {
                yield break;
            }

            float weight = OwO.Pawn.relations.GetPregnancyApproachForPartner(UwU.Pawn).GetPregnancyChanceFactor();
            if (OwO.Parts.Vaginas.Any() && UwU.Parts.Penises.Any())
            {
                yield return new Weighted<LewdablePartKind>(weight, LewdablePartKind.Penis);
            }
            if (OwO.Parts.Penises.Any() && UwU.Parts.Vaginas.Any())
            {
                yield return new Weighted<LewdablePartKind>(weight, LewdablePartKind.Vagina);
            }
        }
	}
}


code:
using RimWorld;
using rjw.Modules.Interactions.Contexts;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using rjw.Modules.Quirks;
using rjw.Modules.Quirks.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.Rules.PartKindUsageRules.Implementation
{
	public class QuirksPartKindUsageRule : IPartPreferenceRule
	{
		private IQuirkService _quirkService => QuirkService.Instance;

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			return Enumerable.Concat(
				ModifierForPodophile(context.Internals.Dominant, context.Internals.Submissive, true),
				ModifierForImpregnationFetish(context.Internals.Dominant, context.Internals.Submissive, true)
				);

		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			return Enumerable.Concat(
				ModifierForPodophile(context.Internals.Submissive, context.Internals.Dominant, false),
				ModifierForImpregnationFetish(context.Internals.Submissive, context.Internals.Dominant, false)
				);
		}

		private IEnumerable<Weighted<LewdablePartKind>> ModifierForPodophile(InteractionPawn pawn, InteractionPawn partner, bool isDominant)
		{
			if(_quirkService.HasQuirk(pawn.Pawn, Quirks.Quirks.Podophile))
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Doubled, LewdablePartKind.Foot);
			}

			//Partner is podophile and dominant (aka requesting pawjob !)
			if(_quirkService.HasQuirk(partner.Pawn, Quirks.Quirks.Podophile) && isDominant == false)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Doubled, LewdablePartKind.Foot);
			}
		}

		private IEnumerable<Weighted<LewdablePartKind>> ModifierForImpregnationFetish(InteractionPawn pawn, InteractionPawn partner, bool isDominant)
		{
			if (_quirkService.HasQuirk(pawn.Pawn, Quirks.Quirks.Impregnation))
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Doubled, LewdablePartKind.Vagina);
			}

			//Partner likes impregnation and is requesting the deal !
			if (_quirkService.HasQuirk(partner.Pawn, Quirks.Quirks.Impregnation) && isDominant == false)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Doubled, LewdablePartKind.Penis);
			}
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
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;

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class RapePartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			if (context.Internals.InteractionType == InteractionType.Rape)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Anus);
				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);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Breasts);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.MaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Tail);

				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.Hand);
				yield return new Weighted<LewdablePartKind>(Multipliers.Average, LewdablePartKind.Foot);
			}
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			if (context.Internals.InteractionType == InteractionType.Rape)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Anus);
				yield return new Weighted<LewdablePartKind>(Multipliers.AlmostNever, LewdablePartKind.Vagina);

				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);
				yield return new Weighted<LewdablePartKind>(Multipliers.VeryRare, LewdablePartKind.Breasts);

				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Penis);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.FemaleOvipositor);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.MaleOvipositor);

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


code:
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 SizeDifferencePartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			return Modifiers(context.Internals.Dominant, context.Internals.Submissive);
		}

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			return Modifiers(context.Internals.Submissive, context.Internals.Dominant);
		}

		public IEnumerable<Weighted<LewdablePartKind>> Modifiers(InteractionPawn current, InteractionPawn partner)
		{
			if (current.Pawn.BodySize * 2 < partner.Pawn.BodySize)
			{
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Vagina);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.Anus);
				yield return new Weighted<LewdablePartKind>(Multipliers.Rare, LewdablePartKind.FemaleOvipositor);
			}
		}
	}
}


code:
using rjw.Modules.Interactions.Contexts;
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;

namespace rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation
{
	public class WhoringPartKindUsageRule : IPartPreferenceRule
	{
		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForDominant(InteractionContext context)
		{
			if (context.Internals.InteractionType == InteractionType.Whoring)
			{
				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.Common, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Beak);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Anus);

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

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

		public IEnumerable<Weighted<LewdablePartKind>> ModifiersForSubmissive(InteractionContext context)
		{
			if (context.Internals.InteractionType == InteractionType.Whoring)
			{
				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.Common, LewdablePartKind.Mouth);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Tongue);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Beak);
				yield return new Weighted<LewdablePartKind>(Multipliers.Common, LewdablePartKind.Breasts);
				yield return new Weighted<LewdablePartKind>(Multipliers.Uncommon, LewdablePartKind.Anus);

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

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


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

namespace rjw
{
	[StaticConstructorOnStartup]
	public static class RJW_Multiplayer
	{
		static RJW_Multiplayer()
		{
			if (!MP.enabled) return;

			// This is where the magic happens and your attributes
			// auto register, similar to Harmony's PatchAll.
			MP.RegisterAll();

				/*
				Log.Message("RJW MP compat testing");
				var type = AccessTools.TypeByName("rjw.RJWdesignations");
				//Log.Message("rjw MP compat " + type.Name);
				Log.Message("is host " + MP.IsHosting);
				Log.Message("PlayerName " + MP.PlayerName);
				Log.Message("IsInMultiplayer " + MP.IsInMultiplayer);

				//MP.RegisterSyncMethod(type, "Comfort");
				/*
				MP.RegisterSyncMethod(type, "<GetGizmos>Service");
				MP.RegisterSyncMethod(type, "<GetGizmos>BreedingHuman");
				MP.RegisterSyncMethod(type, "<GetGizmos>BreedingAnimal");
				MP.RegisterSyncMethod(type, "<GetGizmos>Breeder");
				MP.RegisterSyncMethod(type, "<GetGizmos>Milking");
				MP.RegisterSyncMethod(type, "<GetGizmos>Hero");
				*/

			// You can choose to not auto register and do it manually
			// with the MP.Register* methods.

			// Use MP.IsInMultiplayer to act upon it in other places
			// user can have it enabled and not be in session
		}

		//generate PredictableSeed for Verse.Rand
		public static int PredictableSeed()
		{
			int seed = 0;
			try
			{
				Map map = Find.CurrentMap;
				//int seedHourOfDay = GenLocalDate.HourOfDay(map);
				//int seedDayOfYear = GenLocalDate.DayOfYear(map);
				//int seedYear = GenLocalDate.Year(map);
				seed = (GenLocalDate.HourOfDay(map) + GenLocalDate.DayOfYear(map)) * GenLocalDate.Year(map);
				//int seed = (seedHourOfDay + seedDayOfYear) * seedYear;
				//Log.Warning("seedHourOfDay: " + seedHourOfDay + "\nseedDayOfYear: " + seedDayOfYear + "\nseedYear: " + seedYear + "\n" + seed);
			}
			catch
			{
				seed = Rand.Int;
			}
			return seed;
		}

		//generate PredictableSeed for Verse.Rand
		[SyncMethod]
		public static float RJW_MP_RAND()
		{
			return Rand.Value;
		}
	}
}


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

namespace rjw.Modules.Nymphs
{
	public interface INymphService
	{
		Pawn GenerateNymph(Map map, PawnKindDef nymphKind = null, Faction faction = null);
		IEnumerable<Pawn> GenerateNymphs(Map map, int count);

		PawnKindDef RandomNymphKind();
		IEnumerable<PawnKindDef> ListNymphKindDefs();

		void SetManhunter(Pawn nymph);

	}
}


code:
using RimWorld;
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.Nymphs.Implementation
{
	public class NymphService : INymphService
	{
		private static ILog _log = LogManager.GetLogger<NymphService, NymphLogProvider>();

		public static readonly INymphService Instance;

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

		public Pawn GenerateNymph(Map map, PawnKindDef nymphKind = null, Faction faction = null)
		{
			if (nymphKind == null)
			{
				nymphKind = RandomNymphKind();
			}

			PawnGenerationRequest request = new PawnGenerationRequest(
				kind: nymphKind,
				faction: faction,
				tile: map.Tile,
				forceGenerateNewPawn: true,
				canGeneratePawnRelations: true,
				colonistRelationChanceFactor: 0.0f,
				inhabitant: true,
				relationWithExtraPawnChanceFactor: 0
				);

			Pawn nymph = PawnGenerator.GeneratePawn(request);

			_log.Debug($"Generated Nymph {nymph.GetName()}");

			return nymph;
		}

		public IEnumerable<Pawn> GenerateNymphs(Map map, int count)
		{
			if (count <= 0)
			{
				yield break;
			}

			PawnKindDef nymphKind = RandomNymphKind();

			for (int i = 0; i < count; i++)
			{
				yield return GenerateNymph(map, nymphKind);
			}
		}

		public PawnKindDef RandomNymphKind()
		{
			return ListNymphKindDefs()
				.RandomElement();
		}
		public IEnumerable<PawnKindDef> ListNymphKindDefs()
		{
			return DefDatabase<PawnKindDef>.AllDefs
				.Where(x => x.defName.Contains("Nymph"));
		}

		public void SetManhunter(Pawn nymph)
		{
			if (RJWSettings.NymphPermanentManhunter)
			{
				nymph.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.ManhunterPermanent);
			}
			else
			{
				nymph.mindState.mentalStateHandler.TryStartMentalState(MentalStateDefOf.Manhunter);
			}
		}
	}
}


code:
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Nymphs.Implementation;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using UnityEngine;
using Verse;

namespace rjw.Modules.Nymphs.Incidents
{
	public abstract class IncidentWorker_BaseNymphRaid : IncidentWorker_NeutralGroup
	{
		private static ILog _log = LogManager.GetLogger<IncidentWorker_BaseNymphRaid, NymphLogProvider>();

		protected static readonly INymphService _nymphGeneratorService;

		static IncidentWorker_BaseNymphRaid()
		{
			_nymphGeneratorService = NymphService.Instance;
		}

		protected virtual float ThreatPointMultiplier => 1f;

		[SyncMethod]
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			//No multiplayer support ... I guess
			if (MP.IsInMultiplayer)
			{
				_log.Debug("Could not fire because multiplayer api is on");
				return false;
			}

			//No map
			if (parms.target is Map == false)
			{
				_log.Debug("Could not fire because the incident target is not a map");
				return false;
			}

			return base.CanFireNowSub(parms);
		}

		protected virtual int GetNymphCount(IncidentParms parms, Map map)
		{
			//Calculating nymphs manyness
			int count;
			count = Mathf.RoundToInt(parms.points / parms.pawnKind.combatPower * ThreatPointMultiplier);
			//Cap the min
			count = Mathf.Max(count, 1);

			return count;
		}

		[SyncMethod]
		protected override void ResolveParmsPoints(IncidentParms parms)
		{
			if (parms.points <= 0)
			{
				parms.points = StorytellerUtility.DefaultThreatPointsNow(parms.target) * ThreatPointMultiplier;
			}
			_log.Debug($"Incident generated with {parms.points} points");
		}

		[SyncMethod]
		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			_log.Debug($"Generating incident");

			//Walk from the edge
			parms.raidArrivalMode = PawnsArrivalModeDefOf.EdgeWalkIn;
			if (parms.raidArrivalMode.Worker.TryResolveRaidSpawnCenter(parms) == false)
			{
				_log.Debug($"Incident failed to fire, no spawn points available");
				return false;
			}

			//Find PawnKind
			parms.pawnKind = Nymph_Generator.GetFixedNymphPawnKindDef();

			if(parms.pawnKind == null)
			{
				_log.Debug($"Incident failed to fire, no Pawn Kind for nymphs");
				return false;
			}

			//Calculating nymphs manyness
			int count = GetNymphCount(parms, (Map)parms.target);

			_log.Debug($"Will generate {count} nymphs");

			List<Pawn> nymphs = GenerateNymphs(parms.target as Map, count);

			parms.raidArrivalMode.Worker.Arrive(nymphs, parms);

			SetManhunters(nymphs);

			Find.LetterStack.ReceiveLetter(
				"RJW_nymph_incident_raid_title".Translate(),
				"RJW_nymph_incident_raid_description".Translate(),
				LetterDefOf.ThreatBig,
				nymphs);

			return true;
		}

		protected List<Pawn> GenerateNymphs(Map map, int count)
		{
			List<Pawn> result = new List<Pawn>();

			PawnKindDef nymphKind = _nymphGeneratorService.RandomNymphKind();

			for (int i = 1; i <= count; ++i)
			{
				Pawn nymph = _nymphGeneratorService.GenerateNymph(map, nymphKind);

				//Set it wild
				nymph.ChangeKind(PawnKindDefOf.WildMan);

				result.Add(nymph);
			}

			return result;
		}

		protected void SetManhunters(List<Pawn> nymphs)
		{
			foreach (var nymph in nymphs)
			{
				_nymphGeneratorService.SetManhunter(nymph);
			}
		}
	}
}


code:
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Nymphs.Implementation;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using Verse;

namespace rjw.Modules.Nymphs.Incidents
{
	public class IncidentWorker_NymphJoin : IncidentWorker
	{
		private static ILog _log = LogManager.GetLogger<IncidentWorker_NymphJoin, NymphLogProvider>();

		protected static readonly INymphService _nymphGeneratorService;

		static IncidentWorker_NymphJoin()
		{
			_nymphGeneratorService = NymphService.Instance;
		}

		[SyncMethod]
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			//The event is disabled, don't fire !
			if (RJWSettings.NymphTamed == false)
			{
				_log.Debug("The incident can't fire as it is disabled in RJW settings");
				return false;
			}

			//No multiplayer support
			if (MP.IsInMultiplayer)
			{
				_log.Debug("Could not fire because multiplayer api is on");
				return false;
			}

			//No map
			if (parms.target is Map == false)
			{
				_log.Debug("Could not fire because the incident target is not a map");
				return false;
			}

			return base.CanFireNowSub(parms);
		}

		[SyncMethod]
		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			_log.Debug($"Generating incident");

			//Walk from the edge
			parms.raidArrivalMode = PawnsArrivalModeDefOf.EdgeWalkIn;
			if (parms.raidArrivalMode.Worker.TryResolveRaidSpawnCenter(parms) == false)
			{
				_log.Debug($"Incident failed to fire, no spawn points available");
				return false;
			}

			Pawn nymph = GenerateNymph(parms.target as Map);

			_log.Debug($"Generated nymph {nymph.GetName()}");

			parms.raidArrivalMode.Worker.Arrive(new List<Pawn>() { nymph }, parms);

			JoinPlayerFaction(nymph);

			Find.LetterStack.ReceiveLetter(
				"RJW_nymph_incident_join_title".Translate(),
				"RJW_nymph_incident_join_description".Translate(),
				LetterDefOf.PositiveEvent,
				nymph);

			return true;
		}

		protected Pawn GenerateNymph(Map map)
		{
			Pawn nymph = _nymphGeneratorService.GenerateNymph(map);

			nymph.ChangeKind(PawnKindDefOf.WildMan);

			return nymph;
		}

		protected void JoinPlayerFaction(Pawn nymph)
		{
			nymph.SetFaction(Faction.OfPlayer);
		}
	}
}


code:
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Shared.Logs;
using UnityEngine;
using Verse;

namespace rjw.Modules.Nymphs.Incidents
{
	public class IncidentWorker_NymphRaidEasy : IncidentWorker_BaseNymphRaid
	{
		private static ILog _log = LogManager.GetLogger<IncidentWorker_NymphRaidEasy, NymphLogProvider>();

		[SyncMethod]
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			//The event is disabled, don't fire !
			if (RJWSettings.NymphRaidEasy == false)
			{
				_log.Debug("The incident can't fire as it is disabled in RJW settings");
				return false;
			}

			return base.CanFireNowSub(parms);
		}
		protected override int GetNymphCount(IncidentParms parms, Map map)
		{
			int count;

			//Calculating nymphs manyness
			if (RJWSettings.NymphRaidRP)
			{
				count = base.GetNymphCount(parms, map);
			}
			else
			{
				count = (Find.World.worldPawns.AllPawnsAlive.Count + map.mapPawns.FreeColonistsAndPrisonersSpawnedCount);
				//Cap the max
				count = Mathf.Min(count, 100);
				//Cap the min
				count = Mathf.Max(count, 1);
			}

			return count;
		}
	}
}


code:
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Shared.Logs;
using UnityEngine;
using Verse;

namespace rjw.Modules.Nymphs.Incidents
{
	public class IncidentWorker_NymphRaidHard : IncidentWorker_BaseNymphRaid
	{
		private static ILog _log = LogManager.GetLogger<IncidentWorker_NymphRaidHard, NymphLogProvider>();

		protected override float ThreatPointMultiplier => 1.5f;

		[SyncMethod]
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			//The event is disabled, don't fire !
			if (RJWSettings.NymphRaidHard == false)
			{
				_log.Debug("The incident can't fire as it is disabled in RJW settings");
				return false;
			}

			return base.CanFireNowSub(parms);
		}
		protected override int GetNymphCount(IncidentParms parms, Map map)
		{
			int count;

			//Calculating nymphs manyness
			if (RJWSettings.NymphRaidRP)
			{
				count = base.GetNymphCount(parms, map);
			}
			else
			{
				count = map.mapPawns.AllPawnsSpawnedCount;
				//Cap the max
				count = Mathf.Min(count, 1000);
				//Cap the min
				count = Mathf.Max(count, 1);
			}

			return count;
		}
	}
}


code:
using Multiplayer.API;
using RimWorld;
using rjw.Modules.Nymphs.Implementation;
using rjw.Modules.Shared.Extensions;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using Verse;

namespace rjw.Modules.Nymphs.Incidents
{
	public class IncidentWorker_NymphVisitor : IncidentWorker
	{
		private static ILog _log = LogManager.GetLogger<IncidentWorker_NymphVisitor, NymphLogProvider>();

		protected static readonly INymphService _nymphService;

		static IncidentWorker_NymphVisitor()
		{
			_nymphService = NymphService.Instance;
		}

		[SyncMethod]
		protected override bool CanFireNowSub(IncidentParms parms)
		{
			//The event is disabled, don't fire !
			if (RJWSettings.NymphWild == false)
			{
				_log.Debug("The incident can't fire as it is disabled in RJW settings");
				return false;
			}

			//No multiplayer support ... I guess
			if (MP.IsInMultiplayer)
			{
				_log.Debug("Could not fire because multiplayer api is on");
				return false;
			}

			//No map
			if (parms.target is Map == false)
			{
				_log.Debug("Could not fire because the incident target is not a map");
				return false;
			}

			return base.CanFireNowSub(parms);
		}

		[SyncMethod]
		protected override bool TryExecuteWorker(IncidentParms parms)
		{
			_log.Debug($"Generating incident");

			//Walk from the edge
			parms.raidArrivalMode = PawnsArrivalModeDefOf.EdgeWalkIn;
			if (parms.raidArrivalMode.Worker.TryResolveRaidSpawnCenter(parms) == false)
			{
				_log.Debug($"Incident failed to fire, no spawn points available");
				return false;
			}

			Pawn nymph = GenerateNymph(parms.target as Map);

			_log.Debug($"Generated nymph {nymph.GetName()}");

			parms.raidArrivalMode.Worker.Arrive(new List<Pawn>() { nymph }, parms);

			SetManhunter(nymph);

			Find.LetterStack.ReceiveLetter(
				"RJW_nymph_incident_wander_title".Translate(),
				"RJW_nymph_incident_wander_description".Translate(),
				LetterDefOf.ThreatSmall,
				nymph);

			return true;
		}

		protected Pawn GenerateNymph(Map map)
		{
			Pawn nymph = _nymphService.GenerateNymph(map);

			nymph.ChangeKind(PawnKindDefOf.WildMan);

			return nymph;
		}

		protected void SetManhunter(Pawn nymph)
		{
			_nymphService.SetManhunter(nymph);
		}
	}
}


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

namespace rjw
{
	// Token: 0x020006B5 RID: 1717
	public class JobGiver_NymphSapper : ThinkNode_JobGiver
	{
		// Token: 0x06002E54 RID: 11860 RVA: 0x001041A7 File Offset: 0x001023A7
		public override ThinkNode DeepCopy(bool resolve = true)
		{
			JobGiver_NymphSapper jobGiver_NymphSapper = (JobGiver_NymphSapper)base.DeepCopy(resolve);
			jobGiver_NymphSapper.canMineMineables = this.canMineMineables;
			jobGiver_NymphSapper.canMineNonMineables = this.canMineNonMineables;
			return jobGiver_NymphSapper;
		}

		// Token: 0x06002E55 RID: 11861 RVA: 0x001041D0 File Offset: 0x001023D0
		protected override Job TryGiveJob(Pawn pawn)
		{
			if (!RJWSettings.NymphSappers)
				return null;
			if (!xxx.is_nympho(pawn))
				return null;

			IntVec3 intVec;
			{
				IAttackTarget attackTarget;
				if (!(from x in pawn.Map.mapPawns.FreeColonistsAndPrisonersSpawned
				where !x.ThreatDisabled(pawn) && pawn.CanReach(x, PathEndMode.OnCell, Danger.Deadly, false, false, TraverseMode.PassAllDestroyableThings)
				select x).TryRandomElement(out attackTarget))
				{
					return null;
				}
				intVec = attackTarget.Thing.Position;
			}
			using (PawnPath pawnPath = pawn.Map.pathFinder.FindPath(pawn.Position, intVec, TraverseParms.For(pawn, Danger.Deadly, TraverseMode.PassDoors, false), PathEndMode.OnCell))
			{
				IntVec3 cellBeforeBlocker;
				Thing thing = pawnPath.FirstBlockingBuilding(out cellBeforeBlocker, pawn);
				if (thing != null)
				{
					Job job = DigUtility.PassBlockerJob(pawn, thing, cellBeforeBlocker, this.canMineMineables, this.canMineNonMineables);
					if (job != null)
					{
						return job;
					}
				}
			}
			return JobMaker.MakeJob(JobDefOf.Goto, intVec, 500, true);
		}

		// Token: 0x04001A64 RID: 6756
		private bool canMineMineables = false;

		// Token: 0x04001A65 RID: 6757
		private bool canMineNonMineables = false;

		// Token: 0x04001A66 RID: 6758
		private const float ReachDestDist = 10f;

		// Token: 0x04001A67 RID: 6759
		private const int CheckOverrideInterval = 500;
	}
}


code:
using rjw.Modules.Shared.Logs;

namespace rjw.Modules.Nymphs
{
	public class NymphLogProvider : ILogProvider
	{
		public bool IsActive => RJWSettings.DebugNymph;
	}
}


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

namespace rjw
{
	public struct nymph_passion_chances
	{
		public float major;
		public float minor;

		public nymph_passion_chances(float maj, float min)
		{
			major = maj;
			minor = min;
		}
	}

	public static class nymph_backstories
	{

		public static nymph_passion_chances get_passion_chances(BackstoryDef child_bs, BackstoryDef adult_bs, SkillDef skill_def)
		{
			var maj = 0.0f;
			var min = 0.0f;

			if (adult_bs.defName.Contains("feisty"))
			{
				if (skill_def == SkillDefOf.Melee) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Shooting) { maj = 0.25f; min = 0.75f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.10f; min = 0.67f; }
			}
			else if (adult_bs.defName.Contains("curious"))
			{
				if (skill_def == SkillDefOf.Construction) { maj = 0.15f; min = 0.40f; }
				else if (skill_def == SkillDefOf.Crafting) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.20f; min = 1.00f; }
			}
			else if (adult_bs.defName.Contains("tender"))
			{
				if (skill_def == SkillDefOf.Medicine) { maj = 0.20f; min = 0.60f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.50f; min = 1.00f; }
			}
			else if (adult_bs.defName.Contains("chatty"))
			{
				if (skill_def == SkillDefOf.Social) { maj = 1.00f; min = 1.00f; }
			}
			else if (adult_bs.defName.Contains("broken"))
			{
				if (skill_def == SkillDefOf.Artistic) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.00f; min = 0.33f; }
			}
			else if (adult_bs.defName.Contains("homekeeper"))
			{
				if (skill_def == SkillDefOf.Cooking) { maj = 0.50f; min = 1.00f; }
				else if (skill_def == SkillDefOf.Social) { maj = 0.00f; min = 0.33f; }
			}

			return new nymph_passion_chances(maj, min);
		}

		// Randomly chooses backstories and traits for a nymph
		[SyncMethod]
		public static void generate(Pawn pawn)
		{
			var tr = pawn.story;
			//reset stories
			tr.Childhood = DefDatabase<BackstoryDef>.AllDefsListForReading.Where(x => x.slot == BackstorySlot.Childhood && x.spawnCategories.Contains("rjw_nymphsCategory")).RandomElement();
			tr.Adulthood = DefDatabase<BackstoryDef>.AllDefsListForReading.Where(x => x.slot == BackstorySlot.Adulthood && x.spawnCategories.Contains("rjw_nymphsCategory")).RandomElement();

			pawn.story.traits.allTraits.AddDistinct(new Trait(xxx.nymphomaniac, 0));

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var beauty = 0;
			var rv2 = Rand.Value;

			if (tr.Adulthood.defName.Contains("feisty"))
			{
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Brawler));
				else if (rv2 < 0.67)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Bloodlust));
				else
					pawn.story.traits.allTraits.AddDistinct(new Trait(xxx.rapist));
			}
			else if (tr.Adulthood.defName.Contains("curious"))
			{
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Transhumanist));
			}
			else if (tr.Adulthood.defName.Contains("tender"))
			{
				beauty = Rand.RangeInclusive(1, 2);
				if (rv2 < 0.50)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Kind));
			}
			else if (tr.Adulthood.defName.Contains("chatty"))
			{
				beauty = 2;
				if (rv2 < 0.33)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Greedy));
			}
			else if (tr.Adulthood.defName.Contains("broken"))
			{
				beauty = Rand.RangeInclusive(0, 2);
				if (rv2 < 0.33)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.DrugDesire, 1));
				else if (rv2 < 0.67)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.DrugDesire, 2));
			}
			else if (tr.Adulthood.defName.Contains("homekeeper"))
			{
				beauty = Rand.RangeInclusive(1, 2);
				if (rv2 < 0.33)
					pawn.story.traits.allTraits.AddDistinct(new Trait(TraitDefOf.Kind));
			}

			if (beauty > 0)
				pawn.story.traits.allTraits.AddDistinct(new Trait(VanillaTraitDefOf.Beauty, beauty, false));
		}
	}
}


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

namespace rjw
{
	public static class Nymph_Generator
	{
		/// <summary>
		/// Returns true if the given pawnGenerationRequest is for a nymph pawnKind.
		/// </summary>
		public static bool IsNymph(PawnGenerationRequest pawnGenerationRequest)
		{
			return pawnGenerationRequest.KindDef != null && pawnGenerationRequest.KindDef.defName.Contains("Nymph");
		}

		private static bool is_trait_conflicting_or_duplicate(Pawn pawn, Trait t)
		{
			foreach (var existing in pawn.story.traits.allTraits)
				if ((existing.def == t.def) || (t.def.ConflictsWith(existing)))
					return true;
			return false;
		}

		public static bool IsNymphBodyType(Pawn pawn)
		{
			return pawn.story.bodyType == BodyTypeDefOf.Female || pawn.story.bodyType == BodyTypeDefOf.Thin;
		}

		[SyncMethod]
		public static Gender RandomNymphGender()
		{
			//with males 100% its still 99%, coz im  to lazy to fix it
			//float rnd = Rand.Value;
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			float chance = RJWSettings.male_nymph_chance;

			Gender Pawngender = Rand.Chance(chance) ? Gender.Male: Gender.Female;
			//ModLog.Message(" setnymphsex: " + (rnd < chance) + " rnd:" + rnd + " chance:" + chance);
			//ModLog.Message(" setnymphsex: " + Pawngender);
			return Pawngender;
		}

		/// <summary>
		/// Replaces a pawn's randomly generated backstory and traits to turn it into propper a nymph
		/// </summary>
		[SyncMethod]
		public static void set_story(Pawn pawn)
		{
			//C# rjw story
			nymph_backstories.generate(pawn);

			//The mod More Trait Slots will adjust the max number of traits pawn can get, and therefore,
			//I need to collect pawns' traits and assign other_traits back to the pawn after adding the nymph_story traits.

			//backup generated + rjw traits
			Stack<Trait> other_traits = new Stack<Trait>();
			int numberOfTotalTraits = 0;
			if (!pawn.story.traits.allTraits.NullOrEmpty())
			{
				foreach (Trait t in pawn.story.traits.allTraits)
				{
					other_traits.Push(t);
					++numberOfTotalTraits;
				}
			}
			pawn.story.traits.allTraits.Clear();

			//xml nymph story forced traits
			var trait_count = 0;
			foreach (var t in pawn.story.Adulthood.forcedTraits)
			{
				pawn.story.traits.GainTrait(new Trait(t.def, t.degree));
				++trait_count;
			}

			//xml story + rjw backup + w/e generator generated backup
			while (trait_count < numberOfTotalTraits)
			{
				Trait t = other_traits.Pop();
				if (!is_trait_conflicting_or_duplicate(pawn, t))
					pawn.story.traits.GainTrait(t);
				++trait_count;
			}

			// add broken body hediff to broken nymph
			if (pawn.story.Adulthood.defName.Contains("broken"))
			{
				pawn.health.AddHediff(xxx.feelingBroken);
				(pawn.health.hediffSet.GetFirstHediffOfDef(xxx.feelingBroken)).Severity = Rand.Range(0.4f, 1.0f);
			}

			//reset disabled skills/works
			pawn.skills.Notify_SkillDisablesChanged();
			pawn.Notify_DisabledWorkTypesChanged();
		}

		[SyncMethod]
		private static int sum_previous_gains(SkillDef def, Pawn_StoryTracker sto, Pawn_AgeTracker age)
		{
			int total_gain = 0;
			SkillGain gain;

			// Gains from backstories
			if ((gain = sto.Childhood.skillGains.FirstOrDefault((SkillGain skillGain) => skillGain.skill == def)) != null)
				total_gain += gain.amount;
			if ((gain = sto.Adulthood.skillGains.FirstOrDefault((SkillGain skillGain) => skillGain.skill == def)) != null)
				total_gain += gain.amount;

			// Gains from traits
			foreach (var trait in sto.traits.allTraits)
				if ((gain = trait.CurrentData.skillGains.FirstOrDefault((SkillGain skillGain) => skillGain.skill == def)) != null)
					total_gain += gain.amount;

			// Gains from age
			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			var rgain = Rand.Value * (float)total_gain * 0.35f;
			var age_factor = Mathf.Clamp01((age.AgeBiologicalYearsFloat - 17.0f) / 10.0f); // Assume nymphs are 17~27
			total_gain += (int)(age_factor * rgain);

			return Mathf.Clamp(total_gain, 0, 20);
		}

		/// <summary>
		/// Set a nymph's initial skills & passions from backstory, traits, and age
		/// </summary>
		[SyncMethod]
		public static void set_skills(Pawn pawn)
		{
			foreach (var skill_def in DefDatabase<SkillDef>.AllDefsListForReading)
			{
				var rec = pawn.skills.GetSkill(skill_def);
				if (!rec.TotallyDisabled)
				{
					//Rand.PopState();
					//Rand.PushState(RJW_Multiplayer.PredictableSeed());
					rec.Level = sum_previous_gains(skill_def, pawn.story, pawn.ageTracker);
					rec.xpSinceLastLevel = rec.XpRequiredForLevelUp * Rand.Range(0.10f, 0.90f);

					var pas_cha = nymph_backstories.get_passion_chances(pawn.story.Childhood, pawn.story.Adulthood, skill_def);
					var rv = Rand.Value;
					if (rv < pas_cha.major) rec.passion = Passion.Major;
					else if (rv < pas_cha.minor) rec.passion = Passion.Minor;
					else rec.passion = Passion.None;
				}
				else
					rec.passion = Passion.None;
			}
		}

		[SyncMethod]
		public static PawnKindDef GetFixedNymphPawnKindDef()
		{
			var def = DefDatabase<PawnKindDef>.AllDefs.Where(x => x.defName.Contains("Nymph")).RandomElement();
			//var def = PawnKindDef.Named("Nymph");
			// This is 18 in the xml but something is overwriting it to 5.
			//def.minGenerationAge = 18;
			return def;
		}

		[SyncMethod]
		public static Pawn GenerateNymph(Faction faction = null)
		{
			// Most of the special properties of nymphs are in harmony patches to PawnGenerator.
			PawnGenerationRequest request = new PawnGenerationRequest(
				kind: GetFixedNymphPawnKindDef(),
				faction: faction,
				forceGenerateNewPawn: true,
				canGeneratePawnRelations: true,
				colonistRelationChanceFactor: 0.0f,
				relationWithExtraPawnChanceFactor: 0
				);

			//Rand.PopState();
			//Rand.PushState(RJW_Multiplayer.PredictableSeed());
			Pawn pawn = PawnGenerator.GeneratePawn(request);
			//Log.Message(""+ pawn.Faction);
			//IntVec3 spawn_loc = CellFinder.RandomClosewalkCellNear(around_loc, map, 6);//RandomSpawnCellForPawnNear could be an alternative
			//GenSpawn.Spawn(pawn, spawn_loc, map);
			return pawn;
		}
	}
}


code:
//using System;
//using RimWorld;
//using Verse;

//namespace rjw
//{
//	public class BackstoryDef : Def
//	{
//		public BackstoryDef()
//		{
//			this.slot = BackstorySlot.Childhood;
//		}

//		public static BackstoryDef Named(string defName)
//		{
//			return DefDatabase<BackstoryDef>.GetNamed(defName, true);
//		}

//		public override void ResolveReferences()
//		{
//			base.ResolveReferences();
//			if (BackstoryDatabase.allBackstories.ContainsKey(this.defName))
//			{
//				ModLog.Error("BackstoryDatabase already contains: " + this.defName);
//				return;
//			}
//			{
//				//Log.Warning("BackstoryDatabase does not contains: " + this.defName);
//			}
//			if (!this.title.NullOrEmpty())
//			{
//				Backstory backstory = new Backstory();
//				backstory.SetTitle(this.title, this.titleFemale);
//				backstory.SetTitleShort(this.titleShort, this.titleFemaleShort);
//				backstory.baseDesc = this.baseDescription;
//				backstory.slot = this.slot;
//				backstory.spawnCategories.Add(this.categoryName);
//				backstory.ResolveReferences();
//				backstory.PostLoad();
//				backstory.identifier = this.defName;
//				BackstoryDatabase.allBackstories.Add(backstory.identifier, backstory);
//				//Log.Warning("BackstoryDatabase added: " + backstory.identifier);
//				//Log.Warning("BackstoryDatabase added: " + backstory.spawnCategories.ToCommaList());
//			}
//		}

//		public string baseDescription;

//		public string title;

//		public string titleShort;

//		public string titleFemale;

//		public string titleFemaleShort;

//		public string categoryName;

//		public BackstorySlot slot;
//	}
//}