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

namespace RJW_Menstruation
{
    public class HediffComp_PregeneratedBabies : HediffComp
    {
        public List<Pawn> babies;
        // Unused, but can't hurt to track
        protected Dictionary<Pawn, Pawn> enzygoticSiblings;

        protected static readonly MethodInfo RandomLastName = typeof(PregnancyUtility).GetMethod("RandomLastName", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Pawn), typeof(Pawn), typeof(Pawn) }, null);

        public bool HasBaby
        {
            get => !babies.NullOrEmpty();
        }

        public Pawn PopBaby()
        {
            if (babies.NullOrEmpty()) return null;

            Pawn firstBaby = babies.First();
            babies.Remove(firstBaby);
            return firstBaby;
        }

        public override void CompPostPostRemoved()
        {
            // At this point in the hediff removal process, the new hediff is already on the pawn
            // But it is possible that there is no new hediff (be it a birth, miscarrage, or dev edit)
            base.CompPostPostRemoved();

            // Send the babies from this comp to the new one
            switch (parent)
            {
                case Hediff_Pregnant hediff_Pregnant:
                    Hediff_Labor labor = (Hediff_Labor)Pawn.health.hediffSet.hediffs.Where(hediff => hediff is Hediff_Labor).MaxByWithFallback(hediff => hediff.loadID);
                    HediffComp_PregeneratedBabies laborcomp = labor?.TryGetComp<HediffComp_PregeneratedBabies>();
                    if (laborcomp == null) return;
                    laborcomp.babies = this.babies;
                    laborcomp.enzygoticSiblings = this.enzygoticSiblings;
                    break;
                case Hediff_Labor hediff_Labor:
                    Hediff_LaborPushing pushing = (Hediff_LaborPushing)Pawn.health.hediffSet.hediffs.Where(hediff => hediff is Hediff_LaborPushing).MaxByWithFallback(hediff => hediff.loadID);
                    HediffComp_PregeneratedBabies pushingcomp = pushing?.TryGetComp<HediffComp_PregeneratedBabies>();
                    if (pushingcomp == null) return;
                    pushingcomp.babies = this.babies;
                    pushingcomp.enzygoticSiblings = this.enzygoticSiblings;
                    break;
                case Hediff_LaborPushing hediff_LaborPushing:
                    // Nothing to do, the laborpushing transpiler will pick it up
                    break;
            }
        }

        public override void CompExposeData()
        {
            base.CompExposeData();
            Scribe_Collections.Look(ref babies, "babies", LookMode.Deep);
            Scribe_Collections.Look(ref enzygoticSiblings, "enzygoticSiblings", keyLookMode: LookMode.Reference, valueLookMode: LookMode.Reference);
        }

        public void AddNewBaby(Pawn mother, Pawn father)
        {
            if (babies == null) babies = new List<Pawn>();
            PawnKindDef babyPawnKind = PregnancyCommon.BabyPawnKindDecider(mother, father, true);
            PawnGenerationRequest request = new PawnGenerationRequest
                (
                    kind: babyPawnKind,
                    faction: mother.Faction,
                    allowDowned: true,
                    // fixedLastName seems not to actually do anything, as we eventually end up in PawnBioAndNameGenerator.NameResolvedFrom, which ignores its forcedLastName argument
                    fixedLastName: (string)RandomLastName.Invoke(null, new object[] { mother, mother, xxx.is_human(father) ? father : null }),
                    forceNoIdeo: true,
                    forcedEndogenes: PregnancyUtility.GetInheritedGenes(father, mother),
                    forcedXenotype: XenotypeDefOf.Baseliner,
                    developmentalStages: DevelopmentalStage.Newborn
                );
            int division = 1;
            Pawn firstbaby = null;
            while (Rand.Chance(Configurations.EnzygoticTwinsChance) && division < Configurations.MaxEnzygoticTwins) division++;
            if (division > 1 && enzygoticSiblings == null) enzygoticSiblings = new Dictionary<Pawn, Pawn>();
            for (int i = 0; i < division; i++)
            {
                Pawn baby = PawnGenerator.GeneratePawn(request);
                if (baby == null) break;
                PregnancyCommon.SetupBabyXenotype(mother, father, baby);    // Probably redundant with Biotech post-birth xenotyping
                baby.Drawer.renderer.graphics.ResolveAllGraphics();
                if (division > 1)
                {
                    if (i == 0)
                    {
                        firstbaby = baby;
                        request.FixedGender = baby.gender;
                        request.ForcedEndogenes = baby.genes?.Endogenes.Select(gene => gene.def).ToList();
                    }
                    else
                    {
                        enzygoticSiblings.Add(baby, firstbaby);

                        if (baby.story != null)
                        {
                            baby.story.headType = firstbaby.story.headType;
                            baby.story.hairDef = firstbaby.story.hairDef;
                            baby.story.HairColor = firstbaby.story.HairColor;
                            baby.story.bodyType = firstbaby.story.bodyType;
                            baby.story.furDef = firstbaby.story.furDef;
                            baby.story.skinColorOverride = firstbaby.story.skinColorOverride;
                        }

                        if (baby.genes != null)
                        {
                            baby.genes.SetXenotypeDirect(firstbaby.genes.Xenotype);
                            baby.genes.xenotypeName = firstbaby.genes.xenotypeName;
                            baby.genes.iconDef = firstbaby.genes.iconDef;
                            baby.genes.hybrid = firstbaby.genes.hybrid;
                        }

                        if (baby.IsHAR())
                            HARCompatibility.CopyHARProperties(baby, firstbaby);

                        // MultiplePregnancy calls this post-birth because RJW resets private parts
                        // So xenotype things shouldn't be shared
                        PregnancyCommon.ProcessIdenticalSibling(baby, firstbaby);
                    }
                }
                babies.Add(baby);
                // These get cleared out later, but setting the relations now will help keep track of things.
                baby.SetMother(mother);
                if (mother != father)
                {
                    if (father.gender != Gender.Female) baby.SetFather(father);
                    else baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, father);
                }
            }
        }
    }

    [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))]
    public static class ApplyBirthOutcome_PregeneratedBabies_Patch
    {
        private static Pawn GetPregeneratedBaby(PawnGenerationRequest request, Thing birtherThing)
        {
            // Don't test for the config set here. We can do it at the functions that call ApplyBirthOutcome
            // Easier to work out twins that way

            // From e.g. a vat
            if (!(birtherThing is Pawn mother) || !xxx.is_human(mother))
                return PawnGenerator.GeneratePawn(request);

            // No babies found. Could be an unmodified pregnancy
            HediffComp_PregeneratedBabies comp = mother.health.hediffSet.GetFirstHediff<Hediff_LaborPushing>()?.TryGetComp<HediffComp_PregeneratedBabies>();
            if (comp == null || !comp.HasBaby)
                return PawnGenerator.GeneratePawn(request);

            Pawn baby = comp.PopBaby();
            if (baby == null) return PawnGenerator.GeneratePawn(request);   // Shouldn't happen
            baby.ageTracker.AgeBiologicalTicks = 0;
            baby.ageTracker.AgeChronologicalTicks = 0;
            baby.babyNamingDeadline = Find.TickManager.TicksGame + GenDate.TicksPerDay;
            if (request.ForceDead) baby.Kill(null, null);
            return baby;
        }

        private static readonly MethodInfo ApplyBirthOutcome = typeof(PregnancyUtility).GetMethod(nameof(PregnancyUtility.ApplyBirthOutcome));
        private static readonly int birtherThing = ApplyBirthOutcome.GetParameters().FirstIndexOf(parameter => parameter.Name == "birtherThing" && parameter.ParameterType == typeof(Thing));
        private static readonly MethodInfo GeneratePawn = typeof(PawnGenerator).GetMethod(nameof(PawnGenerator.GeneratePawn), new Type[] {typeof (PawnGenerationRequest)});

        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (birtherThing < 0) throw new InvalidOperationException("Could not locate index of birtherThing");
            if (GeneratePawn?.ReturnType != typeof(Pawn)) throw new InvalidOperationException("GeneratePawn not found");
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.Calls(GeneratePawn))
                {
                    yield return new CodeInstruction(OpCodes.Ldarg, birtherThing);
                    yield return CodeInstruction.Call(typeof(ApplyBirthOutcome_PregeneratedBabies_Patch), nameof(GetPregeneratedBaby));
                }
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PreRemoved))]
    public static class Hediff_LaborPushing_PreRemoved_Patch
    {
        private static Thing ApplyBirthLoop(OutcomeChance outcome, float quality, Precept_Ritual ritual, List<GeneDef> genes, Pawn geneticMother, Thing birtherThing, Pawn father, Pawn doctor, LordJob_Ritual lordJobRitual, RitualRoleAssignments assignments)
        {
            if (birtherThing is Pawn mother)
            {
                HediffComp_PregeneratedBabies comp = mother.health.hediffSet.GetFirstHediff<Hediff_LaborPushing>().TryGetComp<HediffComp_PregeneratedBabies>();
                if (comp?.HasBaby ?? false)
                {
                    OutcomeChance thisOutcome = outcome;
                    Precept_Ritual precept_Ritual = (Precept_Ritual)comp.Pawn.Ideo.GetPrecept(RimWorld.PreceptDefOf.ChildBirth);
                    float birthQuality = PregnancyUtility.GetBirthQualityFor(mother);
                    do
                    {
                        Pawn baby = comp.babies[0];
                        Pawn thisFather = baby.GetFather() ?? father;
                        baby.relations.ClearAllRelations();   // To keep ApplyBirthOutcome from erroring when it tries to set up relations

                        PregnancyUtility.ApplyBirthOutcome(thisOutcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments);
                        // No more babies if mom dies halfway through. Unrealistic maybe, but saves a lot of headache in ApplyBirthOutcome
                        if (mother.Dead) break;
                        if (xxx.is_human(baby))
                            mother.records.Increment(xxx.CountOfBirthHuman);
                        else if (xxx.is_animal(baby))
                            mother.records.Increment(xxx.CountOfBirthAnimal);
                        thisOutcome = ((RitualOutcomeEffectWorker_ChildBirth)precept_Ritual.outcomeEffect).GetOutcome(birthQuality, null);
                    } while (comp.HasBaby);

                    // PreRemoved doesn't use the return value
                    return null;
                }
            }

            return PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, father, doctor, lordJobRitual, assignments);
        }

        private static readonly MethodInfo ApplyBirthOutcome = typeof(PregnancyUtility).GetMethod(nameof(PregnancyUtility.ApplyBirthOutcome),
            new Type[] {typeof(OutcomeChance), typeof(float), typeof(Precept_Ritual), typeof(List<GeneDef>), typeof(Pawn), typeof(Thing), typeof(Pawn), typeof(Pawn), typeof(LordJob_Ritual), typeof(RitualRoleAssignments)});
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (ApplyBirthOutcome?.ReturnType != typeof(Thing)) throw new InvalidOperationException("ApplyBirthOutcome not found");
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.Calls(ApplyBirthOutcome))
                    yield return CodeInstruction.Call(typeof(Hediff_LaborPushing_PreRemoved_Patch), nameof(Hediff_LaborPushing_PreRemoved_Patch.ApplyBirthLoop));
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(RitualOutcomeEffectWorker_ChildBirth), nameof (RitualOutcomeEffectWorker_ChildBirth.Apply))]
    public static class Ritual_ChildBirth_Apply_Patch
    {
        private static Thing ApplyBirthLoop(OutcomeChance outcome, float quality, Precept_Ritual ritual, List<GeneDef> genes, Pawn geneticMother, Thing birtherThing, Pawn father, Pawn doctor, LordJob_Ritual lordJobRitual, RitualRoleAssignments assignments)
        {
            if (birtherThing is Pawn mother)
            {
                HediffComp_PregeneratedBabies comp = mother.health.hediffSet.GetFirstHediff<Hediff_LaborPushing>().TryGetComp<HediffComp_PregeneratedBabies>();
                if (comp?.HasBaby ?? false)
                {
                    // Much the same as the other one

                    // Don't reroll the outcome every time, I think
                    // This is all one ritual, so every baby has the same ritual outcome
                    // I don't think this will add the ritual memory every time?
                    // Though even if it does, that's probably okay. More babies more memories after all
                    do
                    {
                        Pawn baby = comp.babies[0];
                        Pawn thisFather = baby.GetFather() ?? father;
                        baby.relations.ClearAllRelations();

                        PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments);
                        if (mother.Dead) break;
                        if (xxx.is_human(baby))
                            mother.records.Increment(xxx.CountOfBirthHuman);
                        else if (xxx.is_animal(baby))
                            mother.records.Increment(xxx.CountOfBirthAnimal);
                    } while (comp.HasBaby);

                    // The ritual version doesn't use the return value, either
                    return null;
                }
            }
            return PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, father, doctor, lordJobRitual, assignments);
        }
        private static readonly MethodInfo ApplyBirthOutcome = typeof(PregnancyUtility).GetMethod(nameof(PregnancyUtility.ApplyBirthOutcome),
            new Type[] { typeof(OutcomeChance), typeof(float), typeof(Precept_Ritual), typeof(List<GeneDef>), typeof(Pawn), typeof(Thing), typeof(Pawn), typeof(Pawn), typeof(LordJob_Ritual), typeof(RitualRoleAssignments) });
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (ApplyBirthOutcome?.ReturnType != typeof(Thing)) throw new InvalidOperationException("ApplyBirthOutcome not found");
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.Calls(ApplyBirthOutcome))
                    yield return CodeInstruction.Call(typeof(Ritual_ChildBirth_Apply_Patch), nameof(Ritual_ChildBirth_Apply_Patch.ApplyBirthLoop));
                else yield return instruction;
            }
        }
    }

    // HAR patches ApplyBirthOutcome to produce multiple babies based on the mother's littersize. But the pregenerated babies system already makes multiple babies
    // So make it always consider the mother to have one baby
    public static class HAR_LitterSize_Undo
    {
        public static void Postfix(ref int __result, Pawn mother)
        {
            if (Configurations.PregnancySource != Configurations.PregnancyType.Biotech || !Configurations.EnableBiotechTwins) return;
            __result = 0;
            return;
        }
    }
}


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

namespace RJW_Menstruation
{
    public static class MenstruationUtility
    {
        [Obsolete("This method is obsolete. Use GetMenstruationComps or a related function instead", false)]
        public static HediffComp_Menstruation GetMenstruationComp(this Pawn pawn)
        {
            return pawn.GetFirstMenstruationComp();
        }

        public static IEnumerable<HediffComp_Menstruation> GetMenstruationComps(this Pawn pawn)
        {
            List<Hediff> hedifflist = pawn.health.hediffSet.hediffs.FindAll(h => VariousDefOf.AllVaginas.Contains(h.def));
            if (hedifflist == null) yield break;
            foreach (Hediff hediff in hedifflist)
            {
                HediffComp_Menstruation result = hediff.TryGetComp<HediffComp_Menstruation>();
                if (result != null) yield return result;
            }
        }

        public static HediffComp_Menstruation GetFirstMenstruationComp(this Pawn pawn)
        {
            return pawn.GetMenstruationComps().FirstOrDefault();
        }

        public static HediffComp_Menstruation GetRandomMenstruationComp(this Pawn pawn)
        {
            return pawn.GetMenstruationComps().RandomElementWithFallback();
        }

        public static HediffComp_Menstruation GetFertileMenstruationComp(this Pawn pawn)
        {
            List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList();
            return comps.Where(c => c.IsDangerDay).RandomElementWithFallback() ?? comps.RandomElementWithFallback();
        }

        public static HediffComp_Menstruation GetMenstruationCompFromVagina(this Hediff vagina)
        {
            if (VariousDefOf.AllVaginas.Contains(vagina?.def))
            {
                return vagina.TryGetComp<HediffComp_Menstruation>();
            }
            return null;
        }

        public static HediffComp_Menstruation GetMenstruationCompFromPregnancy(this Hediff pregnancy)
        {
            return pregnancy?.pawn.GetMenstruationComps().FirstOrDefault(comp => comp.Pregnancy == pregnancy);
        }

        public static HediffComp_Anus GetAnusComp(this Pawn pawn)
        {
            return pawn.health.hediffSet.hediffs.FirstOrDefault((Hediff h) => VariousDefOf.AllAnuses.Contains(h.def))?.TryGetComp<HediffComp_Anus>();
        }

        [Obsolete("This method is obsolete and can cause ambiguity. Use GetMenstruationCompFromVagina or GetMenstruationCompFromPregnancy instead.", true)]
        public static HediffComp_Menstruation GetMenstruationComp(Hediff hediff)
        {
            switch (hediff)
            {
                case Hediff_BasePregnancy rjwPreg:
                    return rjwPreg.GetMenstruationCompFromPregnancy();
                case Hediff_Pregnant vanillaPreg:
                    return vanillaPreg.GetMenstruationCompFromPregnancy();
                case Hediff_Labor vanillaLabor:
                    return vanillaLabor.GetMenstruationCompFromPregnancy();
                case Hediff_LaborPushing vanillaLaborPushing:
                    return vanillaLaborPushing.GetMenstruationCompFromPregnancy();
                case Hediff_PartBaseNatural rjwNatrual:
                    return rjwNatrual.GetMenstruationCompFromVagina();
                case Hediff_PartBaseArtifical rjwArtificial:
                    return rjwArtificial.GetMenstruationCompFromVagina();
                default:
                    Log.Warning("Obsolete GetMenstruationComp called with unknown hediff. Ensure your submods are up to date.");
                    return null;
            }
        }

        public static HediffComp_Anus GetAnusComp(this Hediff hediff)
        {
            if (hediff is Hediff_PartBaseNatural || hediff is Hediff_PartBaseArtifical)
            {
                return hediff.TryGetComp<HediffComp_Anus>();
            }
            return null;
        }

        public static float GetFertilityChance(this HediffComp_Menstruation comp)
        {
            return 1.0f - Mathf.Pow(1.0f - Configurations.FertilizeChance, comp.TotalFertCum * comp.Props.basefertilizationChanceFactor);
        }

        public static Texture2D GetPregnancyIcon(this HediffComp_Menstruation comp, Hediff hediff)
        {
            float gestationProgress = comp.StageProgress;

            ThingDef babydef;
            int babycount;
            HediffComp_PregeneratedBabies babiescomp = hediff?.TryGetComp<HediffComp_PregeneratedBabies>();
            if (hediff is Hediff_MechanoidPregnancy)
            {
                babydef = VariousDefOf.Scyther;
                babycount = 1;
            }
            else if (hediff is Hediff_BasePregnancy preg)
            {
                babydef = preg.babies?.FirstOrDefault()?.def ?? ThingDefOf.Human;
                babycount = preg.babies?.Count ?? 1;
            }
            else if (babiescomp?.HasBaby ?? false)
            {
                babydef = babiescomp.babies.First().def;
                babycount = babiescomp.babies.Count;
            }
            else
            {
                babydef = comp.Pawn.def;
                babycount = 1;
            }

            string fetustex = babydef.GetModExtension<PawnDNAModExtension>()?.fetusTexPath ?? "Fetus/Fetus_Default";
            string icon;
            if (gestationProgress < 0.2f) icon = comp.WombTex + "_Implanted";
            else if (gestationProgress < 0.4f) icon = fetustex + "00";
            else if (gestationProgress < 0.5f) icon = fetustex + "01";
            else if (gestationProgress < 0.6f) icon = fetustex + "02";
            else if (gestationProgress < 0.7f) icon = fetustex + "03";
            else if (gestationProgress < 0.8f) icon = fetustex + "04";
            else icon = fetustex + "05";

            return TryGetTwinsIcon(icon, babycount) ?? ContentFinder<Texture2D>.Get(icon, true);
        }

        public static Texture2D TryGetTwinsIcon(string path, int babycount)
        {
            for (int i = babycount; i > 1; i--)
            {
                Texture2D result = ContentFinder<Texture2D>.Get(path + "_Multiplet_" + i, false);
                if (result != null) return result;
            }
            return null;
        }

        public static Texture2D GetCumIcon(this HediffComp_Menstruation comp)
        {
            Pawn pawn = comp.Pawn;

            List<Hediff_InsectEgg> insectEggs = new List<Hediff_InsectEgg>();
            comp.Pawn.health.hediffSet.GetHediffs(ref insectEggs);
            if (insectEggs?.Sum(hediff => hediff.eggssize) > 1.0f) return null; // same logic as "Stuffed" in GetInsectEggedIcon

            string icon = comp.WombTex;
            float cumpercent = comp.TotalCumPercent;
            if (cumpercent < 0.001f) return ContentFinder<Texture2D>.Get("Womb/Empty", true);
            else if (cumpercent < 0.01f) icon += "_Cum_00";
            else if (cumpercent < 0.05f) icon += "_Cum_01";
            else if (cumpercent < 0.11f) icon += "_Cum_02";
            else if (cumpercent < 0.17f) icon += "_Cum_03";
            else if (cumpercent < 0.23f) icon += "_Cum_04";
            else if (cumpercent < 0.29f) icon += "_Cum_05";
            else if (cumpercent < 0.35f) icon += "_Cum_06";
            else if (cumpercent < 0.41f) icon += "_Cum_07";
            else if (cumpercent < 0.47f) icon += "_Cum_08";
            else if (cumpercent < 0.53f) icon += "_Cum_09";
            else if (cumpercent < 0.59f) icon += "_Cum_10";
            else if (cumpercent < 0.65f) icon += "_Cum_11";
            else if (cumpercent < 0.71f) icon += "_Cum_12";
            else if (cumpercent < 0.77f) icon += "_Cum_13";
            else if (cumpercent < 0.83f) icon += "_Cum_14";
            else if (cumpercent < 0.89f) icon += "_Cum_15";
            else if (cumpercent < 0.95f) icon += "_Cum_16";
            else icon += "_Cum_17";
            Texture2D cumtex = ContentFinder<Texture2D>.Get(icon, true);
            return cumtex;
        }
        public static Texture2D GetInsectEggedIcon(this HediffComp_Menstruation comp)
        {
            List<Hediff_InsectEgg> hediffs = new List<Hediff_InsectEgg>();
            comp.Pawn.health.hediffSet.GetHediffs(ref hediffs);

            if (hediffs.NullOrEmpty()) return null;
            string path = "Womb/Insect_Egged/Womb_Egged_";

            float sumSize = hediffs.Sum(hediff => hediff.eggssize);
            if (sumSize > 1.0f)
            {
                path += "Stuffed_";
                if (sumSize < 1.0f) path += "00";
                else if (sumSize < 2.0f) path += "01";
                else if (sumSize < 2.5f) path += "02";
                else if (sumSize < 3.0f) path += "03";
                else path += "04";
            }
            else
            {
                if(hediffs.Max(hediff => hediff.eggssize) > 0.3f)   // The threshold for "large egg" in the debug
                {
                    path += "L";
                    if (hediffs.Count == 1) path += "00";
                    else if (hediffs.Count == 2) path += "01";
                    else path += "02";
                }
                else
                {
                    path += "S";
                    if (hediffs.Count == 1) path += "00";
                    else if (hediffs.Count == 2) path += "01";
                    else if (hediffs.Count == 3) path += "02";
                    else if (hediffs.Count == 4) path += "03";
                    else path += "04";
                }
            }

            return ContentFinder<Texture2D>.Get(path);
        }
        public static Texture2D GetWombIcon(this HediffComp_Menstruation comp)
        {
            Texture2D wombtex = comp.GetInsectEggedIcon();
            if (wombtex != null) return wombtex;
            string icon = comp.WombTex;
            HediffComp_Menstruation.Stage stage = comp.curStage;
            if (stage == HediffComp_Menstruation.Stage.Bleeding) icon += "_Bleeding";

            wombtex = ContentFinder<Texture2D>.Get(icon, true);

            return wombtex;
        }
        public static Texture2D GetOvaryIcon(this HediffComp_Menstruation comp)
        {
            const float ovaryChanceToShow_01 = 0.2f;
            const float ovaryChanceToShow_02 = 0.8f;
            float ovulatoryProgress;
            bool isInduced = comp is HediffComp_InducedOvulator;
            if (comp.curStage == HediffComp_Menstruation.Stage.Follicular &&
                isInduced &&
                comp.Pawn.jobs.curDriver is JobDriver_Sex job &&
                job.Sexprops != null &&
                !job.Sexprops.usedCondom &&
                (job.Sexprops.sexType == xxx.rjwSextype.Vaginal || job.Sexprops.sexType == xxx.rjwSextype.DoublePenetration))
                ovulatoryProgress = 0.0f;
            else if (comp.curStage == HediffComp_Menstruation.Stage.Ovulatory) ovulatoryProgress = isInduced ? Mathf.Max(ovaryChanceToShow_01, comp.StageProgessNextUpdate) : comp.StageProgessNextUpdate;
//            else if (comp.curStage == HediffComp_Menstruation.Stage.Luteal && comp.IsEggExist) return ContentFinder<Texture2D>.Get("Ovaries/Ovary_02", true);
            else return ContentFinder<Texture2D>.Get("Womb/Empty", true);

            float combinedAppearance = ovulatoryProgress * comp.OvulationChance;
            if (combinedAppearance >= ovaryChanceToShow_02 && comp.OvulationChance >= 1.0f) return ContentFinder<Texture2D>.Get("Ovaries/Ovary_02", true);
            else if (combinedAppearance >= ovaryChanceToShow_01) return ContentFinder<Texture2D>.Get("Ovaries/Ovary_01", true);
            else return ContentFinder<Texture2D>.Get("Ovaries/Ovary_00", true);
        }
        public static Texture2D GetEggIcon(this HediffComp_Menstruation comp, bool includeOvary)
        {
            switch (comp.CurrentVisibleStage)
            {
                case HediffComp_Menstruation.Stage.Follicular:
                case HediffComp_Menstruation.Stage.Ovulatory:
                    if (!includeOvary) break;
                    else return GetOvaryIcon(comp);
                case HediffComp_Menstruation.Stage.Luteal:
                    if (!comp.IsEggExist) break;
                    int fertTime = comp.EggFertilizedTime;
                    if (fertTime >= 0)
                    {
                        if (fertTime <= GenDate.TicksPerHour * Configurations.CycleAcceleration) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilizing02", true);
                        else if (fertTime <= 18 * GenDate.TicksPerHour) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized00", true);
                        else if (fertTime <= 54 * GenDate.TicksPerHour) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized01", true);
                        else return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized02", true);
                    }
                    else if (includeOvary && comp.curStageTicks <= comp.Props.ovulationIntervalHours * 0.4f * GenDate.TicksPerHour) // Total about as long as it spent in Ovary_01
                    {
                        return ContentFinder<Texture2D>.Get("Ovaries/Ovary_02", true);
                    }
                    else if (comp.IsEggFertilizing)
                    {
                        if (comp.GetFertilityChance() < 0.5f)
                            return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilizing00", true);
                        else
                            return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilizing01", true);
                    }
                    else return ContentFinder<Texture2D>.Get("Eggs/Egg", true);
                case HediffComp_Menstruation.Stage.Pregnant:
                    if (comp.Pregnancy is Hediff_MechanoidPregnancy) break;
                    else if (comp.GetPregnancyProgress() < 0.2f) return ContentFinder<Texture2D>.Get("Eggs/Egg_Implanted00", true);
                    else break;
            }
            return ContentFinder<Texture2D>.Get("Womb/Empty", true);
        }

        public static void DrawEggOverlay(this HediffComp_Menstruation comp, Rect wombRect, bool includeOvary)
        {
            Rect rect = new Rect(wombRect.xMax - wombRect.width / 3, wombRect.y, wombRect.width / 3, wombRect.width / 3);
            GUI.color = Color.white;
            GUI.DrawTexture(rect, comp.GetEggIcon(includeOvary), ScaleMode.ScaleToFit);
        }

        public static Texture2D GetGenitalIcon(this Pawn pawn, HediffComp_Menstruation comp)
        {
            Hediff hediff = comp?.parent;
            if (hediff == null) return ContentFinder<Texture2D>.Get("Genitals/Vagina00", true);
            //HediffComp_Menstruation comp = hediff.GetMenstruationComp();
            string icon;
            float severity = hediff.Severity;
            if (comp != null) icon = comp.VagTex;
            else icon = "Genitals/Vagina";

            if (severity < 0.20f) icon += "00";        //micro
            else if (severity < 0.30f) icon += "01";   //tight
            else if (severity < 0.40f) icon += "02";   //tight
            else if (severity < 0.47f) icon += "03";   //average
            else if (severity < 0.53f) icon += "04";   //average
            else if (severity < 0.60f) icon += "05";   //average
            else if (severity < 0.70f) icon += "06";   //accomodating
            else if (severity < 0.80f) icon += "07";   //accomodating
            else if (severity < 0.87f) icon += "08";   //cavernous
            else if (severity < 0.94f) icon += "09";   //cavernous
            else if (severity < 1.01f) icon += "10";   //cavernous
            else icon += "11";                                //abyssal

            return ContentFinder<Texture2D>.Get(icon, true);
        }

        public static Texture2D GetAnalIcon(this Pawn pawn)
        {
            Hediff hediff = pawn.health.hediffSet.hediffs.FirstOrDefault(h => VariousDefOf.AllAnuses.Contains(h.def)) ??
                pawn.health.hediffSet.hediffs.FirstOrDefault(h => h.def.defName.ToLower().Contains("anus"));
            if (hediff == null) return ContentFinder<Texture2D>.Get("Genitals/Anal00", true);

            string icon = ((CompProperties_Anus)hediff.GetAnusComp()?.props)?.analTex ?? "Genitals/Anal";
            float severity = hediff.Severity;

            if (severity < 0.20f) icon += "00";        //micro
            else if (severity < 0.40f) icon += "01";   //tight
            else if (severity < 0.60f) icon += "02";   //average
            else if (severity < 0.80f) icon += "03";   //accomodating
            else if (severity < 1.01f) icon += "04";   //cavernous
            else icon += "05";                                //abyssal

            return ContentFinder<Texture2D>.Get(icon, true);
        }

        public static float GestationHours(this Hediff hediff)
        {
            if (hediff == null)
            {
                Log.Error("Tried to get gestation length without a pregnancy.");
                return 1f;
            }
            else if (hediff is Hediff_BasePregnancy rjw_preg)
                return (rjw_preg.p_end_tick - rjw_preg.p_start_tick) / GenDate.TicksPerHour;
            // TODO: Biotech pregnancy
            else return hediff.pawn.RaceProps.gestationPeriodDays * GenDate.HoursPerDay;
        }

        public static float RandomVariabilityPercent(int recursion = 0)
        {
            // Humans, in days
            const float mean = 1.635f;
            const float stddev = 0.9138f;
            const float lambda = 0.234f;
            if (recursion >= 10) return mean / (28 * 2);

            float variability = Rand.Gaussian(mean, stddev) - Mathf.Log(Rand.Value) / lambda;
            variability /= 28 * 2;  // Convert to percentage

            if (variability < 0 || variability > 0.35f) return RandomVariabilityPercent(recursion + 1); // ~2% chance, about the limit on how far variability can go before things start to break
            else return variability;
        }

        public static bool ShouldCycle(this Pawn pawn)
        {
            if (!Configurations.EnableAnimalCycle && pawn.IsAnimal()) return false;
            if (pawn.GetComp<CompEggLayer>() != null) return false;
            if (pawn.RaceHasOviPregnancy()) return false;

            if (ModsConfig.BiotechActive && pawn.genes != null &&
                pawn.genes.GenesListForReading.Select(gene => gene.def).Intersect(VariousDefOf.EggLayerGenes).Any()) return false;

            return true;
        }

        public static bool IsInEstrus(this Pawn pawn, bool visible = true)
        {
            if (pawn.Dead) return false;
            return pawn.health?.hediffSet?.HasHediff(visible ? VariousDefOf.Hediff_Estrus : VariousDefOf.Hediff_Estrus_Concealed) ?? false;
        }

        public static HediffComp_Menstruation.EstrusLevel HighestEstrus(this Pawn pawn)
        {
            HediffComp_Menstruation.EstrusLevel res = HediffComp_Menstruation.EstrusLevel.None;

            foreach(HediffComp_Menstruation comp in pawn.GetMenstruationComps())
            {
                switch (comp.GetEstrusLevel())
                {
                    case HediffComp_Menstruation.EstrusLevel.None:
                        break;
                    case HediffComp_Menstruation.EstrusLevel.Concealed:
                        res = HediffComp_Menstruation.EstrusLevel.Concealed;
                        break;
                    case HediffComp_Menstruation.EstrusLevel.Visible:
                        return HediffComp_Menstruation.EstrusLevel.Visible;
                }
            }

            return res;
        }

        public static bool HasIUD(this Pawn pawn)
        {
            if (pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) return true;
            if (ModsConfig.BiotechActive && pawn.health.hediffSet.HasHediff(HediffDefOf.ImplantedIUD)) return true;
            return false;
        }

        public static bool IsProPregnancy(this Pawn pawn, out Precept precept)
        {
            precept = null;

            Ideo ideo = pawn.Ideo;
            if (ideo != null)
            {
                precept = ideo.GetPrecept(VariousDefOf.Pregnancy_Required) ??
                          ideo.GetPrecept(VariousDefOf.Pregnancy_Holy) ??
                          ideo.GetPrecept(VariousDefOf.Pregnancy_Elevated);
            }
            if (precept != null) return true;

            else return pawn.IsBreeder() ||
                pawn.HasImpregnationFetish();
        }

        public static float DamagePants(this Pawn pawn, float fluidAmount)
        {
            if (pawn.apparel == null) return 0;
            Apparel pants = null;
            foreach(Apparel apparel in pawn.apparel.WornApparel.Where(app => app.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs)))
            {
                if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin)
                {
                    pants = apparel;
                    break;
                }
                else if (pants == null || apparel.def.apparel.LastLayer == ApparelLayerDefOf.Middle)
                    // Either grab whatever's available or reassign the pants from shell to a middle
                    pants = apparel;
                // Pants are middle and this is a shell
                else continue;
            }
            if (pants == null) return 0;

            const float HPPerMl = 0.5f;

            DamageWorker.DamageResult damage = pants.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, fluidAmount * HPPerMl, spawnFilth: false));

            if (pants.Destroyed && PawnUtility.ShouldSendNotificationAbout(pawn) && !pawn.Dead)
                Messages.Message("MessageWornApparelDeterioratedAway".Translate(GenLabel.ThingLabel(pants.def, pants.Stuff), pawn).CapitalizeFirst(), pawn, MessageTypeDefOf.NegativeEvent);

            return damage.totalDamageDealt;
        }
    }
}

license: unknown


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

namespace RJW_Menstruation
{
    public class Hediff_Estrus : HediffWithComps
    {
        const int checkInterval = GenTicks.TickRareInterval;
        private bool shouldRemove = false;

        public override bool ShouldRemove
        {
            get
            {
                return shouldRemove || base.ShouldRemove;
            }
        }

        protected bool IsVisible
        {
            get
            {
                return def == VariousDefOf.Hediff_Estrus;
            }
        }

        public override void PostAdd(DamageInfo? dinfo)
        {
            base.PostAdd(dinfo);
            if (IsVisible)
            {
                List<Hediff> removals = pawn.health.hediffSet.hediffs.Where(hediff => hediff.def == VariousDefOf.Hediff_Estrus_Concealed).ToList();
                foreach (Hediff concealedEstrus in removals)
                {
                    pawn.health.RemoveHediff(concealedEstrus);
                }
            }
        }

        public override void PostTick()
        {
            base.PostTick();
            if (!pawn.IsHashIntervalTick(checkInterval)) return;

            if (IsVisible)
            {
                switch (pawn.HighestEstrus())
                {
                    case HediffComp_Menstruation.EstrusLevel.None:
                        shouldRemove = true;
                        break;
                    case HediffComp_Menstruation.EstrusLevel.Concealed:
                        shouldRemove = true;
                        pawn.health.AddHediff(VariousDefOf.Hediff_Estrus_Concealed);
                        break;
                    case HediffComp_Menstruation.EstrusLevel.Visible:
                        break;
                }
            }
            else
            {
                // Adding a visible will remove this one, so we don't have to check it here
                if (pawn.HighestEstrus() == HediffComp_Menstruation.EstrusLevel.None) shouldRemove = true;
            }
        }

        public override bool TryMergeWith(Hediff other)
        {
            if (!(other is Hediff_Estrus otherEstrus)) return false;

            if (this.def == otherEstrus.def) return true;
            else if (!this.IsVisible && otherEstrus.IsVisible && !otherEstrus.shouldRemove) return true; // A concealed estrus won't be added if there's a visible
            // This is a visible estrus overwriting a concealed.
            else return false;  // Since this is being added while hediffs are being looped through, it's not safe to remove the concealed yet. Do it in PostAdd.
        }
    }
}


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

namespace RJW_Menstruation
{
    public class Hediff_MultiplePregnancy : Hediff_BasePregnancy
    {
        protected Dictionary<Pawn, Pawn> enzygoticSiblings = new Dictionary<Pawn, Pawn>();  // Each pawn and who they split from

        public override void DiscoverPregnancy()
        {
            PregnancyThought();
            base.DiscoverPregnancy();
        }

        protected void PregnancyThought()
        {
            if (is_discovered ||
                !xxx.is_human(pawn) ||
                pawn.IsBreeder() || (pawn.Ideo?.HasPrecept(VariousDefOf.Pregnancy_Required) ?? false) ||
                (pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) ||
                x.def.Equals(PawnRelationDefOf.Fiance))) != null)
                return;
            if (pawn.IsProPregnancy(out Precept precept) || pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Lover)) != null)
            {
                if (precept != null)
                    pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancyMild);
            }
            else
            {
                pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancy);
            }
        }

        public override void Miscarry()
        {
            this.GetMenstruationCompFromPregnancy().Pregnancy = null;
            base.Miscarry();
        }

        public override void GiveBirth()
        {

            if (babies.NullOrEmpty())
            {
                ModLog.Warning(" no babies (debug?) " + this.GetType().Name);
                if (father == null)
                {
                    father = Trytogetfather(ref pawn);
                }
                Initialize(pawn, father, SelectDnaGivingParent(pawn, father));
            }

            foreach (Pawn baby in babies)
            {
                if (xxx.is_animal(baby))
                {
                    BestialBirth(baby);
                }
                else
                {
                    HumanlikeBirth(baby);
                }

                baby.ageTracker.AgeChronologicalTicks = 0;
            }

            pawn.health.RemoveHediff(this);
            HediffComp_Menstruation comp = this.GetMenstruationCompFromPregnancy();
            if(comp != null) comp.Pregnancy = null;
            HediffComp_Breast breastcomp = pawn.GetBreastComp();
            if (ModsConfig.BiotechActive && xxx.is_human(pawn) && breastcomp != null)
                pawn.health.AddHediff(HediffDefOf.Lactating);
            breastcomp?.GaveBirth();
        }

        private void HumanlikeBirth(Pawn baby)
        {
            Pawn mother = pawn; Pawn father = Utility.GetFather(baby, pawn);
            //backup melanin, LastName for when baby reset by other mod on spawn/backstorychange
            //var skin_whiteness = baby.story.melanin;
            //var last_name = baby.story.birthLastName;

            PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother);

            Need_Sex sex_need = mother.needs?.TryGetNeed<Need_Sex>();
            if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null)
            {
                sex_need.CurLevel = 1.0f;
            }
            if (mother.Faction != null)
            {
                if (mother.Faction != baby.Faction)
                    baby.SetFaction(mother.Faction);
            }
            if (mother.IsSlaveOfColony)
            {
                if (mother.SlaveFaction != null)
                    baby.SetFaction(mother.SlaveFaction);
                else if (mother.HomeFaction != null)
                    baby.SetFaction(mother.HomeFaction);
                else if (mother.Faction != null)
                    baby.SetFaction(mother.Faction);
                else
                    baby.SetFaction(Faction.OfPlayer);
                baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Slave);
            }
            else if (mother.IsPrisonerOfColony)
            {
                if (mother.HomeFaction != null)
                    baby.SetFaction(mother.HomeFaction);
                baby.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Prisoner);
            }

            if (xxx.is_human(mother)) TaleRecorder.RecordTale(TaleDefOf.GaveBirth, new object[] { mother, baby });

            if (ModsConfig.BiotechActive)
            {
                // Ugly, but it'll have to do
                OutcomeChance bestOutcome = RitualOutcomeEffectDefOf.ChildBirth.BestOutcome;

                string label = bestOutcome.label;
                string description = bestOutcome.description.Formatted(mother.Named("MOTHER"));

                baby.babyNamingDeadline = Find.TickManager.TicksGame + GenDate.TicksPerDay;
                ChoiceLetter_BabyBirth choiceLetter_BabyBirth = (ChoiceLetter_BabyBirth)LetterMaker.MakeLetter(
                    label, description, LetterDefOf.BabyBirth, baby
                    );
                choiceLetter_BabyBirth.Start();
                Find.LetterStack.ReceiveLetter(choiceLetter_BabyBirth);
            }

            PostBirth(mother, father, baby);
        }

        private void BestialBirth(Pawn baby)
        {
            Pawn mother = pawn; Pawn father = Utility.GetFather(baby, pawn);
            //backup melanin, LastName for when baby reset by other mod on spawn/backstorychange
            //var skin_whiteness = baby.story.melanin;
            //var last_name = baby.story.birthLastName;

            PawnUtility.TrySpawnHatchedOrBornPawn(baby, mother);

            Need_Sex sex_need = mother.needs?.TryGetNeed<Need_Sex>();
            if (mother.Faction != null && !(mother.Faction?.IsPlayer ?? false) && sex_need != null)
            {
                sex_need.CurLevel = 1.0f;
            }
            if (mother.Faction != null)
            {
                if (mother.Faction != baby.Faction)
                    baby.SetFaction(mother.Faction);
            }

            Train(baby, mother);

            PostBirth(mother, father, baby);

            //restore melanin, LastName for when baby reset by other mod on spawn/backstorychange
            //baby.story.melanin = skin_whiteness;
            //baby.story.birthLastName = last_name;
        }

        public override void PostBirth(Pawn mother, Pawn father, Pawn baby)
        {
            base.PostBirth(mother, father, baby);
            // Has to happen on birth since RJW redoes the genitals at birth
            if (!enzygoticSiblings.NullOrEmpty() && enzygoticSiblings.TryGetValue(baby, out Pawn original) && baby != original)
                PregnancyCommon.ProcessIdenticalSibling(baby, original);
        }

        // From RJW's trait code
        protected List<Trait> GetInheritableTraits(Pawn mother, Pawn father)
        {
            List<Trait> traitpool = new List<Trait>();
            List<Trait> momtraits = new List<Trait>();
            List<Trait> poptraits = new List<Trait>();
            List<Trait> traits_to_inherit = new List<Trait>();
            float max_num_momtraits_inherited = RJWPregnancySettings.max_num_momtraits_inherited;
            float max_num_poptraits_inherited = RJWPregnancySettings.max_num_poptraits_inherited;
            float max_num_traits_inherited = max_num_momtraits_inherited + max_num_poptraits_inherited;
            int i = 1;
            int j = 1;
            if (xxx.has_traits(mother) && mother.RaceProps.Humanlike)
            {
                foreach (Trait momtrait in mother.story.traits.allTraits)
                {
                    if (!non_genetic_traits.Contains(momtrait.def.defName) && !momtrait.ScenForced)
                        momtraits.Add(momtrait);
                }
            }
            if (father != null && xxx.has_traits(father) && father.RaceProps.Humanlike)
            {
                foreach (Trait poptrait in father.story.traits.allTraits)
                {
                    if (!non_genetic_traits.Contains(poptrait.def.defName) && !poptrait.ScenForced)
                        poptraits.Add(poptrait);
                }
            }
            int rand_trait_index;
            if (!momtraits.NullOrEmpty())
            {
                i = 1;
                while (momtraits.Count > 0 && i <= max_num_momtraits_inherited)
                {
                    rand_trait_index = Rand.Range(0, momtraits.Count);
                    traits_to_inherit.Add(momtraits[rand_trait_index]);
                    momtraits.RemoveAt(rand_trait_index);
                }
            }
            if (!poptraits.NullOrEmpty())
            {
                j = 1;
                while (poptraits.Count > 0 && j <= max_num_poptraits_inherited)
                {
                    rand_trait_index = Rand.Range(0, poptraits.Count);
                    traits_to_inherit.Add(poptraits[rand_trait_index]);
                    poptraits.RemoveAt(rand_trait_index);
                }
            }

            if (poptraits.NullOrEmpty() || momtraits.NullOrEmpty())
            {
                foreach (Trait traits in traits_to_inherit)
                {
                    traitpool.Add(traits);
                }
            }
            else
            {
                if (traits_to_inherit.Count() != max_num_traits_inherited)
                {
                    if (momtraits.Count != 0 && i != max_num_momtraits_inherited)
                    {
                        while (poptraits != null && momtraits.Count() > 0 && i <= max_num_momtraits_inherited)
                        {
                            rand_trait_index = Rand.Range(0, momtraits.Count);
                            if (!traits_to_inherit.Contains(momtraits[rand_trait_index]))
                            {
                                traits_to_inherit.Add(momtraits[rand_trait_index]);
                            }
                            momtraits.RemoveAt(rand_trait_index);
                        }
                    }
                    if (poptraits != null && poptraits.Count != 0 && j != max_num_poptraits_inherited)
                    {
                        while (poptraits.Count > 0 && i < max_num_poptraits_inherited)
                        {
                            rand_trait_index = Rand.Range(0, poptraits.Count);
                            if (!traits_to_inherit.Contains(poptraits[rand_trait_index]))
                            {
                                traits_to_inherit.Add(poptraits[rand_trait_index]);
                            }
                            poptraits.RemoveAt(rand_trait_index);
                        }
                    }
                }
                foreach (Trait traits in traits_to_inherit)
                {
                    traitpool.Add(traits);
                }
            }
            return traitpool;
        }

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Collections.Look(ref enzygoticSiblings, "enzygoticSiblings", keyLookMode: LookMode.Reference, valueLookMode: LookMode.Reference);
        }

        protected override void GenerateBabies(DnaGivingParent _)
        {
            AddNewBaby(pawn, father);
        }

        protected void Train(Pawn baby, Pawn mother)
        {
            if (xxx.is_human(baby) || baby.Faction != Faction.OfPlayer) return;

            if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Obedience, out _).Accepted)
            {
                baby.training.Train(TrainableDefOf.Obedience, mother);
            }
            if (xxx.is_human(mother) && baby.Faction == Faction.OfPlayer && baby.training.CanAssignToTrain(TrainableDefOf.Tameness, out _).Accepted)
            {
                baby.training.Train(TrainableDefOf.Tameness, mother);
            }
        }

        public bool AddNewBaby(Pawn mother, Pawn father)
        {
            string lastname;
            if (xxx.is_human(mother)) lastname = NameTriple.FromString(mother.Name.ToStringFull).Last;
            else if (xxx.is_human(father)) lastname = NameTriple.FromString(father.Name.ToStringFull).Last;
            else lastname = "";

            PawnGenerationRequest request = new PawnGenerationRequest(
                developmentalStages: DevelopmentalStage.Newborn,
                allowDowned: true,
                faction: mother.Faction,
                canGeneratePawnRelations: false,
                forceGenerateNewPawn: true,
                colonistRelationChanceFactor: 0,
                allowFood: false,
                allowAddictions: false,
                relationWithExtraPawnChanceFactor: 0,
                fixedLastName: lastname,
                kind: PregnancyCommon.BabyPawnKindDecider(mother, father, false),
                //fixedIdeo: mother.Ideo,
                forbidAnyTitle: true,
                forceNoBackstory: true,
                forcedEndogenes: PregnancyUtility.GetInheritedGenes(father, mother),
                forcedXenotype: ModsConfig.BiotechActive ? XenotypeDefOf.Baseliner : null
                );

            int division = 1;
            Pawn firstbaby = null;
            int traitSeed = Rand.Int;
            List<Trait> parentTraits = GetInheritableTraits(mother, father);
            while (Rand.Chance(Configurations.EnzygoticTwinsChance) && division < Configurations.MaxEnzygoticTwins) division++;
            for (int i = 0; i < division; i++)
            {
                Pawn baby = GenerateBaby(request, mother, father, parentTraits, traitSeed);
                if (baby == null) break;
                PregnancyCommon.SetupBabyXenotype(mother, father, baby);
                // HAR and some xenotype mods don't randomize graphics until it's rendered
                // So poke it early
                baby.Drawer.renderer.graphics.ResolveAllGraphics();
                if (division > 1)
                {
                    if (i == 0)
                    {

                        firstbaby = baby;
                        request.FixedGender = baby.gender;
                        request.ForcedEndogenes = baby.genes?.Endogenes.Select(gene => gene.def).ToList();
                    }
                    else
                    {
                        enzygoticSiblings.Add(baby, firstbaby);

                        if (baby.story != null)
                        {
                            baby.story.headType = firstbaby.story.headType;
                            baby.story.hairDef = firstbaby.story.hairDef;
                            baby.story.HairColor = firstbaby.story.HairColor;
                            baby.story.bodyType = firstbaby.story.bodyType;
                            baby.story.furDef = firstbaby.story.furDef;
                            baby.story.skinColorOverride = firstbaby.story.skinColorOverride;
                        }

                        if (baby.genes != null && ModsConfig.BiotechActive)
                        {
                            baby.genes.SetXenotypeDirect(firstbaby.genes.Xenotype);
                            baby.genes.xenotypeName = firstbaby.genes.xenotypeName;
                            baby.genes.iconDef = firstbaby.genes.iconDef;
                            baby.genes.hybrid = firstbaby.genes.hybrid;
                        }

                        if (baby.IsHAR())
                            HARCompatibility.CopyHARProperties(baby, firstbaby);
                        if (Configurations.AnimalGeneticsActivated)
                            AnimalGeneticsCompatibility.CopyGenes(baby, firstbaby);
                    }
                }
                babies.Add(baby);
            }

            return true;

        }

        public Pawn GenerateBaby(PawnGenerationRequest request, Pawn mother, Pawn father, List<Trait> parentTraits, int traitSeed)
        {
            if (Configurations.AnimalGeneticsActivated) AnimalGeneticsCompatibility.PreConception(mother, father);
            Pawn baby = PawnGenerator.GeneratePawn(request);
            if (Configurations.AnimalGeneticsActivated) AnimalGeneticsCompatibility.PostConception();
            if (baby == null)
            {
                Log.Error("Baby not generated. Request: " + request.ToString());
                return null;
            }
            if (xxx.is_human(baby) || (baby.relations != null && !RJWSettings.Disable_bestiality_pregnancy_relations))
            {
                baby.SetMother(mother);
                if (mother != father)
                {
                    if (father.gender != Gender.Female) baby.SetFather(father);
                    else baby.relations.AddDirectRelation(PawnRelationDefOf.Parent, father);
                }
            }
            if (xxx.is_human(baby))
            {
                // Ensure the same inherited traits are chosen each run
                // Has to happen right here so GeneratePawn up there still gets unique results
                Rand.PushState(traitSeed);  // With a seed just to make sure that fraternal twins *don't* get trait-duped
                UpdateTraits(baby, parentTraits);
                Rand.PopState();
            }
            return baby;
        }

        /// <summary>
        /// Copy from RJW
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="parentTraits"></param>
        ///
        public void UpdateTraits(Pawn pawn, List<Trait> parentTraits)
        {
            if (pawn?.story?.traits == null)
            {
                return;
            }

            int traitLimit = pawn.story.traits.allTraits.Count;

            //Personal pool
            List<Trait> personalTraitPool = new List<Trait>(pawn.story.traits.allTraits);
            //Parents
            List<Trait> parentTraitPool = new List<Trait>(parentTraits);
            parentTraitPool.RemoveAll(x => x.ScenForced);
            int numberInherited;
            if (parentTraitPool != null)
                numberInherited = System.Math.Min(parentTraitPool.Count(), Rand.RangeInclusive(0, 2));   // Not 3; give a better chance for a natural trait to appear
            else
                numberInherited = 0;

            //Game suggested traits.
            IEnumerable<Trait> forcedTraits = personalTraitPool
                .Where(x => x.ScenForced)
                .Distinct(new TraitComparer(ignoreDegree: true)); // result can be a mess, because game allows this mess to be created in scenario editor

            List<Trait> selectedTraits = new List<Trait>();
            TraitComparer comparer = new TraitComparer(); // trait comparision implementation, because without game compares traits *by reference*, makeing them all unique.
            selectedTraits.AddRange(forcedTraits); // enforcing scenario forced traits
            for (int i = 0; i < numberInherited; i++)   // add parent traits first
            {
                int index = Rand.Range(0, parentTraitPool.Count);
                Trait trait = parentTraitPool[index];
                parentTraitPool.RemoveAt(index);
                if (!selectedTraits.Any(x => comparer.Equals(x, trait) ||
                                             x.def.ConflictsWith(trait)))
                    selectedTraits.Add(new Trait(trait.def, trait.Degree, false));
            }

            while (selectedTraits.Count < traitLimit && personalTraitPool.Count > 0)
            {
                int index = Rand.Range(0, personalTraitPool.Count); // getting trait and removing from the pull
                Trait trait = personalTraitPool[index];
                personalTraitPool.RemoveAt(index);

                if (!selectedTraits.Any(x => comparer.Equals(x, trait) ||  // skipping traits conflicting with already added
                                             x.def.ConflictsWith(trait)))
                    selectedTraits.Add(new Trait(trait.def, trait.Degree, false));

            }

            pawn.story.traits.allTraits = selectedTraits;
        }

        public override bool TryMergeWith(Hediff other)
        {
            return false;
        }
    }

    /// <summary>
    /// Copy from RJW
    /// </summary>
    public class TraitComparer : IEqualityComparer<Trait>
    {
        readonly bool ignoreForced;
        readonly bool ignoreDegree;

        public TraitComparer(bool ignoreDegree = false, bool ignoreForced = true)
        {
            this.ignoreDegree = ignoreDegree;
            this.ignoreForced = ignoreForced;
        }

        public bool Equals(Trait x, Trait y)
        {
            return
                x.def == y.def &&
                (ignoreDegree || (x.Degree == y.Degree)) &&
                (ignoreForced || (x.ScenForced == y.ScenForced));
        }

        public int GetHashCode(Trait obj)
        {
            return
                (obj.def.GetHashCode() << 5) +
                (ignoreDegree ? 0 : obj.Degree) +
                ((ignoreForced || obj.ScenForced) ? 0 : 0x10);
        }
    }

    public class RaceComparer : IEqualityComparer<Pawn>
    {
        public bool Equals(Pawn x, Pawn y)
        {
            return x.def.Equals(y.def);
        }

        public int GetHashCode(Pawn obj)
        {
            return obj.def.GetHashCode();
        }
    }

    public class FatherComparer : IEqualityComparer<Pawn>
    {
        readonly Pawn mother;

        public FatherComparer(Pawn mother)
        {
            this.mother = mother;
        }

        public bool Equals(Pawn x, Pawn y)
        {
            if (Utility.GetFather(x, mother) == null && Utility.GetFather(y, mother) == null) return true;
            return Utility.GetFather(x, mother)?.Label.Equals(Utility.GetFather(y, mother)?.Label) ?? false;
        }

        public int GetHashCode(Pawn obj)
        {
            return obj.def.GetHashCode();
        }
    }

}


code:
using RimWorld;
using Verse;

namespace RJW_Menstruation
{
    public class IngestionOutcomeDoer_GiveHediff_StackCount : IngestionOutcomeDoer_GiveHediff
    {
        protected override void DoIngestionOutcomeSpecial(Pawn pawn, Thing ingested)
        {
            Hediff hediff = HediffMaker.MakeHediff(hediffDef, pawn);
            float effect = ((!(severity > 0f)) ? hediffDef.initialSeverity : severity) * ingested.stackCount;
            AddictionUtility.ModifyChemicalEffectForToleranceAndBodySize_NewTemp(pawn, toleranceChemical, ref effect, multiplyByGeneToleranceFactors);
            hediff.Severity = effect;
            pawn.health.AddHediff(hediff);
        }
    }
}


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

namespace RJW_Menstruation
{
    public class JobDriver_VaginaWashing : JobDriver
    {
        const int excretingTime = 300;//ticks - 120 = 2 real seconds, 3 in-game minutes

        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            return pawn.Reserve(pawn, job, 1, -1, null, errorOnFailed);
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList();
            this.FailOn(delegate
            {
                return comps.All(comp => comp.TotalCumPercent < 0.001);
            });
            Toil excreting = Toils_General.Wait(excretingTime, TargetIndex.None);//duration of

            excreting.WithProgressBarToilDelay(TargetIndex.A);
            yield return excreting;
            yield return new Toil()
            {
                initAction = delegate ()
                {
                    foreach (HediffComp_Menstruation comp in comps)
                        comp.CumOut(null, 0.5f);
                    if (comps.Any(comp => comp.TotalCumPercent > 0.001)) JumpToToil(excreting);
                }
            };
            //yield return excreting;
            yield break;
        }
    }

    public class JobDriver_MilkSelf : JobDriver
    {
        protected float progress = 0;
        protected float MilkingTime
        {
            get
            {
                return 250f * Fullness + 50f;
            }
        }
        protected virtual float Fullness
        {
            get
            {
                return comp?.Fullness ?? 0;
            }
        }

        private CompMilkable comp;

        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            return pawn.Reserve(pawn, job, 1, -1, null, errorOnFailed);
        }

        protected virtual void PreMakeNewToils()
        {
            comp = pawn.GetComp<CompMilkable>();
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            PreMakeNewToils();
            this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
            this.FailOnNotCasualInterruptible(TargetIndex.A);
            Toil milking = new Toil
            {
                initAction = delegate ()
                {
                    pawn.pather.StopDead();
                },
                tickAction = MilkingTick
            };
            milking.AddFinishAction(Finish);
            milking.defaultCompleteMode = ToilCompleteMode.Never;
            milking.WithProgressBar(TargetIndex.A, () => progress / MilkingTime);
            yield return milking;
            yield break;
        }

        protected void MilkingTick()
        {
            progress += pawn.GetStatValue(StatDefOf.AnimalGatherSpeed);
            if (progress > MilkingTime)
            {
                Gathered();
                pawn.jobs.EndCurrentJob(JobCondition.Succeeded);
            }
            PostTickAction();
        }

        protected virtual void Gathered()
        {
            pawn.GetComp<CompMilkable>().Gathered(pawn);
        }

        protected virtual void Finish()
        {
            if (pawn.CurJobDef == JobDefOf.Wait_MaintainPosture)
            {
                pawn.jobs.EndCurrentJob(JobCondition.InterruptForced);
            }
        }

        protected virtual void PostTickAction()
        {
        }

    }

}


code:
using Verse;

namespace RJW_Menstruation
{
    public class MenstruationModExtension : DefModExtension
    {
        public float eggLifeTimeFactor = 1.0f;
        public bool neverEstrus = false;
        public bool alwaysEstrus = false;
        public float ovulationFactor = 1.0f;
        public bool noBleeding = false;

        public bool disableCycle = false;
    }
}


code:
using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Verse;

namespace RJW_Menstruation
{
    [HarmonyPatch(typeof(Hediff_Pregnant), "Miscarry")]
    public class Miscarry_Patch
    {
        public static void Postfix(Hediff_Pregnant __instance)
        {
            HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
            if (comp == null) return;
            comp.Pregnancy = null;
        }
    }

    [HarmonyPatch(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.StartLabor))]
    public class StartLabor_Patch
    {
        public static void Postfix(Hediff_Pregnant __instance)
        {
            HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
            if (comp == null) return;
            comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLabor);
        }
    }

    [HarmonyPatch(typeof(Hediff_Labor), nameof(Hediff_Labor.PreRemoved))]
    public class Labor_PreRemoved_Patch
    {
        public static void Postfix(Hediff_Labor __instance)
        {
            HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
            if (comp == null) return;
            comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLaborPushing);
        }
    }

    [HarmonyPatch(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PreRemoved))]
    public class LaborPushing_PreRemoved_Patch
    {
        public static void Postfix(Hediff_LaborPushing __instance)
        {
            HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
            if (comp == null) return;
            comp.Pregnancy = null;
        }
    }

    // Prevents a pregnancy from going into labor if another pregnancy already is
    [HarmonyPatch(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.GestationProgress), MethodType.Getter)]
    public class Hediff_Pregnant_GestationProgess_Patch
    {
        public static void Postfix(Hediff_Pregnant __instance, ref float __result)
        {
            if (__result < 1f) return;
            Pawn pawn = __instance.pawn;
            if (pawn.health.hediffSet.hediffs.Any(hediff => hediff.def == HediffDefOf.PregnancyLabor || hediff.def == HediffDefOf.PregnancyLaborPushing))
                __result = 0.999f;
        }
    }

    [HarmonyPatch(typeof(Recipe_ExtractOvum), nameof(Recipe_ExtractOvum.AvailableReport))]
    public class ExtractOvum_AvailableReport_Patch
    {
        public static void Postfix(Thing thing, ref AcceptanceReport __result)
        {
            if (!__result.Accepted) return;
            Pawn pawn = (Pawn)thing;
            if (pawn.IsRJWPregnant())
            {
                __result = "CannotPregnant".Translate();
                return;
            }
            List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList();
            if (!comps.Any()) return;
            if (comps.All(comp => comp.ovarypower <= 0))
            {
                __result = Translations.CannotNoEggs;
                return;
            }
            return;
        }
    }

    [HarmonyPatch(typeof(Recipe_ExtractOvum), "OnSurgerySuccess")]
    public class ExtractOvum_OnSurgerySuccess_Patch
    {
        public static void Postfix(Pawn pawn)
        {
            List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList();
            if (!comps.Any()) return;
            HediffComp_Menstruation mostEggs = comps.MaxBy(comp => comp.ovarypower);
            if (mostEggs.ovarypower <= 0) return;   // Shouldn't happen
            mostEggs.ovarypower--;
        }
    }

    [HarmonyPatch(typeof(Recipe_ImplantEmbryo), nameof(Recipe_ImplantEmbryo.ApplyOnPawn))]
    public class ImplantEmbryo_ApplyOnPawn_Patch
    {
        public static void Postfix(Pawn pawn)
        {
            foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
                comp.TakeLoosePregnancy();
        }
    }

    [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))]
    public class ApplyBirthOutcome_Breast_Patch
    {
        public static void Postfix(Thing birtherThing)
        {
            if (birtherThing is Pawn pawn && !pawn.Dead)
                pawn.GetBreastComp()?.GaveBirth();
        }
    }

    [HarmonyPatch]
    public class TerminatePregnancy_Patch
    {
        public static IEnumerable<MethodBase> TargetMethods()
        {
            yield return AccessTools.Method(typeof(PregnancyUtility), nameof(PregnancyUtility.TryTerminatePregnancy));
            yield return AccessTools.Method(typeof(Recipe_TerminatePregnancy), nameof(Recipe_TerminatePregnancy.ApplyOnPawn));
        }

        private static PregnancyAttitude? GetAttitude(Hediff pregnancy)
        {
            if (pregnancy is Hediff_Pregnant preg) return preg.Attitude;
            else return null;
        }

        private static Hediff GetEarliestPregnancy(Pawn pawn)
        {
            Hediff Earliest_Pregnancy = PregnancyUtility.GetPregnancyHediff(pawn);
            foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
            {
                Hediff pregnancy = comp.Pregnancy;
                if (pregnancy == null) continue;
                if (Earliest_Pregnancy == null || Earliest_Pregnancy.Severity > pregnancy.Severity) Earliest_Pregnancy = pregnancy;
            }

            return Earliest_Pregnancy;
        }

        private static readonly MethodInfo GetPregnancyHediff = AccessTools.Method(typeof(PregnancyUtility), nameof(PregnancyUtility.GetPregnancyHediff), new Type[] { typeof(Pawn) });
        private static readonly MethodInfo Get_Attitude = AccessTools.DeclaredPropertyGetter(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.Attitude));

        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            if (GetPregnancyHediff?.ReturnType != typeof(Hediff)) throw new InvalidOperationException("GetPregnancyHediff not found");
            if (Get_Attitude == null || Nullable.GetUnderlyingType(Get_Attitude.ReturnType) != typeof(PregnancyAttitude)) throw new InvalidOperationException("get_Attitude not found");
            foreach (CodeInstruction instruction in instructions)
            {
                if (instruction.Calls(GetPregnancyHediff))
                    yield return CodeInstruction.Call(typeof(TerminatePregnancy_Patch), nameof(TerminatePregnancy_Patch.GetEarliestPregnancy));
                // Menstruation pregnancies don't have an attitude, so skip the cast to Hediff_Pregnant and call a version that handles it
                else if (instruction.opcode == OpCodes.Castclass && (Type)instruction.operand == typeof(Hediff_Pregnant))
                    yield return new CodeInstruction(OpCodes.Nop);
                else if (instruction.Calls(Get_Attitude))
                    yield return CodeInstruction.Call(typeof(TerminatePregnancy_Patch), nameof(TerminatePregnancy_Patch.GetAttitude));
                else yield return instruction;
            }
        }
    }

    [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.TryTerminatePregnancy))]
    public class PregnancyUtility_TryTerminatePregnancy_Patch
    {
        public static void Postfix(bool __result, Pawn pawn)
        {
            if (__result)
                foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
                    _ = comp.Pregnancy; // get_Pregnancy will remove the hediff attached to the comp that doesn't have it anymore
        }
    }

    [HarmonyPatch(typeof(Recipe_TerminatePregnancy), nameof(Recipe_TerminatePregnancy.AvailableOnNow))]
    public class TerminatePregnancy_AvailableOnNow_Patch
    {
        public static void Postfix(ref bool __result, Thing thing)
        {
            if (!ModsConfig.BiotechActive || !(thing is Pawn pawn)) return;
            __result |= pawn.GetMenstruationComps().Any(comp => comp.Pregnancy != null);
        }
    }

    [HarmonyPatch(typeof(Pawn_GeneTracker), "Notify_GenesChanged")]
    public class Notify_GenesChanged_Patch
    {
        public static void Postfix(Pawn_GeneTracker __instance, GeneDef addedOrRemovedGene)
        {
            if (!addedOrRemovedGene.HasModExtension<MenstruationModExtension>()) return;
            foreach (HediffComp_Menstruation comp in __instance.pawn.GetMenstruationComps())
                comp.Notify_UpdatedGenes();
        }
    }

    [HarmonyPatch(typeof(StatPart_FertilityByGenderAge), "AgeFactor")]
    public class AgeFactor_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;
        }
    }

    //[HarmonyPatch(typeof(ChildcareUtility), nameof(ChildcareUtility.SuckleFromLactatingPawn))]
    //public class GreedyConsume_Patch
    //{
    //    private static float ConsumeAndAdjustNipples(HediffComp_Chargeable instance, float desiredCharge)
    //    {
    //          // Pulling breast comp every tick might be too much for performance.
    //        const float averageNippleChangePerTick = 0.0025f / 50000f;
    //        instance.Pawn.GetBreastComp()?.AdjustNippleProgress(Rand.Range(0.0f, averageNippleChangePerTick * 2) * Configurations.MaxBreastIncrementFactor);
    //        return instance.GreedyConsume(desiredCharge);
    //    }

    //    private static readonly MethodInfo GreedyConsume = AccessTools.Method(typeof(HediffComp_Chargeable), nameof(HediffComp_Chargeable.GreedyConsume), new Type[] { typeof(HediffComp_Chargeable), typeof(float) });
    //    public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
    //    {
    //        if(GreedyConsume == null) throw new InvalidOperationException("GreedyConsume not found");
    //        foreach (var instruction in instructions)
    //        {
    //            if (instruction.Calls(GreedyConsume))
    //                yield return CodeInstruction.Call(typeof(GreedyConsume_Patch), nameof(GreedyConsume_Patch.ConsumeAndAdjustNipples));
    //            yield return instruction;
    //        }
    //    }
    //}
}


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

namespace RJW_Menstruation.Patch
{
    [HarmonyPatch(typeof(WorldPawnGC), "GetCriticalPawnReason")]
    public static class GetCriticalPawnReason_Patch
    {
        readonly public static HashSet<Pawn> cummedPawns = new HashSet<Pawn>();

        public static void Postfix(ref string __result, Pawn pawn)
        {
            if (!__result.NullOrEmpty()) return;
            if (cummedPawns.Contains(pawn))
                __result = "EggFertOrCumInWomb";
        }
    }

    [HarmonyPatch(typeof(WorldPawnGC), "AccumulatePawnGCData")]
    public static class AccumulatePawnGCData_Patch
    {
        public static void Prefix()
        {
            GetCriticalPawnReason_Patch.cummedPawns.Clear();
            //            foreach(Pawn p in PawnsFinder.All_AliveOrDead)
            foreach (Pawn p in PawnsFinder.AllMapsCaravansAndTravelingTransportPods_Alive_OfPlayerFaction.Union(PawnsFinder.AllMapsCaravansAndTravelingTransportPods_Alive_PrisonersOfColony))
            {
                foreach (HediffComp_Menstruation comp in p.GetMenstruationComps())
                    GetCriticalPawnReason_Patch.cummedPawns.UnionWith(comp.GetCummersAndFertilizers());
            }
        }
    }
}


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

namespace RJW_Menstruation
{
    [HarmonyPatch(typeof(Pawn), nameof(Pawn.GetGizmos))]
    public class Pawn_GetGizmos
    {
        public static void Postfix(ref IEnumerable<Gizmo> __result, Pawn __instance)
        {
            if (!__instance.ShowStatus())
            {
                return;
            }

            if (__instance.ShouldShowWombGizmo())
            {
                __result = AddWombGizmos(__instance, __result);
            }
        }

        private static IEnumerable<Gizmo> AddWombGizmos(Pawn __instance, IEnumerable<Gizmo> gizmos)
        {
            foreach (Gizmo gizmo in gizmos)
                yield return gizmo;

            foreach (HediffComp_Menstruation comp in __instance.GetMenstruationComps())
                foreach (Gizmo gizmo in GetMenstruationGizmos(__instance, comp))
                    yield return gizmo;
        }

        public static List<Gizmo> GetMenstruationGizmos(Pawn pawn, HediffComp_Menstruation comp)
        {
            return new List<Gizmo>() { CreateGizmo_WombStatus(pawn, comp) };
        }

        private static Gizmo CreateGizmo_WombStatus(Pawn pawn, HediffComp_Menstruation comp)
        {
            if (!comp.loaded)
                comp.Initialize();
            Texture2D icon, icon_overlay;
            StringBuilder description = new StringBuilder();
            if (Configurations.Debug)
            {
                description
                    .AppendFormat("{0}: {1}\n", comp.curStage, comp.curStageTicks / GenDate.TicksPerHour);
                if (comp.Pregnancy is Hediff_BasePregnancy rjwpreg) description
                    .AppendFormat("due: {0}\n", rjwpreg.DueDate());
                else if (comp.Pregnancy is Hediff_Pregnant biopreg) description
                    .AppendFormat("due: {0}\n", biopreg.DueDate());
                description
                    .AppendFormat("fertcums: {0}\n" +
                                  "ovarypower: {1}\n" +
                                  "eggs: {2}\n",
                                  comp.TotalFertCum, comp.ovarypower, comp.GetNumOfEggs);
            }
            else description.AppendFormat("{0}\n", comp.GetCurStageLabel);
            if (pawn.IsRJWPregnant() || pawn.IsBiotechPregnant())
            {
                Hediff hediff = comp.Pregnancy;
                if (hediff != null && Utility.ShowFetusImage(hediff))
                {
                    icon = comp.GetPregnancyIcon(hediff);
                    float gestationProgress = comp.StageProgress;
                    if (hediff is Hediff_BasePregnancy || hediff is HediffWithParents)
                    {
                        if (gestationProgress < 0.2f) icon_overlay = comp.GetCumIcon();
                        else icon_overlay = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                    }
                    else icon_overlay = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                }
                else
                {
                    icon = comp.GetWombIcon();
                    icon_overlay = comp.GetCumIcon();
                }
            }
            else
            {
                Hediff hediff = pawn.health.hediffSet.GetFirstHediff<Hediff_InsectEgg>();
                if (hediff != null)
                {
                    icon = MenstruationUtility.GetInsectEggedIcon(comp);
                }
                else
                {
                    icon = comp.GetWombIcon();
                }
                icon_overlay = comp.GetCumIcon();

            }
            foreach (string s in comp.GetCumsInfo) description.AppendFormat("{0}\n", s);

            Color c = comp.GetCumMixtureColor;

            return new Gizmo_Womb
            {
                defaultLabel = pawn.LabelShort,
                defaultDesc = description.ToString(),
                icon = icon,
                icon_overlay = icon_overlay,
                shrinkable = Configurations.AllowShrinkIcon,
                cumcolor = c,
                comp = comp,
                //order = 100,
                hotKey = comp == pawn.GetFirstMenstruationComp() ? VariousDefOf.OpenStatusWindowKey : null,
                groupKey = 0,
                action = delegate
                {
                    Dialog_WombStatus.ToggleWindow(pawn, comp);
                }
            };
        }
    }

}


code:
using HarmonyLib;
using rjw;
using rjw.Modules.Interactions.Internals.Implementation;
using rjw.Modules.Interactions.Rules.PartKindUsageRules;
using rjw.Modules.Shared.Logs;
using System.Collections.Generic;
using System.Reflection;
using Verse;

namespace RJW_Menstruation
{
    [StaticConstructorOnStartup]
    internal static class First
    {
        static First()
        {
            Harmony har = new Harmony("RJW_Menstruation");
            har.PatchAll(Assembly.GetExecutingAssembly());
            if (ModsConfig.IsActive("erdelf.HumanoidAlienRaces"))   // Don't use the cached in Configurations, it isn't initialized yet
            {
                har.Patch(GenTypes.GetTypeInAnyAssembly("AlienRace.HarmonyPatches").GetMethod(nameof(AlienRace.HarmonyPatches.BirthOutcomeMultiplier)),
                    postfix: new HarmonyMethod(typeof(HAR_LitterSize_Undo).GetMethod(nameof(HAR_LitterSize_Undo.Postfix))));
            }

            InjectIntoRjwInteractionServices();
        }
        private static void InjectIntoRjwInteractionServices()
        {
            ILog log = LogManager.GetLogger("StaticConstructorOnStartup");

            List<IPartPreferenceRule> partKindUsageRules = Unprivater.GetProtectedValue<List<IPartPreferenceRule>>("_partKindUsageRules", typeof(PartPreferenceDetectorService));
            partKindUsageRules.Add(new Interactions.EstrusPartKindUsageRule());
            log.Message("Added 1 rule to PartPreferenceDetectorService._partKindUsageRules");
        }
    }
}


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

namespace RJW_Menstruation
{

    [HarmonyPatch(typeof(Pawn), nameof(Pawn.SpawnSetup))]
    public class Pawn_Patch
    {
        public static void Postfix(Pawn __instance)
        {
            //Log.Message("Initialize on spawnsetup");
            foreach (HediffComp_Menstruation comp in __instance.GetMenstruationComps())
            {
                comp.Initialize();
            }

            __instance.GetBreastComp()?.Initialize();
        }
    }

    [HarmonyPatch(typeof(FloatMenuMakerMap), "AddHumanlikeOrders")]
    public class HumanlikeOrder_Patch
    {
        public static void Postfix(Vector3 clickPos, Pawn pawn, List<FloatMenuOption> opts)
        {
            IEnumerable<LocalTargetInfo> selftargets = GenUI.TargetsAt(clickPos, TargetingParameters.ForSelf(pawn));

            foreach (LocalTargetInfo t in selftargets)
            {
                if (t.Pawn == pawn && pawn.HasMenstruationComp()) opts.AddDistinct(MakeSelfMenu(pawn, t));
                break;
            }
        }

        public static FloatMenuOption MakeSelfMenu(Pawn pawn, LocalTargetInfo target)
        {
            FloatMenuOption option = FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(Translations.FloatMenu_CleanSelf, delegate ()
                 {
                     pawn.jobs.TryTakeOrderedJob(new Verse.AI.Job(VariousDefOf.VaginaWashing, null, null, target.Cell));
                 }, MenuOptionPriority.Low), pawn, target);

            return option;
        }
    }

    [HarmonyPatch(typeof(HealthCardUtility), "DrawOverviewTab")]
    public class DrawOverviewTab_Patch
    {
        public const float buttonWidth = 50f;
        public const float buttonHeight = 20f;

        public static void Prefix(Rect leftRect, Pawn pawn, float curY)
        {
            if (Configurations.EnableButtonInHT && pawn.ShowStatus() && pawn.ShouldCycle())
            {
                HediffComp_Menstruation comp = pawn.GetFirstMenstruationComp();
                if (comp != null)
                {
                    Text.Font = GameFont.Tiny;
                    Rect buttonRect = new Rect(leftRect.xMax - buttonWidth - 8f, curY + 4f, buttonWidth, buttonHeight);
                    if (Widgets.ButtonText(buttonRect, Translations.Button_HealthTab))
                    {
                        Dialog_WombStatus.ToggleWindow(pawn, comp);
                    }
                }
            }
        }
    }

    [HarmonyPatch(typeof(CompBiosculpterPod), nameof(CompBiosculpterPod.CannotUseNowPawnCycleReason), new Type[] { typeof(Pawn), typeof(Pawn), typeof(CompBiosculpterPod_Cycle), typeof(bool) })]
    public class CannotUseNowPawnCycleReason_Patch
    {
        private const string eggRestorationKey = "eggRestoration";
        public static void Postfix(ref string __result, Pawn biosculptee, CompBiosculpterPod_Cycle cycle)
        {
            if (__result != null) return;
            if (cycle.Props.key == eggRestorationKey && !biosculptee.GetMenstruationComps().Any())
                __result = Translations.CannotNoWomb;
        }
    }

    // Doesn't cover everything, but at least it'll get the auto equip
    [HarmonyPatch(typeof(Apparel), nameof(Apparel.PawnCanWear))]
    public class PawnCanWear_Patch
    {
        public static void Postfix(ref bool __result, Apparel __instance, Pawn pawn)
        {
            if (__result && __instance is Absorber)
            {
                __result = pawn.ShouldCycle() && pawn.GetMenstruationComps().Any();
            }
        }
    }

    // Might cause issues when it comes to caravans
    //[HarmonyPatch(typeof(JobGiver_OptimizeApparel), nameof(JobGiver_OptimizeApparel.ApparelScoreRaw))]
    //public class ApparelScoreRaw_Patch
    //{
    //    public static void Postfix(ref float __result, Pawn pawn, Apparel ap)
    //    {
    //        if (__result > 0f && ap is Absorber && !pawn.GetMenstruationComps().Any(comp => comp.TotalCum > 0))
    //            __result = -10f;
    //    }
    //}
}