code:
using HarmonyLib;
using RimWorld;
using rjw;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions.Objects;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using Verse;

namespace RJW_Menstruation
{

    [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.impregnate))]
    public static class Impregnate_Patch
    {
        public static bool Prefix(SexProps props)
        {
            xxx.rjwSextype sextype = props.sexType;
            Pawn pawn = props.pawn;
            Pawn partner = props.partner;

            if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return true;

            if (!partner.ShouldCycle()) return true;

            if (!InteractionCanCausePregnancy(props)) return false;

            List<Hediff> pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));

            HediffComp_Menstruation comp;
            if (pawn.HasImpregnationFetish() || partner.HasImpregnationFetish() || partner.IsInEstrus())
                comp = partner.GetFertileMenstruationComp();
            else comp = partner.GetRandomMenstruationComp();
            if (comp == null) return true;

            if (Genital_Helper.has_penis_fertile(pawn, pawnparts) && PregnancyHelper.CanImpregnate(pawn, partner, sextype))
            {
                PregnancyHelper.DoImpregnate(pawn, partner);
                return false;
            }
            else if (Genital_Helper.has_ovipositorM(pawn, pawnparts))
            {
                comp.CumIn(pawn, Rand.Range(0.75f, 4.5f) * pawn.BodySize, pawn.SterileGenes() ? 0.0f : 1.0f);
            }
            else comp.CumIn(pawn, pawn.GetCumVolume(pawnparts), 0);

            return true;
        }

        public static void Postfix(SexProps props)
        {
            Pawn pawn = props.partner;

            if (props.sexType != xxx.rjwSextype.MechImplant && pawn.health.hediffSet.GetFirstHediff<Hediff_InsectEgg>() == null) return;

            // The existing pregnancies might have been destroyed, so go through see if any new mech pregnancies need to be picked up
            foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
            {
                _ = comp.Pregnancy; // get_Pregnancy will do any removals
                comp.TakeLoosePregnancy();
            }
        }

        /// <summary>
        /// Checks if pregnancy can happen based on the interaction def
        /// This is needed for futanari sex, but should work for everyone
        /// </summary>
        /// <param name="props"></param>
        /// <returns>Interaction can result in pregnancy</returns>
        public static bool InteractionCanCausePregnancy(SexProps props)
        {
            InteractionWithExtension interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(props.dictionaryKey);

            if (!interaction.HasInteractionTag(InteractionTag.Fertilization))
                return false;

            bool usesPawnsPenis;
            bool usesPartnersVagina;

            if (!props.isReceiver)
            {
                usesPawnsPenis = interaction.DominantHasTag(GenitalTag.CanPenetrate);
                usesPartnersVagina = interaction.SubmissiveHasFamily(GenitalFamily.Vagina);
            }
            else
            {
                usesPawnsPenis = interaction.SubmissiveHasTag(GenitalTag.CanPenetrate);
                usesPartnersVagina = interaction.DominantHasFamily(GenitalFamily.Vagina);
            }

            return usesPawnsPenis && usesPartnersVagina;
        }
    }

    [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.DoImpregnate))]
    public static class DoImpregnate_Patch
    {
        public static bool Prefix(Pawn pawn, Pawn partner) // partner has vagina
        {
            if (!partner.ShouldCycle()) return true;
            HediffComp_Menstruation comp;
            if (pawn.HasImpregnationFetish() || partner.HasImpregnationFetish() || partner.IsInEstrus())
                comp = partner.GetFertileMenstruationComp();
            else comp = partner.GetRandomMenstruationComp();
            if (comp == null)
            {
                if (Configurations.Debug) ModLog.Message("used original rjw method: Comp missing");
                return true;
            }
            else if (AndroidsCompatibility.IsAndroid(pawn) && !AndroidsCompatibility.AndroidPenisFertility(pawn))
            {
                comp.CumIn(pawn, pawn.GetCumVolume(), 0);
                return false;
            }
            else comp.CumIn(pawn, pawn.GetCumVolume(), pawn.SterileGenes() ? 0.0f : pawn.health.capacities.GetLevel(xxx.reproduction));
            return false;
        }
    }

    [HarmonyPatch(typeof(PregnancyHelper), nameof(PregnancyHelper.CanImpregnate))]
    public static class CanImpregnate_Patch
    {
        private static bool PregnancyBlocksImpregnation(this Pawn pawn, bool _)
        {
            if (!pawn.ShouldCycle()) return pawn.IsPregnant();
            else if (pawn.GetMenstruationComps().Any()) return false;
            else return pawn.IsPregnant();
        }
        private static readonly MethodInfo IsPregnant = AccessTools.Method(typeof(PawnExtensions), nameof(PawnExtensions.IsPregnant), new System.Type[] { typeof(Pawn), typeof(bool) });
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (IsPregnant?.ReturnType != typeof(bool)) throw new System.InvalidOperationException("IsPregnant not found");
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.Calls(IsPregnant))
                    yield return CodeInstruction.Call(typeof(CanImpregnate_Patch), nameof(PregnancyBlocksImpregnation));
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(Hediff_BasePregnancy), nameof(Hediff_BasePregnancy.PostBirth))]
    public static class RJW_Patch_PostBirth
    {
        public static void Postfix(Hediff_BasePregnancy __instance, Pawn mother, Pawn baby)
        {
            if (Configurations.EnableBirthVaginaMorph)
            {
                // The comp still has the pregnancy attached at this point in the process
                Hediff vagina = (__instance.GetMenstruationCompFromPregnancy()?.parent) ?? mother.health.hediffSet.hediffs.FirstOrFallback(x => VariousDefOf.AllVaginas.Contains(x.def));
                if (vagina == null) return;
                float morph = Mathf.Max(baby.BodySize - Mathf.Pow(vagina.Severity * mother.BodySize, 2), 0f);
                vagina.Severity += morph * Configurations.VaginaMorphPower;
            }
        }
    }

    [HarmonyPatch(typeof(Quirk), nameof(Quirk.IsSatisfiedBy))]
    public static class IsSatisfiedBy_Patch
    {
        public static void Postfix(Quirk __instance, ref bool __result, Pawn pawn, Pawn partner)
        {
            // This is stricter than can_impregnate, so quickly filter out scenarios that are negative anyways.
            if (__result == false || __instance != Quirk.ImpregnationFetish) return;
            __result =
                (PregnancyHelper.CanImpregnate(pawn, partner) && (partner.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true))
                ||
                (PregnancyHelper.CanImpregnate(partner, pawn) && (pawn.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true));
        }
    }

    [HarmonyPatch(typeof(Quirk), nameof(Quirk.CountSatisfiedQuirks))]
    public static class CountSatisfiedQuirks_Patch
    {
        public static void Postfix(ref int __result, SexProps props)
        {
            // Awkward, but it'll have to do
            Pawn pawn = props.pawn;
            if (__result == 0 || !pawn.HasImpregnationFetish() || !props.hasPartner()) return;

            // Check if the existing code would have added the count
            Pawn partner = props.partner;
            if (!(PregnancyHelper.CanImpregnate(pawn, partner, props.sexType) || PregnancyHelper.CanImpregnate(partner, pawn, props.sexType))) return;
            else __result--;

            if (
                (PregnancyHelper.CanImpregnate(pawn, partner, props.sexType) && (partner.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true))
                ||
                (PregnancyHelper.CanImpregnate(partner, pawn, props.sexType) && (pawn.GetMenstruationComps()?.Any(comp => comp.IsDangerDay) ?? true)))
                __result++;
        }
    }

    [HarmonyPatch(typeof(SexAppraiser), "GetBodyFactor")]
    public static class GetBodyFactor_Patch
    {
        private static float GetNetFertility(Pawn fucker, Pawn fucked)
        {
            float fert = fucked.health.capacities.GetLevel(xxx.reproduction);
            if (fucker.def.defName != fucked.def.defName)
            {
                if (RJWPregnancySettings.complex_interspecies)
                    fert *= SexUtility.BodySimilarity(fucker, fucked);
                else
                    fert *= RJWPregnancySettings.interspecies_impregnation_modifier;
            }
            return fert;
        }
        public static void Postfix(ref float __result, Pawn fucker, Pawn fucked)
        {
            if (fucker.IsInEstrus(true) && PregnancyHelper.CanImpregnate(fucked, fucker))
            {
                __result *= (1f + GetNetFertility(fucker, fucked) / 4);
            }
            else if (fucker.IsInEstrus(false) && PregnancyHelper.CanImpregnate(fucked, fucker))
            {
                __result *= (1f + GetNetFertility(fucker, fucked) / 40);
            }
            else if (xxx.is_animal(fucker) && fucked.IsInEstrus(true) && PregnancyHelper.CanImpregnate(fucker, fucked))
            {
                __result *= 1.25f;
            }
        }
    }

    [HarmonyPatch(typeof(CasualSex_Helper), nameof(CasualSex_Helper.roll_to_skip))]
    public static class Roll_To_Skip_Patch
    {
        private static float FuckabilityThreshold(Pawn pawn, Pawn partner)
        {
            return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn))
                ? Configurations.EstrusFuckabilityToHookup : RJWHookupSettings.MinimumFuckabilityToHookup;
        }

        private static readonly FieldInfo MinimumFuckabilityToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumFuckabilityToHookup));
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (MinimumFuckabilityToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumFuckabilityToHookup not found");
            bool first_fuckability = true;
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.LoadsField(MinimumFuckabilityToHookup))
                {
                    // The first load will be for the estrus-haver considering a partner, the second for a pawn considering the estrus-haver
                    yield return new CodeInstruction(first_fuckability ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1);
                    yield return new CodeInstruction(first_fuckability ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0);

                    yield return CodeInstruction.Call(typeof(Roll_To_Skip_Patch), nameof(FuckabilityThreshold));
                    first_fuckability = false;
                }
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(CasualSex_Helper), nameof(CasualSex_Helper.FindBestPartner))]
    public static class FindBestPartner_Patch
    {
        private static float AttractivenessThreshold(Pawn pawn, Pawn partner)
        {
            return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn))
                ? Configurations.EstrusAttractivenessToHookup : RJWHookupSettings.MinimumAttractivenessToHookup;
        }
        private static float RelationshipThreshold(Pawn pawn, Pawn partner)
        {
            return (Configurations.EstrusOverridesHookupSettings && pawn.IsInEstrus() && PregnancyHelper.CanImpregnate(partner, pawn))
                ? Configurations.EstrusRelationshipToHookup : RJWHookupSettings.MinimumRelationshipToHookup;
        }

        private static readonly FieldInfo MinimumAttractivenessToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumAttractivenessToHookup));
        private static readonly FieldInfo MinimumRelationshipToHookup = AccessTools.Field(typeof(RJWHookupSettings), nameof(RJWHookupSettings.MinimumRelationshipToHookup));
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (MinimumAttractivenessToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumAttractivenessToHookup not found");
            if (MinimumRelationshipToHookup?.FieldType != typeof(float)) throw new System.InvalidOperationException("MinimumRelationshipToHookup not found");
            LocalBuilder pawn_index = null;
            // Like in the last one, we switch the arguments around for the second load
            bool first_attractiveness = true;
            bool first_relationship = true;
            foreach (CodeInstruction instruction in instructions)
            {
                // Get where the compiler decided to index the pawn at
                if (pawn_index == null && instruction.opcode == OpCodes.Stloc_S)    // the first stloc.s in the IL is the pawn being loaded out of the list
                {                                                                   // a future RJW or compiler update might change this, or maybe another mod's patch
                    pawn_index = (LocalBuilder)instruction.operand;
                    yield return instruction;
                }
                else if (instruction.LoadsField(MinimumAttractivenessToHookup))
                {
                    if (pawn_index?.LocalType != typeof(Pawn))
                        throw new System.InvalidOperationException($"pawn_index is not a Pawn ({pawn_index?.LocalType})");

                    yield return first_attractiveness ? new CodeInstruction(OpCodes.Ldarg_0) : new CodeInstruction(OpCodes.Ldloc_S, pawn_index);
                    yield return first_attractiveness ? new CodeInstruction(OpCodes.Ldloc_S, pawn_index) : new CodeInstruction(OpCodes.Ldarg_0);

                    yield return CodeInstruction.Call(typeof(FindBestPartner_Patch), nameof(AttractivenessThreshold));
                    first_attractiveness = false;
                }
                else if (instruction.LoadsField(MinimumRelationshipToHookup))
                {
                    if (pawn_index?.LocalType != typeof(Pawn))
                        throw new System.InvalidOperationException($"pawn_index is not a Pawn ({pawn_index?.LocalType})");

                    yield return first_relationship ? new CodeInstruction(OpCodes.Ldarg_0) : new CodeInstruction(OpCodes.Ldloc_S, pawn_index);
                    yield return first_relationship ? new CodeInstruction(OpCodes.Ldloc_S, pawn_index) : new CodeInstruction(OpCodes.Ldarg_0);

                    yield return CodeInstruction.Call(typeof(FindBestPartner_Patch), nameof(RelationshipThreshold));
                    first_relationship = false;
                }
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(JobDriver_Sex), nameof(JobDriver_Sex.PlayCumSound))]
    public static class Orgasm_Patch
    {
        public static void Postfix(JobDriver_Sex __instance)
        {
#if false
            Pawn pawn = __instance.pawn;
            foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
            {
                comp.CumIn(pawn, (comp.parent.Severity / 10) * Rand.Range(0.75f, 1.25f), pawn.Label, -5.0f, VariousDefOf.GirlCumFilth);
            }
#endif
        }
    }

    [HarmonyPatch(typeof(JobDriver_Sex), nameof(JobDriver_Sex.SexTick))]
    public static class SexTick_Patch
    {
        private const float fertilePrecummersPercentage = 0.33f;
        private const float precumRatio = 0.1f; // Relative to ejaculation volume
        private const float precumFertility = 0.5f;
        private const float expectedDurationTicks = 2000f * (0.9f - 0.5f) / 2;

        public static void Postfix(JobDriver_Sex __instance, Pawn pawn, Thing target)
        {
            if (!pawn.IsHashIntervalTick(__instance.ticks_between_thrusts)) return;
            xxx.rjwSextype sextype = __instance.Sexprops.sexType;
            if (!(target is Pawn partner)) return;
            if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return;
            if (__instance.Sexprops.usedCondom) return;
            if (AndroidsCompatibility.IsAndroid(pawn)) return;
            if (!Impregnate_Patch.InteractionCanCausePregnancy(__instance.Sexprops)) return;
            if (!partner.ShouldCycle()) return;

            // Archotech penises have more control. Or something.
            CompHediffBodyPart penisComp = pawn.GetGenitalsList()?.Find(genital => (genital as Hediff_PartBaseNatural)?.def.defName.ToLower().Contains("penis") ?? false)?.TryGetComp<CompHediffBodyPart>();
            if (penisComp == null || Rand.ChanceSeeded(1.0f - fertilePrecummersPercentage, Gen.HashOffset(penisComp.parent.loadID))) return;
            HediffComp_Menstruation vaginaComp = partner.GetRandomMenstruationComp();
            if (vaginaComp == null) return;

            float precumAmount = pawn.GetCumVolume(penisComp) * precumRatio * __instance.ticks_between_thrusts / expectedDurationTicks;
            vaginaComp.CumIn(pawn, precumAmount, pawn.SterileGenes() ? 0.0f : precumFertility * pawn.health.capacities.GetLevel(xxx.reproduction), true);
        }
    }

    [HarmonyPatch(typeof(CompHediffBodyPart), nameof(CompHediffBodyPart.updatesize))]
    public static class Updatesize_Patch
    {
        public static void Postfix(CompHediffBodyPart __instance)
        {
            HediffComp_Breast comp = __instance.parent.GetBreastComp();
            if (comp != null)
            {
                __instance.parent.Severity += comp.BreastSizeIncreased;
            }

        }
    }

    [HarmonyPatch(typeof(PawnCapacityWorker_Fertility), "CalculateAgeImpact")]
    public static class PawnCapacityWorker_Fertility_Age_Patch
    {
        public static void Postfix(ref float __result, Pawn pawn)
        {
            if (__result <= 0.0f) return;
            if (pawn.GetMenstruationComps().Any(comp => comp.CalculatingOvulationChance))
                __result = 1.0f;
        }
    }
}


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

namespace RJW_Menstruation
{
    public static class PregnancyCommon
    {
        private static readonly MethodInfo TryGetInheritedXenotype = typeof(PregnancyUtility).GetMethod("TryGetInheritedXenotype", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn), typeof(XenotypeDef).MakeByRefType() }, null);
        private static readonly MethodInfo ShouldByHybrid = typeof(PregnancyUtility).GetMethod("ShouldByHybrid", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn) }, null);

        public static string GetBabyInfo(IEnumerable<Pawn> babies)
        {
            if (babies == null || !babies.Any()) return "Null";

            StringBuilder res = new StringBuilder();

            int iteration = 0;
            foreach (Pawn baby in babies.Distinct(new RaceComparer()))
            {
                int num = babies.Where(x => x.def.Equals(baby.def)).Count();
                if (iteration > 0) res.Append(", ");
                res.AppendFormat("{0} {1}", num, baby.def.label);
                iteration++;
            }
            res.AppendFormat(" {0}", Translations.Dialog_WombInfo02);
            return res.ToString();
        }

        public static string GetFatherInfo(IEnumerable<Pawn> babies, Pawn mother, bool is_parent_known)
        {
            if (babies == null || !babies.Any()) return "Null";

            StringBuilder res = new StringBuilder();
            res.AppendFormat("{0}: ", Translations.Dialog_WombInfo03);

            if (!is_parent_known && Configurations.InfoDetail != Configurations.DetailLevel.All)
                return res.Append(Translations.Dialog_FatherUnknown).ToString();

            int iteration = 0;
            foreach (Pawn baby in babies.Distinct(new FatherComparer(mother)))
            {
                if (iteration > 0) res.Append(", ");
                res.Append(Utility.GetFather(baby, mother)?.LabelShort ?? Translations.Dialog_FatherUnknown);
                iteration++;
            }
            return res.ToString();
        }

        /// <summary>
        /// Decide pawnkind from mother and father <para/>
        /// Come from RJW
        /// </summary>
        /// <param name="mother"></param>
        /// <param name="father"></param>
        /// <returns></returns>

        /// <param name="noAnimalsFromHumanlikes"></param>
        public static PawnKindDef BabyPawnKindDecider(Pawn mother, Pawn father, bool noAnimalsFromHumanlikes)
        {
            PawnKindDef motherKindDef = Utility.GetRacesPawnKind(mother);
            PawnKindDef fatherKindDef = Utility.GetRacesPawnKind(father);

            PawnKindDef spawn_kind_def = motherKindDef;

            int flag = 0;
            if (xxx.is_human(mother)) flag += 2;
            if (xxx.is_human(father)) flag += 1;
            //Mother - Father = Flag
            //Human  - Human  =  3
            //Human  - Animal =  2
            //Animal - Human  =  1
            //Animal - Animal =  0

            switch (flag)
            {
                case 3:
                    if (!Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother)) spawn_kind_def = fatherKindDef;
                    break;
                case 2:
                    if (RJWPregnancySettings.bestiality_DNA_inheritance == 0f) spawn_kind_def = fatherKindDef;
                    else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef;
                    break;
                case 1:
                    if (RJWPregnancySettings.bestiality_DNA_inheritance == 1f) spawn_kind_def = fatherKindDef;
                    else if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef;
                    break;
                case 0:
                    if (!Rand.Chance(RJWPregnancySettings.bestial_DNA_from_mother)) spawn_kind_def = fatherKindDef;
                    break;
            }

            bool IsAndroidmother = AndroidsCompatibility.IsAndroid(mother);
            bool IsAndroidfather = AndroidsCompatibility.IsAndroid(father);
            if (IsAndroidmother && !IsAndroidfather)
            {
                spawn_kind_def = fatherKindDef;
            }
            else if (!IsAndroidmother && IsAndroidfather)
            {
                spawn_kind_def = motherKindDef;
            }

            string MotherRaceName = "";
            string FatherRaceName = "";
            MotherRaceName = motherKindDef?.race?.defName;
            PawnKindDef non_hybrid_kind_def = spawn_kind_def;
            if (father != null)
                FatherRaceName = fatherKindDef?.race?.defName;

            if (FatherRaceName != "" && Configurations.UseHybridExtention)
            {
                spawn_kind_def = GetHybrid(father, mother);
                //Log.Message("pawnkind: " + spawn_kind_def?.defName);
            }

            if (MotherRaceName != FatherRaceName && FatherRaceName != "")
            {
                if (!Configurations.UseHybridExtention || spawn_kind_def == null)
                {
                    spawn_kind_def = non_hybrid_kind_def;
                    IEnumerable<RaceGroupDef> groups = DefDatabase<RaceGroupDef>.AllDefs.Where(x => !(x.hybridRaceParents.NullOrEmpty() || x.hybridChildKindDef.NullOrEmpty()));

                    //ModLog.Message(" found custom RaceGroupDefs " + groups.Count());
                    foreach (RaceGroupDef t in groups)
                    {
                        if ((t.hybridRaceParents.Contains(MotherRaceName) && t.hybridRaceParents.Contains(FatherRaceName))
                            || (t.hybridRaceParents.Contains("Any") && (t.hybridRaceParents.Contains(MotherRaceName) || t.hybridRaceParents.Contains(FatherRaceName))))
                        {
                            //ModLog.Message(" has hybridRaceParents");
                            if (t.hybridChildKindDef.Contains("MotherKindDef"))
                                spawn_kind_def = motherKindDef;
                            else if (t.hybridChildKindDef.Contains("FatherKindDef") && father != null)
                                spawn_kind_def = fatherKindDef;
                            else
                            {
                                //ModLog.Message(" trying hybridChildKindDef " + t.defName);
                                List<PawnKindDef> child_kind_def_list = new List<PawnKindDef>();
                                child_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => t.hybridChildKindDef.Contains(x.defName)));

                                //ModLog.Message(" found custom hybridChildKindDefs " + t.hybridChildKindDef.Count);
                                if (!child_kind_def_list.NullOrEmpty())
                                    spawn_kind_def = child_kind_def_list.RandomElement();
                            }
                        }
                    }
                }

            }
            else if (!Configurations.UseHybridExtention || spawn_kind_def == null)
            {
                spawn_kind_def = mother.RaceProps?.AnyPawnKind ?? motherKindDef;
            }

            if (spawn_kind_def.defName.Contains("Nymph"))
            {
                //child is nymph, try to find other PawnKindDef
                List<PawnKindDef> spawn_kind_def_list = new List<PawnKindDef>();
                spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == spawn_kind_def.race && !x.defName.Contains("Nymph")));
                //no other PawnKindDef found try mother
                if (spawn_kind_def_list.NullOrEmpty())
                    spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == motherKindDef.race && !x.defName.Contains("Nymph")));
                //no other PawnKindDef found try father
                if (spawn_kind_def_list.NullOrEmpty() && father != null)
                    spawn_kind_def_list.AddRange(DefDatabase<PawnKindDef>.AllDefs.Where(x => x.race == fatherKindDef.race && !x.defName.Contains("Nymph")));
                //no other PawnKindDef found fallback to generic colonist
                if (spawn_kind_def_list.NullOrEmpty())
                    spawn_kind_def = PawnKindDefOf.Colonist;

                if (!spawn_kind_def_list.NullOrEmpty()) spawn_kind_def = spawn_kind_def_list.RandomElement();
            }

            // If both parents are humanlike, Biotech will attempt to assign genes to the child
            // Normally not a problem, but with the hybrid system, two humanlikes might produce an animal
            // So override it and force the child to be human
            if (noAnimalsFromHumanlikes && mother.genes != null && father?.genes != null && !spawn_kind_def.race.race.Humanlike)
                spawn_kind_def = Rand.Chance(RJWPregnancySettings.humanlike_DNA_from_mother) ? motherKindDef : fatherKindDef;

            return spawn_kind_def;

        }

        public static void SetupBabyXenotype(Pawn mother, Pawn father, Pawn baby)
        {
            if (baby.genes == null || !ModsConfig.BiotechActive) return;

            if (GeneUtility.SameHeritableXenotype(mother, father) && mother.genes.UniqueXenotype)
            {
                baby.genes.xenotypeName = mother.genes.xenotypeName;
                baby.genes.iconDef = mother.genes.iconDef;
            }

            object[] args = new object[] { mother, father, null };
            if ((bool)TryGetInheritedXenotype.Invoke(null, args))
            {
                baby.genes.SetXenotypeDirect((XenotypeDef)args[2]);
            }
            else if ((bool)ShouldByHybrid.Invoke(null, new object[] { mother, father }))
            {
                baby.genes.hybrid = true;
                baby.genes.xenotypeName = "Hybrid".Translate();
            }
        }

        public static PawnKindDef GetHybrid(Pawn first, Pawn second)
        {
            PawnKindDef res = null;
            Pawn opposite = second;
            HybridInformations info = null;

            if (!Configurations.HybridOverride.NullOrEmpty())
            {
                info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == first.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == second.def?.defName) ?? false));
                if (info == null)
                {
                    info = Configurations.HybridOverride.FirstOrDefault(x => x.DefName == second.def?.defName && (x.hybridExtension?.Exists(y => y.DefName == first.def?.defName) ?? false));
                    opposite = first;
                }
            }

            if (info != null)
            {
                res = info.GetHybridWith(opposite.def.defName) ?? null;
            }
            if (res != null) return res;

            PawnDNAModExtension dna;
            dna = first.def.GetModExtension<PawnDNAModExtension>();
            if (dna != null)
            {
                res = dna.GetHybridWith(second.def.defName) ?? null;
            }
            else
            {
                dna = second.def.GetModExtension<PawnDNAModExtension>();
                if (dna != null)
                {
                    res = dna.GetHybridWith(first.def.defName) ?? null;
                }
            }
            return res;
        }

        private static void CopyBodyPartProperties(Hediff part, Hediff originalPart)
        {
            CompHediffBodyPart comp = part.TryGetComp<CompHediffBodyPart>();
            CompHediffBodyPart originalComp = originalPart.TryGetComp<CompHediffBodyPart>();

            if (comp != null && originalComp != null)
            {
                // the string properties should be the same between both pawns anyways, besides the name of the owner
                part.Severity = originalPart.Severity;
                comp.SizeBase = originalComp.SizeBase;
                comp.SizeOwner = originalComp.SizeOwner;
                comp.EffSize = originalComp.EffSize;
                comp.FluidAmmount = originalComp.FluidAmmount;
                comp.FluidModifier = originalComp.FluidModifier;
            }

            HediffComp_Menstruation originalMenstruationComp = originalPart.GetMenstruationCompFromVagina();
            if (originalMenstruationComp != null)
            {
                part.GetMenstruationCompFromVagina()?.CopyCycleProperties(originalMenstruationComp);
            }
            HediffComp_Breast originalBreastComp = originalPart.GetBreastComp();
            if (originalBreastComp != null)
            {
                part.GetBreastComp()?.CopyBreastProperties(originalBreastComp);
            }
        }

        private static void CopyBodyPartRecord(Pawn baby, Pawn original, BodyPartRecord babyBPR, BodyPartRecord originalBPR)
        {
            if (babyBPR == null || originalBPR == null) return;

            Hediff_BasePregnancy.RemoveBabyParts(baby, Genital_Helper.get_PartsHediffList(baby, babyBPR));
            foreach (Hediff originalPart in Genital_Helper.get_PartsHediffList(original, originalBPR))
            {
                Hediff part = SexPartAdder.MakePart(originalPart.def, baby, babyBPR);
                CopyBodyPartProperties(part, originalPart);
                baby.health.AddHediff(part, babyBPR);
            }
        }

        // Baby is the sibling to be changed, original is the first of the set and the one to copy to the rest.
        public static void ProcessIdenticalSibling(Pawn baby, Pawn original)
        {
            // They'll be the same pawnkind, which lets us make a lot of useful assumptions
            // However, some RNG might still be involved in genital generation (e.g. futas), so the easiest method is to clear out and re-generate
            // A bit wasteful since Hediff_BasePregnancy.PostBirth already redid the genitals
            CopyBodyPartRecord(baby, original, Genital_Helper.get_genitalsBPR(baby), Genital_Helper.get_genitalsBPR(original));
            CopyBodyPartRecord(baby, original, Genital_Helper.get_breastsBPR(baby), Genital_Helper.get_breastsBPR(original));
            CopyBodyPartRecord(baby, original, Genital_Helper.get_uddersBPR(baby), Genital_Helper.get_uddersBPR(original));
            CopyBodyPartRecord(baby, original, Genital_Helper.get_anusBPR(baby), Genital_Helper.get_anusBPR(original));
        }

        public static string DueDate(this Hediff_BasePregnancy preg)
        {
            if (preg.pawn.Tile == -1) return "";
            return GenDate.DateFullStringWithHourAt(GenDate.TickGameToAbs((int)preg.p_end_tick), Find.WorldGrid.LongLatOf(preg.pawn.Tile));
        }

        public static string DueDate(this Hediff_Pregnant preg)
        {
            if (preg.pawn.Tile == -1) return "";
            int ticksRemaining = (int)((1f - preg.GestationProgress) * preg.pawn.RaceProps.gestationPeriodDays * GenDate.TicksPerDay);
            int dueTickAbs = GenTicks.TicksAbs + ticksRemaining;
            return GenDate.DateFullStringWithHourAt(dueTickAbs, Find.WorldGrid.LongLatOf(preg.pawn.Tile));
        }
    }
}

license: unknown


code:
using rjw;
using Verse;

namespace RJW_Menstruation
{
    public static class QuirkUtility
    {
        // All quirks used in Menstruation
        private enum Quirks
        {
            Breeder,
            ImpregnationFetish,
            Messy,
            Teratophile,
        }
        private static bool HasQuirk(Pawn pawn, Quirks quirk)
        {
            switch (quirk)
            {
                case Quirks.Breeder:
                    return pawn.Has(Quirk.Breeder);
                case Quirks.ImpregnationFetish:
                    return pawn.Has(Quirk.ImpregnationFetish);
                case Quirks.Messy:
                    return pawn.Has(Quirk.Messy);
                case Quirks.Teratophile:
                    return pawn.Has(Quirk.Teratophile);
                default:
                    return false;
            }
        }
        public static bool IsBreeder(this Pawn pawn) => HasQuirk(pawn, Quirks.Breeder);
        public static bool HasImpregnationFetish(this Pawn pawn) => HasQuirk(pawn, Quirks.ImpregnationFetish);
        public static bool IsMessy(this Pawn pawn) => HasQuirk(pawn, Quirks.Messy);
        public static bool IsTeratophile(this Pawn pawn) => HasQuirk(pawn, Quirks.Teratophile);
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{EED2F3B9-8C20-4194-919E-8D151B29F70B}</ProjectGuid>
    <OutputType>Library</OutputType>
    <RootNamespace>RJW_Menstruation</RootNamespace>
    <AssemblyName>RJW_Menstruation</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <Deterministic>true</Deterministic>
    <PublishUrl>게시\</PublishUrl>
    <Install>true</Install>
    <InstallFrom>Disk</InstallFrom>
    <UpdateEnabled>false</UpdateEnabled>
    <UpdateMode>Foreground</UpdateMode>
    <UpdateInterval>7</UpdateInterval>
    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
    <UpdatePeriodically>false</UpdatePeriodically>
    <UpdateRequired>false</UpdateRequired>
    <MapFileExtensions>true</MapFileExtensions>
    <ApplicationRevision>0</ApplicationRevision>
    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
    <IsWebBootstrapper>false</IsWebBootstrapper>
    <UseApplicationTrust>false</UseApplicationTrust>
    <BootstrapperEnabled>true</BootstrapperEnabled>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>..\..\..\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>..\..\..\Assemblies\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup>
    <StartupObject />
  </PropertyGroup>
  <PropertyGroup>
    <SignAssembly>false</SignAssembly>
  </PropertyGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Compatibility\AnimalGeneticsCompatibility.cs" />
    <Compile Include="Compatibility\HARCompatibility.cs" />
    <Compile Include="CompBiosculpterPod_EggRestorationCycle.cs" />
    <Compile Include="Configurations.cs" />
    <Compile Include="Cum.cs" />
    <Compile Include="DebugActions.cs" />
    <Compile Include="DrugOutcomeDoers.cs" />
    <Compile Include="EstrusPartKindUsageRule.cs" />
    <Compile Include="HediffComps\HediffComp_InducedOvulator.cs" />
    <Compile Include="HediffComps\HediffComp_PeriodicOvulator.cs" />
    <Compile Include="HediffComps\HediffComp_Pheromones.cs" />
    <Compile Include="HediffComps\HediffComp_PregeneratedBabies.cs" />
    <Compile Include="HediffComps\MenstruationUtility.cs" />
    <Compile Include="Hediff_Estrus.cs" />
    <Compile Include="IngestionOutcomeDoers.cs" />
    <Compile Include="MenstruationModExtension.cs" />
    <Compile Include="Patch\Biotech_Patch.cs" />
    <Compile Include="Patch\GC_Patch.cs" />
    <Compile Include="Patch\Gizmo_Patch.cs" />
    <Compile Include="PregnancyCommon.cs" />
    <Compile Include="QuirkUtility.cs" />
    <Compile Include="Recipe_Surgery.cs" />
    <Compile Include="StatParts.cs" />
    <Compile Include="UI\Dialog_HybridCustom.cs" />
    <Compile Include="UI\Dialog_WombStatus.cs" />
    <Compile Include="FilthMaker_Colored.cs" />
    <Compile Include="HediffComps\HediffComp_Breast.cs" />
    <Compile Include="Hediff_MultiplePregnancy.cs" />
    <Compile Include="JobDrivers.cs" />
    <Compile Include="UI\Gizmo_Womb.cs" />
    <Compile Include="Patch\Harmony.cs" />
    <Compile Include="HediffComps\HediffComp_Menstruation.cs" />
    <Compile Include="Patch\Pawn_Patch.cs" />
    <Compile Include="Patch\RJW_Patch.cs" />
    <Compile Include="Reflect.cs" />
    <Compile Include="TextureCache.cs" />
    <Compile Include="Things.cs" />
    <Compile Include="Translations.cs" />
    <Compile Include="Utility.cs" />
    <Compile Include="VariousDefOf.cs" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="AlienRace">
      <HintPath>..\..\..\..\..\..\..\..\workshop\content\294100\839005762\1.4\Assemblies\AlienRace.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="AnimalGenetics">
      <HintPath>..\..\..\..\..\..\..\..\workshop\content\294100\2830943477\1.4\Assemblies\AnimalGenetics.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\..\..\..\rjw\1.4\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Xml" />
    <Reference Include="UnityEngine">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.IMGUIModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.InputLegacyModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.InputModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.InputModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.TextCoreModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.TextCoreModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.TextRenderingModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.UI">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.UI.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.UIElementsModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.UIElementsModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="UnityEngine.UIModule">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.UIModule.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
      <Visible>False</Visible>
      <ProductName>Microsoft .NET Framework 4.7.2%28x86 및 x64%29</ProductName>
      <Install>true</Install>
    </BootstrapperPackage>
    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
      <Visible>False</Visible>
      <ProductName>.NET Framework 3.5 SP1</ProductName>
      <Install>false</Install>
    </BootstrapperPackage>
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Krafs.Rimworld.Ref">
      <Version>1.4.3901</Version>
    </PackageReference>
    <PackageReference Include="Lib.Harmony">
      <Version>2.2.2</Version>
      <ExcludeAssets>runtime</ExcludeAssets>
      <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <Content Include="..\..\..\..\changelogs.txt">
      <Link>changelogs.txt</Link>
    </Content>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


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

namespace RJW_Menstruation
{
    public class Recipe_BreastSurgery : Recipe_Surgery
    {
        public override IEnumerable<BodyPartRecord> GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
        {
            BodyPartRecord part = Genital_Helper.get_breastsBPR(pawn) ?? Genital_Helper.get_uddersBPR(pawn);
            if (part != null)
            {

                if (pawn.GetBreastComp() != null) yield return part;
            }
        }

        public override void ApplyOnPawn(Pawn pawn, BodyPartRecord part, Pawn billDoer, List<Thing> ingredients, Bill bill)
        {
            HediffComp_Breast breast = pawn.GetBreastComp();
            if (billDoer != null && breast != null)
            {
                TaleRecorder.RecordTale(TaleDefOf.DidSurgery, new object[]
                {
                    billDoer,
                    pawn
                });
                SurgeryResult(breast);
            }
        }

        protected virtual void SurgeryResult(HediffComp_Breast breast)
        {
        }
    }

    public class Recipe_ExpandAreola : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustAreolaSizeImmediately(0.2f);
        }
    }

    public class Recipe_ContractAreola : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustAreolaSizeImmediately(-0.2f);
        }
    }

    public class Recipe_ExpandNipple : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustNippleSizeImmediately(0.2f);
        }
    }

    public class Recipe_ContractNipple : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustNippleSizeImmediately(-0.2f);
        }
    }

    public class Recipe_DarkenNipple : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustNippleColorImmediately(0.2f);
        }
    }

    public class Recipe_LightenNipple : Recipe_BreastSurgery
    {
        protected override void SurgeryResult(HediffComp_Breast breast)
        {
            breast.AdjustNippleColorImmediately(-0.2f);
        }
    }
}


code:
using System;
using System.Reflection;

namespace RJW_Menstruation
{
    public static class Reflector
    {
        public static object GetMemberValue(this Type type, string name)
        {
            BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
            FieldInfo fieldInfo = type?.GetField(name, flags);
            return fieldInfo?.GetValue(null);
        }

        public static object GetMemberValue(this object obj, string name)
        {
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            FieldInfo fieldInfo = obj?.GetType().GetField(name, flags);
            return fieldInfo?.GetValue(obj);
        }

        public static object GetPropertyValue(this Type type, string name)
        {
            BindingFlags flags = BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            PropertyInfo propertyInfo = type?.GetProperty(name, flags);
            return propertyInfo?.GetValue(null);
        }

        public static object GetPropertyValue(this object obj, string name)
        {
            BindingFlags flags = BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            PropertyInfo propertyInfo = obj?.GetType().GetProperty(name, flags);
            return propertyInfo?.GetValue(obj);
        }

        public static void SetMemberValue(this Type type, string name, object value)
        {
            BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
            FieldInfo fieldInfo = type?.GetField(name, flags);
            fieldInfo?.SetValue(null, value);
        }

        public static void SetMemberValue(this object obj, string name, object value)
        {
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            FieldInfo fieldInfo = obj?.GetType().GetField(name, flags);
            fieldInfo?.SetValue(obj, value);
        }

    }
}


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

namespace RJW_Menstruation
{
    public abstract class StatPart_Menstruation_Effects : StatPart
    {
        public override void TransformValue(StatRequest req, ref float val)
        {
            if (req.Thing is Pawn pawn && ApplicableComps(pawn).Any())
                val *= GetFactor(pawn);
        }
        protected abstract IEnumerable<HediffComp_Menstruation> ApplicableComps(Pawn pawn);

        protected IEnumerable<HediffComp_Menstruation> AllComps(Pawn pawn)
        {
            return pawn.GetMenstruationComps();
        }

        protected abstract float GetFactor(Pawn pawn);
    }

    public abstract class StatPart_Climacteric_Effects : StatPart_Menstruation_Effects
    {
        public override string ExplanationPart(StatRequest req)
        {
            if (req.Thing is Pawn pawn && ApplicableComps(pawn).Any())
            {
                return $"{Translations.Stage_Climacteric.CapitalizeFirst()}: x{GetFactor(pawn).ToStringPercent()}";
            }
            else return null;
        }

        protected override IEnumerable<HediffComp_Menstruation> ApplicableComps(Pawn pawn)
        {
            return pawn.GetMenstruationComps().Where(comp => comp.EggHealth < 1f && comp.EggHealth > 0f);
        }
    }

    public abstract class StatPart_Menopause_Effects : StatPart_Menstruation_Effects
    {
        public override string ExplanationPart(StatRequest req)
        {
            if (req.Thing is Pawn pawn && ApplicableComps(pawn).Any())
            {
                return $"{Translations.Stage_Menopause.CapitalizeFirst()}: x{GetFactor(pawn).ToStringPercent()}";
            }
            else return null;
        }

        protected override IEnumerable<HediffComp_Menstruation> ApplicableComps(Pawn pawn)
        {
            return pawn.GetMenstruationComps().Where(comp => comp.EggHealth <= 0f);
        }
    }

    public class StatPart_Climacteric_SexFrequency : StatPart_Climacteric_Effects
    {
        protected override float GetFactor(Pawn pawn)
        {
            return AllComps(pawn).Average(comp => comp.SexFrequencyModifier);
        }
    }

    public class StatPart_Climacteric_SexSatisfaction : StatPart_Climacteric_Effects
    {
        protected override float GetFactor(Pawn pawn)
        {
            return AllComps(pawn).Average(comp => comp.SexSatisfactionModifier);
        }
    }

    public class StatPart_Menopause_SexFrequency : StatPart_Menopause_Effects
    {
        protected override float GetFactor(Pawn pawn)
        {
            return AllComps(pawn).Average(comp => comp.SexFrequencyModifier);
        }
    }

    public class StatPart_Menopause_SexSatisfaction : StatPart_Menopause_Effects
    {
        protected override float GetFactor(Pawn pawn)
        {
            return AllComps(pawn).Average(comp => comp.SexSatisfactionModifier);
        }
    }
}


code:
using UnityEngine;
using Verse;

namespace RJW_Menstruation
{
    [StaticConstructorOnStartup]
    public static class TextureCache
    {
        // Milk bars
        public static Texture2D MilkTexture
        {
            get
            {
                if (milktexturecache == null) milktexturecache = SolidColorMaterials.NewSolidColorTexture(0.992f, 1.0f, 0.960f, 1.0f);
                return milktexturecache;
            }
        }
        // Ovulation, sex drive
        public static Texture2D OvulatoryTexture
        {
            get
            {
                if (ovulatorytexturecache == null) ovulatorytexturecache = SolidColorMaterials.NewSolidColorTexture(0.686f, 0.062f, 0.698f, 1.0f);
                return ovulatorytexturecache;
            }
        }
        // Bleeding, vulnerability
        public static Texture2D BleedingTexture
        {
            get
            {
                if (bleedingtexturecache == null) bleedingtexturecache = SolidColorMaterials.NewSolidColorTexture(0.415f, 0.0f, 0.003f, 1.0f);
                return bleedingtexturecache;
            }
        }
        // Pregnant, default, formerly sex ability
        public static Texture2D PregnantTexture
        {
            get
            {
                if (pregnanttexturecache == null) pregnanttexturecache = SolidColorMaterials.NewSolidColorTexture(0.082f, 0.453f, 0.6f, 1.0f);
                return pregnanttexturecache;
            }
        }
        // Recover, count of egg births
        public static Texture2D RecoverTexture
        {
            get
            {
                if (recovertexturecache == null) recovertexturecache = SolidColorMaterials.NewSolidColorTexture(0.6f, 0.83f, 0.35f, 1.0f);
                return recovertexturecache;
            }
        }
        // Follicular, count of humanlike births
        public static Texture2D FollicularTexture
        {
            get
            {
                if (folliculartexturecache == null) folliculartexturecache = SolidColorMaterials.NewSolidColorTexture(0.878f, 0.674f, 0.411f, 1.0f);
                return folliculartexturecache;
            }
        }
        // Count of animal births
        public static Texture2D AnimalTexture
        {
            get
            {
                if (animaltexturecache == null) animaltexturecache = SolidColorMaterials.NewSolidColorTexture(0.411f, 0.521f, 0.878f, 1.0f);
                return animaltexturecache;
            }
        }
        // Luteal, fertility
        public static Texture2D LutealTexture
        {
            get
            {
                if (lutealtexturecache == null) lutealtexturecache = SolidColorMaterials.NewSolidColorTexture(0.843f, 0.474f, 0.6f, 1.0f);
                return lutealtexturecache;
            }
        }
        // Unused, formerly count of whored
        public static Texture2D WhoredTexture
        {
            get
            {
                if (whoredtexturecache == null) whoredtexturecache = SolidColorMaterials.NewSolidColorTexture(0.7f, 0.7f, 0.0f, 1.0f);
                return whoredtexturecache;
            }
        }

        public static readonly Texture2D FertChanceTex = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 1f, 1f, 0.4f));
        private static Texture2D milktexturecache = SolidColorMaterials.NewSolidColorTexture(0.992f, 1.0f, 0.960f, 1.0f);
        private static Texture2D ovulatorytexturecache = SolidColorMaterials.NewSolidColorTexture(0.686f, 0.062f, 0.698f, 1.0f);
        private static Texture2D bleedingtexturecache = SolidColorMaterials.NewSolidColorTexture(0.415f, 0.0f, 0.003f, 1.0f);
        private static Texture2D pregnanttexturecache = SolidColorMaterials.NewSolidColorTexture(0.082f, 0.453f, 0.6f, 1.0f);
        private static Texture2D recovertexturecache = SolidColorMaterials.NewSolidColorTexture(0.6f, 0.83f, 0.35f, 1.0f);
        private static Texture2D folliculartexturecache = SolidColorMaterials.NewSolidColorTexture(0.878f, 0.674f, 0.411f, 1.0f);
        private static Texture2D animaltexturecache = SolidColorMaterials.NewSolidColorTexture(0.411f, 0.521f, 0.878f, 1.0f);
        private static Texture2D lutealtexturecache = SolidColorMaterials.NewSolidColorTexture(0.843f, 0.474f, 0.6f, 1.0f);
        private static Texture2D whoredtexturecache = SolidColorMaterials.NewSolidColorTexture(0.7f, 0.7f, 0.0f, 1.0f);
    }
}


code:
using RimWorld;
using rjw;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using UnityEngine;
using Verse;

namespace RJW_Menstruation
{
    public class PawnDNAModExtension : DefModExtension
    {
        public string fetusTexPath;
        public ColorInt cumColor;
        public Color CumColor => cumColor.ToColor;
        public float cumThickness = 0f;
        public List<HybridExtension> hybridExtension;
        public List<IngestionOutcomeDoer> ingestionOutcomeDoers;

        public HybridExtension GetHybridExtension(string race)
        {
            if (hybridExtension.NullOrEmpty()) return null;
            else return hybridExtension.Find(x => x.thingDef.defName.Equals(race));
        }

        public PawnKindDef GetHybridWith(string race)
        {
            return GetHybridExtension(race)?.ChooseOne() ?? null;
        }
    }

    public class HybridExtension
    {

        public Dictionary<string, float> hybridInfo = new Dictionary<string, float>();
        public ThingDef thingDef;

        public HybridExtension() { }

        public PawnKindDef ChooseOne()
        {

            if (hybridInfo.EnumerableNullOrEmpty()) return null;
            PawnKindDef res = null;
            do
            {
                string key = hybridInfo.RandomElementByWeight(x => x.Value).Key;
                res = DefDatabase<PawnKindDef>.GetNamedSilentFail(key);
                if (res == null) res = DefDatabase<ThingDef>.GetNamedSilentFail(key)?.race?.AnyPawnKind;

                if (res == null)
                {
                    Log.Warning($"Could not find pawnKind or race {key}, removing hybrid definition");
                    hybridInfo.Remove(key);
                }
            } while (res == null && !hybridInfo.EnumerableNullOrEmpty());

            return res;
        }

        public void LoadDataFromXmlCustom(XmlNode xmlRoot)
        {
            DirectXmlCrossRefLoader.RegisterObjectWantsCrossRef(this, "thingDef", xmlRoot.Name);
            XmlNodeList childNodes = xmlRoot.ChildNodes;

            if (childNodes.Count >= 1) foreach (XmlNode node in childNodes)
                {
#if DEBUG
                Log.Message(xmlRoot.Name + "HybridInfo: " + node.Name + " " + node.InnerText);
#endif
                    hybridInfo.Add(node.Name, ParseHelper.FromString<float>(node.InnerText));
                }
        }
    }

    public class HybridInformations : IExposable
    {
        public List<HybridExtensionExposable> hybridExtension = new List<HybridExtensionExposable>();

        private ThingDef thingDef;
        private string thingDefName;

        public string DefName
        {
            get
            {
                return thingDefName;
            }
        }
        public bool IsNull
        {
            get
            {
                return (thingDefName?.Length ?? 0) < 1;
            }
        }
        public ThingDef GetDef
        {
            get
            {
                if (thingDef != null) return thingDef;
                else
                {
                    thingDef = DefDatabase<ThingDef>.GetNamedSilentFail(thingDefName);
                    return thingDef;
                }
            }
        }

        public HybridInformations() { }

        public HybridInformations(ThingDef def)
        {
            thingDef = def;
            thingDefName = def.defName;
        }

        public HybridExtensionExposable GetHybridExtension(string race)
        {
            if (hybridExtension.NullOrEmpty()) return null;
            else
            {
                return hybridExtension.Find(x => x.GetDef?.defName?.Equals(race) ?? false);
            }
        }

        public PawnKindDef GetHybridWith(string race)
        {
            return GetHybridExtension(race)?.ChooseOne() ?? null;
        }

        public void ExposeData()
        {
            Scribe_Values.Look(ref thingDefName, "thingDefName");
            Scribe_Collections.Look(ref hybridExtension, "hybridExtension", LookMode.Deep, new object[0]);
        }
    }

    public class HybridExtensionExposable : HybridExtension, IExposable
    {
        private string thingDefName;

        public string DefName
        {
            get
            {
                return thingDefName;
            }
        }
        public bool IsNull
        {
            get
            {
                return (thingDefName?.Length ?? 0) < 1;
            }
        }
        public ThingDef GetDef
        {
            get
            {
                if (thingDef != null) return thingDef;
                else
                {
                    thingDef = DefDatabase<ThingDef>.GetNamedSilentFail(thingDefName);
                    return thingDef;
                }
            }
        }

        public HybridExtensionExposable() { }

        public HybridExtensionExposable(ThingDef def)
        {
            thingDef = def;
            thingDefName = def.defName;
            hybridInfo = new Dictionary<string, float>();
        }

        public void ExposeData()
        {
            Scribe_Values.Look(ref thingDefName, "thingDefName");
            Scribe_Collections.Look(ref hybridInfo, "hybridInfo", LookMode.Value, LookMode.Value);

        }
    }
    public class AbsorberModExtension : DefModExtension
    {
        public float passiveAbsorptionPerHour = 0.1f;
        public bool leakAfterDirty = false;
        public bool effectsAfterDirty = false;
        public ThingDef dirtyDef = null;
        public int minHourstoDirtyEffect = 0;
    }

    public class Absorber : Apparel
    {

        public float absorbedfluids = 0;
        public bool dirty = false;
        public int wearTicks = 0;
        public virtual float PassiveAbsorptionPerHour => def.GetModExtension<AbsorberModExtension>().passiveAbsorptionPerHour;
        public virtual bool LeakAfterDirty => def.GetModExtension<AbsorberModExtension>().leakAfterDirty;
        public virtual bool EffectAfterDirty => def.GetModExtension<AbsorberModExtension>().effectsAfterDirty;
        public virtual ThingDef DirtyDef => def.GetModExtension<AbsorberModExtension>().dirtyDef;
        public virtual int MinHrstoDirtyEffect => def.GetModExtension<AbsorberModExtension>().minHourstoDirtyEffect;

        public Color fluidColor = Color.white;

        public virtual void DirtyEffect(int tickInterval) { }

        public virtual void WearEffect(int tickInterval)
        {
            absorbedfluids += PassiveAbsorptionPerHour * tickInterval / GenDate.TicksPerHour;
            CheckDirty();
            if (dirty) wearTicks += tickInterval;
        }

        public void CheckDirty()
        {
            if (absorbedfluids > this.GetStatValue(VariousDefOf.MaxAbsorbable) && !(Wearer?.apparel?.IsLocked(this) ?? false) && DirtyDef != def && DirtyDef != null)
            {
                bool oldHasStats = !def.equippedStatOffsets.NullOrEmpty();
                bool newHasStats = !DirtyDef.equippedStatOffsets.NullOrEmpty();
                def = DirtyDef;
                dirty = true;
                Wearer.outfits?.forcedHandler?.SetForced(this, false);
                if (oldHasStats || newHasStats)
                    Wearer.health.capacities.Notify_CapacityLevelsDirty();
                Wearer.apparel.Notify_ApparelChanged();
            }
        }

        public override Color DrawColorTwo => fluidColor;

        public override void ExposeData()
        {
            base.ExposeData();
            if(Scribe.mode == LoadSaveMode.LoadingVars)
            {
                int wearhours = -1;
                Scribe_Values.Look(ref wearhours, "wearhours", wearhours, true);
                if (wearhours >= 0) wearTicks = wearhours * GenDate.TicksPerHour;
            }
            Scribe_Values.Look(ref absorbedfluids, "absorbedfluids", 0);
            Scribe_Values.Look(ref dirty, "dirty", false);
            Scribe_Values.Look(ref wearTicks, "wearTicks", 0);
            Scribe_Values.Look(ref fluidColor, "fluidColor", Color.white);
        }

        public override string DescriptionDetailed
        {
            get
            {
                StringBuilder text = new StringBuilder(base.DescriptionDetailed);
                text.AppendLine();
                text.Append(Translations.Description_Absorbed);
                text.Append(": ");
                text.Append(absorbedfluids.ToStringDecimalIfSmall());
                text.Append("/");
                text.Append(this.GetStatValue(VariousDefOf.MaxAbsorbable).ToStringDecimalIfSmall());
                text.Append("ml");

                return text.ToString();
            }
        }
    }

    public class Absorber_Tampon : Absorber
    {
        public override void DirtyEffect(int tickInterval)
        {

            if (wearTicks > MinHrstoDirtyEffect * GenDate.TicksPerHour && Rand.MTBEventOccurs(100.0f, GenDate.TicksPerHour, tickInterval) && !(Wearer.apparel?.IsLocked(this) ?? false))
            {
                Wearer.health.AddHediff(HediffDefOf.WoundInfection, Genital_Helper.get_genitalsBPR(Wearer));
            }
        }

    }

    public class Filth_Colored : Filth
    {

        private Color color = Color.white;

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Values.Look(ref color, "color", Color.white);
        }

        public override Color DrawColor
        {
            get
            {
                if (color != Color.white)
                {
                    return color;
                }
                if (Stuff != null)
                {
                    return def.GetColorForStuff(Stuff);
                }
                if (def.graphicData != null)
                {
                    return def.graphicData.color;
                }
                return color;
            }
            set
            {
                color = value;
            }
        }
    }
}


code:
using Verse;

namespace RJW_Menstruation
{
    public static class Translations
    {
        public static readonly string Mod_Title = "Mod_Title".Translate();
        public static readonly string Info_noCum = "Info_noCum".Translate();
        public static readonly string Menstrual_Blood = "Menstrual_Blood".Translate();
        public static readonly string Stage_Follicular = "Stage_Follicular".Translate();
        public static readonly string Stage_Ovulatory = "Stage_Ovulatory".Translate();
        public static readonly string Stage_Luteal = "Stage_Luteal".Translate();
        public static readonly string Stage_Bleeding = "Stage_Bleeding".Translate();
        public static readonly string Stage_Pregnant = "Stage_Pregnant".Translate();
        public static readonly string Stage_Recover = "Stage_Recover".Translate();
        public static readonly string Stage_None = "Stage_None".Translate();
        public static readonly string Stage_Climacteric = "Stage_Climacteric".Translate();
        public static readonly string Stage_Menopause = "Stage_Menopause".Translate();
        public static readonly string Stage_Anestrus = "Stage_Anestrus".Translate();
        public static readonly string Stage_Follicular_Desc = "Stage_Follicular_Desc".Translate();
        public static readonly string Stage_Follicular_Induced_Desc = "Stage_Follicular_Induced_Desc".Translate();
        public static readonly string Stage_Ovulatory_Desc = "Stage_Ovulatory_Desc".Translate();
        public static readonly string Stage_Luteal_Desc = "Stage_Luteal_Desc".Translate();
        public static readonly string Stage_Bleeding_Desc = "Stage_Bleeding_Desc".Translate();
        public static readonly string Stage_Pregnant_Desc = "Stage_Pregnant_Desc".Translate();
        public static readonly string Stage_Recover_Desc = "Stage_Recover_Desc".Translate();
        public static readonly string Stage_None_Desc = "Stage_None_Desc".Translate();
        public static readonly string Stage_Menopause_Desc = "Stage_Menopause_Desc".Translate();
        public static readonly string Stage_Climacteric_Desc = "Stage_Climacteric_Desc".Translate();
        public static readonly string Stage_Anestrus_Desc = "Stage_Anestrus_Desc".Translate();
        public static readonly string Button_HealthTab = "Button_HealthTab".Translate();
        public static readonly string Button_MilkTooltip = "Button_MilkTooltip".Translate();
        public static readonly string Dialog_DoCleanWomb_Tooltip = "Dialog_DoCleanWomb_Tooltip".Translate();
        public static readonly string Dialog_DontCleanWomb_Tooltip = "Dialog_DontCleanWomb_Tooltip".Translate();
        public static readonly string Dialog_FatherUnknown = "Dialog_FatherUnknown".Translate();

        public static readonly string Dialog_WombInfo01 = "Dialog_WombInfo01".Translate();
        public static readonly string Dialog_WombInfo02 = "Dialog_WombInfo02".Translate();
        public static readonly string Dialog_WombInfo03 = "Dialog_WombInfo03".Translate();
        public static readonly string Dialog_WombInfo04 = "Dialog_WombInfo04".Translate();
        public static readonly string Dialog_WombInfo05 = "Dialog_WombInfo05".Translate();
        public static readonly string Dialog_WombInfo06 = "Dialog_WombInfo06".Translate();
        public static readonly string Dialog_WombInfo07 = "Dialog_WombInfo07".Translate();
        public static readonly string Dialog_WombInfo08 = "Dialog_WombInfo08".Translate();
        public static readonly string Dialog_WombInfo09 = "Dialog_WombInfo09".Translate();
        public static readonly string Dialog_WombInfo10 = "Dialog_WombInfo10".Translate();
        public static readonly string Description_Absorbed = "Description_Absorbed".Translate();

        public static readonly string Option1_Label_1 = "Option1_Label_1".Translate();
        public static readonly string Option1_Label_2 = "Option1_Label_2".Translate();
        public static readonly string Option2_Label = "Option2_Label".Translate();
        public static readonly string Option2_Desc = "Option2_Desc".Translate();
        public static readonly string Option3_Label = "Option3_Label".Translate();
        public static readonly string Option3_Desc = "Option3_Desc".Translate();
        public static readonly string Option4_Label = "Option4_Label".Translate();
        public static readonly string Option4_Desc = "Option4_Desc".Translate();
        public static readonly string Option5_Label = "Option5_Label".Translate();
        public static readonly string Option5_Desc = "Option5_Desc".Translate();
        public static readonly string Option6_Label = "Option6_Label".Translate();
        public static readonly string Option6_Desc = "Option6_Desc".Translate();
        public static readonly string Option7_Label = "Option7_Label".Translate();
        public static readonly string Option7_Desc = "Option7_Desc".Translate();
        public static readonly string Option_ColonistUpdateInterval_Label = "Option_ColonistUpdateInterval_Label".Translate();
        public static readonly string Option_ColonistUpdateInterval_Desc = "Option_ColonistUpdateInterval_Desc".Translate();
        public static readonly string Option_NonColonistUpdateInterval_Label = "Option_NonColonistUpdateInterval_Label".Translate();
        public static readonly string Option_NonColonistUpdateInterval_Desc = "Option_NonColonistUpdateInterval_Desc".Translate();
        public static readonly string Option_AnimalUpdateInterval_Label = "Option_AnimalUpdateInterval_Label".Translate();
        public static readonly string Option_AnimalUpdateInterval_Desc = "Option_AnimalUpdateInterval_Desc".Translate();
        public static readonly string Option8_Label = "Option8_Label".Translate();
        public static readonly string Option8_Desc = "Option8_Desc".Translate();
        public static readonly string Option9_Label = "Option9_Label".Translate();
        public static readonly string Option9_Desc = "Option9_Desc".Translate();
        public static readonly string Option10_Label = "Option10_Label".Translate();
        public static readonly string Option10_Desc = "Option10_Desc".Translate();
        public static readonly string Option11_Label = "Option11_Label".Translate();
        public static readonly string Option11_Desc_1 = "Option11_Desc_1".Translate();
        public static readonly string Option11_Desc_2 = "Option11_Desc_2".Translate();
        public static readonly string Option11_Desc_3 = "Option11_Desc_3".Translate();
        public static readonly string Option11_Desc_4 = "Option11_Desc_4".Translate();
        public static readonly string Option12_Label = "Option12_Label".Translate();
        public static readonly string Option12_Desc = "Option12_Desc".Translate();
        public static readonly string Option_EnablePheromones_Label = "Option_EnablePheromones_Label".Translate();
        public static readonly string Option_EnablePheromones_Desc = "Option_EnablePheromones_Desc".Translate();
        public static readonly string Option_AnimalPheromoneEffect_Label = "Option_AnimalPheromoneEffect_Label".Translate();
        public static readonly string Option_AnimalPheromoneEffect_Desc = "Option_AnimalPheromoneEffect_Desc".Translate();
        public static readonly string Option13_Label = "Option13_Label".Translate();
        public static readonly string Option13_Desc = "Option13_Desc".Translate();
        public static readonly string Option14_Label = "Option14_Label".Translate();
        public static readonly string Option14_Desc = "Option14_Desc".Translate();
        public static readonly string Option15_Label = "Option15_Label".Translate();
        public static readonly string Option15_Desc = "Option15_Desc".Translate();
        public static readonly string Option16_Label = "Option16_Label".Translate();
        public static readonly string Option16_Desc = "Option16_Desc".Translate();
        public static readonly string Option17_Label = "Option17_Label".Translate();
        public static readonly string Option17_Desc = "Option17_Desc".Translate();
        public static readonly string Option18_Label = "Option18_Label".Translate();
        public static readonly string Option18_Desc = "Option18_Desc".Translate();
        public static readonly string Option19_Label_1 = "Option19_Label_1".Translate();
        public static readonly string Option19_Label_2 = "Option19_Label_2".Translate();
        public static readonly string Option19_Desc = "Option19_Desc".Translate();
        public static readonly string Option20_Label_1 = "Option20_Label_1".Translate();
        public static readonly string Option20_Label_2 = "Option20_Label_2".Translate();
        public static readonly string Option20_Label_3 = "Option20_Label_3".Translate();
        public static readonly string Option20_Label_4 = "Option20_Label_4".Translate();
        public static readonly string Option20_Label_5 = "Option20_Label_5".Translate();
        public static readonly string Option21_Label = "Option21_Label".Translate();
        public static readonly string Option21_Desc = "Option21_Desc".Translate();
        public static readonly string Option22_Label = "Option22_Label".Translate();
        public static readonly string Option22_Desc = "Option22_Desc".Translate();
        public static readonly string Option23_Label = "Option23_Label".Translate();
        public static readonly string Option23_Label_1 = "Option23_Label_1".Translate();
        public static readonly string Option23_Label_2 = "Option23_Label_2".Translate();
        public static readonly string Option28_Label = "Option28_Label".Translate();
        public static readonly string Option28_Tooltip = "Option28_Tooltip".Translate();
        public static readonly string Option29_Label = "Option29_Label".Translate();
        public static readonly string Option29_Desc = "Option29_Desc".Translate();
        public static readonly string Option30_Label = "Option30_Label".Translate();
        public static readonly string Option30_Desc = "Option30_Desc".Translate();
        public static readonly string Option31_Label = "Option31_Label".Translate();
        public static readonly string Option31_Desc = "Option31_Desc".Translate();
        public static readonly string Option32_Label = "Option32_Label".Translate();
        public static readonly string Option32_Desc = "Option32_Desc".Translate();
        public static readonly string Option_MaxBreastIncrementFactor_Label = "Option_MaxBreastIncrementFactor_Label".Translate();
        public static readonly string Option_MaxBreastIncrementFactor_Desc = "Option_MaxBreastIncrementFactor_Desc".Translate();
        public static readonly string Option_MaxNippleIncrementFactor_Label = "Option_MaxNippleIncrementFactor_Label".Translate();
        public static readonly string Option_MaxNippleIncrementFactor_Desc = "Option_MaxNippleIncrementFactor_Desc".Translate();
        public static readonly string Option_PermanentNippleChange_Label = "Option_PermanentNippleChange_Label".Translate();
        public static readonly string Option_PermanentNippleChange_Desc = "Option_PermanentNippleChange_Desc".Translate();
        public static readonly string Option_EnableGatherCumGizmo_Label = "Option_EnableGatherCumGizmo_Label".Translate();
        public static readonly string Option_EstrusOverride_Label = "Option_EstrusOverride_Label".Translate();
        public static readonly string Option_EstrusOverride_Desc = "Option_EstrusOverride_Desc".Translate();
        public static readonly string Option_EstrusFuckability_Label = "Option_EstrusFuckability_Label".Translate();
        public static readonly string Option_EstrusAttractability_Label = "Option_EstrusAttractability_Label".Translate();
        public static readonly string Option_EstrusRelationship_Label = "Option_EstrusRelationship_Label".Translate();
        public static readonly string Option_PregnancyFromBaseRJW_Label = "Option_PregnancyFromBaseRJW_Label".Translate();
        public static readonly string Option_PregnancyFromMultiplePregnancy_Label = "Option_PregnancyFromMultiplePregnancy_Label".Translate();
        public static readonly string Option_PregnancyFromBiotech_Label = "Option_PregnancyFromBiotech_Label".Translate();
        public static readonly string Option_EnableBiotechTwins_Label = "Option_EnableBiotechTwins_Label".Translate();
        public static readonly string Option_EnableBiotechTwins_Desc = "Option_EnableBiotechTwins_Desc".Translate();
        public static readonly string Option_EnableDraftedIcon_Label = "Option_EnableDraftedIcon_Label".Translate();
        public static readonly string Option_EnableDraftedIcon_Desc = "Option_EnableDraftedIcon_Desc".Translate();

        public static readonly string Button_ResetToDefault = "Button_ResetToDefault".Translate();

        public static readonly string EstimatedCumLifespan = "EstimatedCumLifespan".Translate();
        public static readonly string EstimatedEggLifespan = "EstimatedEggLifespan".Translate();
        public static string OvulationChanceLabel(string value) => "OvulationChanceLabel".Translate(value);
        public static readonly string OvulationChanceDesc = "OvulationChanceDesc".Translate();
        public static string FertilityDesc(string value) => "FertilityDesc".Translate(value);

        public static readonly string Gizmo_GatherCum = "Gizmo_GatherCum".Translate();
        public static readonly string FloatMenu_CleanSelf = "FloatMenu_CleanSelf".Translate();

        public static readonly string CannotNoEggs = "CannotNoEggs".Translate();

        public static readonly string CannotNoWomb = "CannotNoWomb".Translate();
        public static string EggRestorationCompleted(NamedArgument name) { return "EggRestorationCompleted".Translate(name); }

        public static readonly string CustomHybrid_List_Title = "CustomHybrid_List_Title".Translate();
        static public string CustomHybrid_Title(string label) { return TranslatorFormattedStringExtensions.Translate("CustomHybrid_Title", label); }
        static public string CustomHybrid_Tooltip(string label, string breedee, string baby, string chance) { return TranslatorFormattedStringExtensions.Translate("CustomHybrid_Tooltip", label, breedee, baby, chance); }
    }
}


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

namespace RJW_Menstruation
{
    public class Dialog_HybridCustom : Window
    {
        private static Vector2 scroll;
        private const float windowMargin = 20f;
        private static readonly List<FloatMenuOption> raceList = new List<FloatMenuOption>();
        private static readonly List<HybridInformations> removeList = new List<HybridInformations>();

        public Dialog_HybridCustom()
        {
            BuildRaceList();
        }

        public void BuildRaceList()
        {
            raceList.Clear();
            if (!VariousDefOf.AllRaces.NullOrEmpty())
                foreach (ThingDef def in VariousDefOf.AllRaces)
                {
                    if (def.race == null || Configurations.IsOverrideExist(def)) continue;
                    raceList.Add(new FloatMenuOption(def.label, delegate { AddHybridOverride(def); }, def.uiIcon, Color.white));
                }
            raceList.SortBy(x => x.Label);
        }

        public void AddHybridOverride(ThingDef def)
        {
            FloatMenuOption option = raceList.FirstOrDefault(x => x.Label.Equals(def?.label));
            if (option == null) return;
            raceList.Remove(option);
            if (Configurations.HybridOverride.NullOrEmpty())
            {
                Configurations.HybridOverride = new List<HybridInformations>();
            }
            Configurations.HybridOverride.Add(new HybridInformations(def));
            Configurations.HybridOverride.SortBy(x => x.GetDef?.label ?? "Undefined");

        }

        public override Vector2 InitialSize
        {
            get
            {
                float width = 480f;
                float height = 640f;
                return new Vector2(width, height);
            }
        }

        public override void DoWindowContents(Rect inRect)
        {
            soundClose = SoundDefOf.CommsWindow_Close;
            //closeOnClickedOutside = true;
            absorbInputAroundWindow = false;
            forcePause = false;
            preventCameraMotion = false;
            draggable = true;
            //resizeable = true;

            if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Escape))
            {
                Event.current.Use();
            }

            Rect windowRect = inRect.ContractedBy(windowMargin);
            Rect mainRect = new Rect(windowRect.x, windowRect.y, windowRect.width, windowRect.height);
            Rect closeRect = new Rect(windowRect.xMax, 0f, 20f, 20f);

            DoMainContents(mainRect);

            if (Widgets.CloseButtonFor(closeRect))
            {
                Close();
            }
        }

        public override void PostClose()
        {
            base.PostClose();
            if (!removeList.NullOrEmpty())
            {
                foreach (HybridInformations extension in removeList)
                {
                    Configurations.HybridOverride.Remove(extension);
                }
                raceList.SortBy(x => x.Label);
            }
            removeList.Clear();
        }

        protected void DoMainContents(Rect inRect)
        {
            Rect buttonRect = new Rect(inRect.xMax - 120, 0, 100, 30);
            Rect titleRect = new Rect(inRect.x, 0, 200, 30);
            Widgets.Label(titleRect, Translations.CustomHybrid_List_Title);
            Widgets.DrawLineHorizontal(titleRect.x, titleRect.yMax + 12f, inRect.width);
            if (Widgets.ButtonText(buttonRect, "Add"))
            {
                if (!raceList.NullOrEmpty()) Find.WindowStack.Add(new FloatMenu(raceList));
            }
            if (!removeList.NullOrEmpty())
            {
                buttonRect.x -= 100;
                if (Widgets.ButtonText(buttonRect, "Undo"))
                {
                    HybridInformations element = removeList.Last();
                    Configurations.HybridOverride.Add(element);
                    Configurations.HybridOverride.SortBy(x => x.GetDef?.label ?? "Undefined");
                    removeList.Remove(element);
                }

                foreach (HybridInformations extension in removeList)
                {
                    Configurations.HybridOverride.Remove(extension);
                }
                raceList.SortBy(x => x.Label);
            }

            Rect outRect = new Rect(inRect.x, inRect.y + 30f, inRect.width, inRect.height - 30f);
            Rect mainRect = new Rect(inRect.x, inRect.y + 30f, inRect.width - 30f, Math.Max(24f * Configurations.HybridOverride?.Count() ?? 1, 10f));
            Listing_Standard listmain = new Listing_Standard();

            Widgets.BeginScrollView(outRect, ref scroll, mainRect);
            listmain.Begin(mainRect);
            if (!Configurations.HybridOverride.NullOrEmpty())
                foreach (HybridInformations extension in Configurations.HybridOverride)
                {
                    if (extension.GetDef != null) DoRow(listmain.GetRect(24f), extension);
                }
            Widgets.EndScrollView();
            listmain.End();

        }

        protected void DoRow(Rect rect, HybridInformations extension)
        {
            Rect buttonRect = new Rect(rect.xMax - 90f, rect.y, 80f, rect.height);
            Widgets.Label(rect, extension.GetDef?.label ?? "Undefined");
            if (Widgets.ButtonText(buttonRect, "Delete"))
            {
                removeList.Add(extension);
                //raceList.Add(new FloatMenuOption(extension.GetDef.label, delegate { AddHybridOverride(extension.GetDef); }));
            }
            buttonRect.x -= 80f;
            if (Widgets.ButtonText(buttonRect, "Edit"))
            {
                Dialog_EditHybrid.OpenWindow(extension);
            }

            Widgets.DrawHighlightIfMouseover(rect);
        }

        public static void ToggleWindow()
        {
            Dialog_HybridCustom window = (Dialog_HybridCustom)Find.WindowStack.Windows.FirstOrDefault(x => x.GetType().Equals(typeof(Dialog_HybridCustom)));
            if (window != null)
            {
                Find.WindowStack.TryRemove(typeof(Dialog_HybridCustom), true);
            }
            else
            {
                SoundDefOf.CommsWindow_Open.PlayOneShotOnCamera();
                Find.WindowStack.Add(new Dialog_HybridCustom());
            }

        }

    }

    public class Dialog_EditHybrid : Window
    {
        private const float windowMargin = 20f;
        private const float rowH = 24f;
        private HybridInformations info;
        private Vector2 scroll;
        protected List<FloatMenuOption> raceList = new List<FloatMenuOption>();
        protected List<HybridExtensionExposable> removeList = new List<HybridExtensionExposable>();
        protected float totalWeight = 0;

        public Dialog_EditHybrid(HybridInformations info)
        {
            this.info = info;
            BuildRaceList();
        }

        public override Vector2 InitialSize
        {
            get
            {
                float width = 840f;
                float height = 640f;
                return new Vector2(width, height);
            }
        }

        public override void DoWindowContents(Rect inRect)
        {
            soundClose = SoundDefOf.CommsWindow_Close;
            //closeOnClickedOutside = true;
            absorbInputAroundWindow = false;
            forcePause = false;
            preventCameraMotion = false;
            draggable = true;
            //resizeable = true;

            if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Escape))
            {
                Event.current.Use();
            }

            Rect windowRect = inRect.ContractedBy(windowMargin);
            Rect mainRect = new Rect(windowRect.x, windowRect.y, windowRect.width, windowRect.height);
            Rect closeRect = new Rect(windowRect.xMax, 0f, 20f, 20f);

            DoMainContents(mainRect);

            if (Widgets.CloseButtonFor(closeRect))
            {
                Close();
            }
        }

        public static void OpenWindow(HybridInformations info)
        {
            Dialog_EditHybrid window = (Dialog_EditHybrid)Find.WindowStack.Windows.FirstOrDefault(x => x.GetType().Equals(typeof(Dialog_EditHybrid)));
            if (window != null)
            {
                if (window.info != info)
                {
                    SoundDefOf.TabOpen.PlayOneShotOnCamera();
                    window.ChangeExtension(info);
                }
            }
            else
            {
                SoundDefOf.TabClose.PlayOneShotOnCamera();
                Find.WindowStack.Add(new Dialog_EditHybrid(info));
            }

        }

        protected void ChangeExtension(HybridInformations info)
        {
            this.info = info;
            BuildRaceList();
        }

        protected void BuildRaceList()
        {
            raceList.Clear();
            if (VariousDefOf.AllRaces.NullOrEmpty()) return;

            foreach (ThingDef def in VariousDefOf.AllRaces)
            {
                if (def.race != null)
                {
                    if (info.hybridExtension.Exists(x => x.DefName == def.defName)) continue;
                    else
                    {
                        raceList.Add(new FloatMenuOption(def.label, delegate { AddHybridInfo(def); }, Widgets.GetIconFor(def), Color.white));
                    }
                }
            }
            raceList.SortBy(x => x.Label);
        }

        protected void AddHybridInfo(ThingDef def)
        {
            FloatMenuOption option = raceList.FirstOrDefault(x => x.Label == def.label);
            if (option != null)
            {
                raceList.Remove(option);
            }
            info.hybridExtension.Add(new HybridExtensionExposable(def));
        }

        protected void DoMainContents(Rect inRect)
        {
            Rect labelRect = new Rect(inRect.xMin, inRect.yMin, 300, 24);
            Rect buttonRect = new Rect(inRect.xMax - 120, 0, 100, 30);

            Widgets.Label(labelRect, Translations.CustomHybrid_Title(info.GetDef?.label ?? "Undefined"));
            Widgets.DrawLineHorizontal(inRect.x, labelRect.yMax, inRect.width);
            if (Widgets.ButtonText(buttonRect, "Add"))
            {
                if (!raceList.NullOrEmpty()) Find.WindowStack.Add(new FloatMenu(raceList));
            }
            if (!removeList.EnumerableNullOrEmpty())
            {
                buttonRect.x -= 100;
                if (Widgets.ButtonText(buttonRect, "Undo"))
                {
                    HybridExtensionExposable element = removeList.Last();
                    info.hybridExtension.Add(element);
                    removeList.Remove(element);
                }

                foreach (HybridExtensionExposable element in removeList)
                {
                    info.hybridExtension.Remove(element);
                }
            }

            float additionalHeight = 0f;
            if (!info.hybridExtension.NullOrEmpty()) foreach (HybridExtensionExposable e in info.hybridExtension)
                {
                    additionalHeight += (e.hybridInfo?.Count() ?? 1) * rowH;
                }

            Rect outRect = new Rect(inRect.x, inRect.y + 30f, inRect.width, inRect.height - 30f);
            Rect mainRect = new Rect(inRect.x, inRect.y + 30f, inRect.width - 30f, rowH * (info.hybridExtension?.Count() ?? 1) + additionalHeight);
            Listing_Standard listmain = new Listing_Standard();

            Widgets.BeginScrollView(outRect, ref scroll, mainRect);
            listmain.Begin(mainRect);

            if (!info.hybridExtension.NullOrEmpty())
            {
                foreach (HybridExtensionExposable extension in info.hybridExtension)
                {
                    DoRow(listmain.GetRect(rowH + rowH * (extension.hybridInfo?.Count() ?? 1)), extension);
                }

            }

            Widgets.EndScrollView();
            listmain.End();
        }

        protected void DoRow(Rect rect, HybridExtensionExposable extension)
        {
            Rect mainRect = new Rect(rect.x, rect.y, rect.width, rowH);
            Rect subRect = new Rect(rect.x, rect.y + rowH, rect.width, rect.height - rowH);
            Rect buttonRect = new Rect(rect.xMax - 90f, rect.y, 80f, rowH);
            Widgets.Label(mainRect, extension.GetDef?.label ?? "Undefined");

            if (Widgets.ButtonText(buttonRect, "Delete"))
            {
                removeList.Add(extension);
            }
            buttonRect.x -= 80f;
            if (Widgets.ButtonText(buttonRect, "Add"))
            {
                List<FloatMenuOption> list = new List<FloatMenuOption>();
                if (!VariousDefOf.AllRaces.NullOrEmpty()) foreach (ThingDef def in VariousDefOf.AllRaces)
                    {
                        if (def.race != null)
                        {
                            if (extension.hybridInfo.ContainsKey(def.defName)) continue;
                            else
                            {
                                list.Add(new FloatMenuOption(def.label, delegate { extension.hybridInfo.Add(def.defName, 1.0f); }, Widgets.GetIconFor(def), Color.white));
                            }
                        }
                    }
                if (!list.NullOrEmpty())
                {
                    list.SortBy(x => x.Label);
                    Find.WindowStack.Add(new FloatMenu(list));
                }

            }
            buttonRect.x -= 80f;

            Listing_Standard sublist = new Listing_Standard();
            sublist.Begin(subRect);

            List<string> removeelements = new List<string>();
            if (!extension.hybridInfo.EnumerableNullOrEmpty())
            {
                totalWeight = 0;
                foreach (KeyValuePair<string, float> element in extension.hybridInfo)
                {
                    totalWeight += element.Value;
                }

                List<string> keys = new List<string>(extension.hybridInfo.Keys);
                foreach (string key in keys)
                {
                    DoSubRow(sublist.GetRect(rowH), key, extension, removeelements);
                }
            }
            foreach (string key in removeelements)
                extension.hybridInfo.Remove(key);

            sublist.End();
            Widgets.DrawHighlightIfMouseover(rect);

        }

        protected void DoSubRow(Rect rect, string key, HybridExtensionExposable extension, List<string> removeelements)
        {
            bool isPawnKind = false;
            int value = (int)extension.hybridInfo.TryGetValue(key);
            string valuestr = value.ToString();
            string label = null;
            label = DefDatabase<ThingDef>.GetNamedSilentFail(key)?.label;
            if (label == null)
            {
                label = DefDatabase<PawnKindDef>.GetNamedSilentFail(key)?.label ?? "Undefined";
                isPawnKind = true;
            }
            Rect buttonRect = new Rect(rect.xMax - 90f, rect.y, 80f, rect.height);
            if (Widgets.ButtonText(buttonRect, "Delete"))
            {
                removeelements.Add(key);
            }
            buttonRect.x -= 80f;
            if (!isPawnKind)
            {
                if (Widgets.ButtonText(buttonRect, "PawnKind"))
                {
                    List<FloatMenuOption> list = new List<FloatMenuOption>();
                    if (!VariousDefOf.AllKinds.NullOrEmpty()) foreach (PawnKindDef def in VariousDefOf.AllKinds)
                        {
                            if (def.race.defName == key)
                            {
                                if (extension.hybridInfo.ContainsKey(def.defName)) continue;
                                else
                                {
                                    list.Add(new FloatMenuOption(def.label, delegate { extension.hybridInfo.Add(def.defName, 1.0f); }, Widgets.GetIconFor(def.race), Color.white));
                                }
                            }
                        }
                    if (!list.NullOrEmpty()) Find.WindowStack.Add(new FloatMenu(list));
                    else SoundDefOf.ClickReject.PlayOneShotOnCamera();
                }
                buttonRect.x -= 80f;
            }
            else
            {
                Widgets.Label(buttonRect, "  PawnKind");
                buttonRect.x -= 80f;
            }
            label += ": " + key;
            Widgets.Label(rect, " - " + label);
            Widgets.TextFieldNumeric(buttonRect, ref value, ref valuestr, 0, 9999999);
            extension.hybridInfo.SetOrAdd(key, value);
            buttonRect.x -= 80f;
            Widgets.Label(buttonRect, String.Format("{0,0:P2}", value / totalWeight));
            Widgets.DrawHighlightIfMouseover(rect);
            TooltipHandler.TipRegion(rect, Translations.CustomHybrid_Tooltip(info.GetDef?.label ?? "Undefined", extension.GetDef?.label ?? "Undefined", label, String.Format("{0,0:0.########%}", value / totalWeight)));
        }

    }

}