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

namespace RJW_Menstruation
{
    public class Hediff_MultiplePregnancy : Hediff_BasePregnancy
    {
        protected Dictionary<Pawn, Pawn> enzygoticSiblings;  // 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.Has(Quirk.Breeder) ||
                (pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) ||
                x.def.Equals(PawnRelationDefOf.Fiance))) != null)
                return;
            if (pawn.Has(Quirk.ImpregnationFetish) || pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Lover)) != null)
            {
                pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancyMild);
            }
            else
            {
                pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancy);
            }
        }

        public override void Miscarry()
        {
            this.GetMenstruationComp().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);
            }

            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.GetMenstruationComp();
            if(comp != null) comp.Pregnancy = null;
            pawn.GetBreastComp()?.GaveBirth();
        }

        public string GetBabyInfo()
        {
            if (babies.NullOrEmpty())
                return "Null";

            StringBuilder res = new StringBuilder();

            IEnumerable<Pawn> babiesdistinct = babies.Distinct(new RaceComparer());
            int iteration = 0;
            foreach (Pawn baby in babiesdistinct)
            {
                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 string GetFatherInfo()
        {
            if (babies.NullOrEmpty())
                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();

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

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

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

         protected 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.GetMenstruationComp();
            if (originalMenstruationComp != null)
            {
                part.GetMenstruationComp()?.CopyCycleProperties(originalMenstruationComp);
            }
            HediffComp_Breast originalBreastComp = originalPart.GetBreastComp();
            if (originalBreastComp != null)
            {
                part.GetBreastComp()?.CopyBreastProperties(originalBreastComp);
            }
        }

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

            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 virtual 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));
            if (baby.IsHAR())
                HARCompatibility.CopyHARPropertiesPostBirth(baby, original);
        }

        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)
                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()
        {
            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)
        {
            float melanin;
            string lastname;
            if (xxx.is_human(mother))
            {
                if (xxx.is_human(father))
                {
                    melanin = ChildRelationUtility.GetRandomChildSkinColor(father.story?.melanin ?? 0f, mother.story?.melanin ?? 0f);
                    //melanin = (mother.story?.melanin ?? 0f + father.story?.melanin ?? 0f) / 2;
                }
                else
                {
                    melanin = mother.story?.melanin ?? 0f;
                }
                lastname = NameTriple.FromString(mother.Name.ToStringFull).Last;
            }
            else if (xxx.is_human(father))
            {
                melanin = father.story?.melanin ?? 0f;
                lastname = NameTriple.FromString(father.Name.ToStringFull).Last;
            }
            else
            {
                melanin = Rand.Range(0, 1.0f);
                lastname = "";
            }

            PawnGenerationRequest request = new PawnGenerationRequest(
                newborn: true,
                allowDowned: true,
                faction: mother.Faction,
                canGeneratePawnRelations: false,
                forceGenerateNewPawn: true,
                colonistRelationChanceFactor: 0,
                allowFood: false,
                allowAddictions: false,
                relationWithExtraPawnChanceFactor: 0,
                fixedMelanin: melanin,
                fixedLastName: lastname,
                kind: BabyPawnKindDecider(mother, father),
                //fixedIdeo: mother.Ideo,
                forbidAnyTitle: true,
                forceNoBackstory: true
                );

            int division = 1;
            Pawn firstbaby = null;
            string firstheadpath = 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;
                if (division > 1)
                {
                    if (i == 0)
                    {
                        firstbaby = baby;
                        request.FixedGender = baby.gender;

                        if (baby.story != null)
                        {
                            firstheadpath = (string)baby.story.GetMemberValue("headGraphicPath");
                            if (firstheadpath == null)
                            {
                                Graphic_Multi head = GraphicDatabaseHeadRecords.GetHeadRandom(baby.gender, baby.story.SkinColor, baby.story.crownType, true);
                                if (head != null) baby.story.SetMemberValue("headGraphicPath", head.GraphicPath);
                                firstheadpath = (string)baby.story.GetMemberValue("headGraphicPath");
                            }
                        }
                    }
                    else
                    {
                        enzygoticSiblings?.Add(baby, firstbaby);

                        if (baby.story != null)
                        {
                            baby.story.hairDef = firstbaby.story.hairDef;
                            baby.story.hairColor = firstbaby.story.hairColor;
                            baby.story.bodyType = firstbaby.story.bodyType;
                            baby.story.crownType = firstbaby.story.crownType;
                            baby.story.SetMemberValue("headGraphicPath", firstheadpath);
                        }

                        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>
        /// Decide pawnkind from mother and father <para/>
        /// Come from RJW
        /// </summary>
        /// <param name="mother"></param>
        /// <param name="father"></param>
        /// <returns></returns>
        public PawnKindDef BabyPawnKindDecider(Pawn mother, Pawn father)
        {
            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();
            }

            return spawn_kind_def;

        }

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

        /// <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 void Initialize(Pawn mother, Pawn dad)
        {
            enzygoticSiblings = new Dictionary<Pawn, Pawn>();
            base.Initialize(mother, dad);
        }

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

        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(pawn, toleranceChemical, ref effect);
            hediff.Severity = effect;
            pawn.health.AddHediff(hediff);
        }

    }
}

license: unknown


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 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 = "Has cum or fertilized egg in a womb";
        }
    }

    [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 rjw;
using System.Collections.Generic;
using System.Linq;
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)
        {
            Texture2D icon, icon_overay;
            StringBuilder description = new StringBuilder();
            if (Configurations.Debug)
            {
                description
                    .AppendFormat("{0}: {1}\n", comp.curStage, comp.curStageHrs);
                if (comp.Pregnancy is Hediff_MultiplePregnancy preg) description
                    .AppendFormat("due: {0}\n", preg.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())
            {
                Hediff_BasePregnancy hediff = comp.Pregnancy;
                if (hediff != null && Utility.ShowFetusImage(hediff))
                {
                    icon = comp.GetPregnancyIcon(hediff);
                    if (hediff is Hediff_BasePregnancy h)
                    {
                        if (h.GestationProgress < 0.2f) icon_overay = comp.GetCumIcon();
                        else icon_overay = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                    }
                    else icon_overay = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                }
                else
                {
                    icon = comp.GetWombIcon();
                    icon_overay = comp.GetCumIcon();
                }
            }
            else
            {
                Hediff hediff = pawn.health.hediffSet.GetHediffs<Hediff_InsectEgg>().FirstOrDefault();
                if (hediff != null)
                {
                    icon = MenstruationUtility.GetInsectEggedIcon(comp);
                }
                else
                {
                    icon = comp.GetWombIcon();
                }
                icon_overay = 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_overay = icon_overay,
                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());

            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.Collections.Generic;
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();
            }

            HediffComp_Breast bcomp = __instance.GetBreastComp();
            if (bcomp != null)
            {
                bcomp.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), "DrawHediffListing")]
    //public class DrawHediffListing_Patch
    //{
    //    public const float buttonWidth = 80f;
    //    public const float buttonHeight = 20f;
    //
    //    public static void Postfix(Rect rect, Pawn pawn, bool showBloodLoss)
    //    {
    //        if (Configurations.EnableButtonInHT && pawn.HasMenstruationComp())
    //        {
    //            Rect buttonrect = new Rect(rect.xMax - buttonWidth, rect.yMax - buttonHeight, buttonWidth, buttonHeight);
    //            if (Widgets.ButtonText(buttonrect, "Status"))
    //            {
    //                Dialog_WombStatus.ToggleWindow(pawn,pawn.GetMenstruationComp());
    //            }
    //        }
    //
    //
    //    }
    //}

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

        private static HediffComp_Menstruation GetFirstMenstruation(IEnumerable<Hediff> diffs)
        {
            foreach (Hediff diff in diffs)
            {
                HediffComp_Menstruation comp = diff.GetMenstruationComp();
                if (comp != null) return comp;
            }
            return null;
        }

        public static void Prefix(Rect rect, Pawn pawn, IEnumerable<Hediff> diffs, ref float curY)
        {
            if (Configurations.EnableButtonInHT && pawn.ShowStatus())
            {
                HediffComp_Menstruation comp = GetFirstMenstruation(diffs);
                if (comp != null)
                {
                    Rect buttonrect = new Rect((rect.xMax) / 2 - 5f, curY + 2f, buttonWidth, buttonHeight);
                    if (Widgets.ButtonText(buttonrect, Translations.Button_HealthTab))
                    {
                        Dialog_WombStatus.ToggleWindow(pawn, comp);
                    }
                }
            }

        }

    }

    //Merged to RJW
    //[HarmonyPatch(typeof(PawnColumnWorker_Pregnant), "GetIconFor")]
    //public class PawnColumnWorker_Patch_Icon
    //{
    //    public static void Postfix(Pawn pawn, ref Texture2D __result)
    //    {
    //        if (pawn.IsVisiblyPregnant()) __result = ContentFinder<Texture2D>.Get("UI/Icons/Animal/Pregnant", true);
    //    }
    //
    //}
    //
    //[HarmonyPatch(typeof(PawnColumnWorker_Pregnant), "GetTooltipText")]
    //public class PawnColumnWorker_Patch_Tooltip
    //{
    //    public static bool Prefix(Pawn pawn, ref string __result)
    //    {
    //        float gestationProgress = PregnancyHelper.GetPregnancy(pawn).Severity;
    //        int num = (int)(pawn.RaceProps.gestationPeriodDays * 60000f);
    //        int numTicks = (int)(gestationProgress * (float)num);
    //        __result = "PregnantIconDesc".Translate(numTicks.ToStringTicksToDays("F0"), num.ToStringTicksToDays("F0"));
    //        return false;
    //    }
    //
    //}
    //
    //[HarmonyPatch(typeof(TransferableUIUtility), "DoExtraAnimalIcons")]
    //public class TransferableUIUtility_Patch_Icon
    //{
    //    //private static readonly Texture2D PregnantIcon = ContentFinder<Texture2D>.Get("UI/Icons/Animal/Pregnant", true);
    //
    //
    //
    //    public static void Postfix(Transferable trad, Rect rect, ref float curX, Texture2D ___PregnantIcon)
    //    {
    //        Pawn pawn = trad.AnyThing as Pawn;
    //        if (pawn?.health?.hediffSet != null && pawn.IsVisiblyPregnant())
    //        {
    //            Rect rect3 = new Rect(curX - 24f, (rect.height - 24f) / 2f, 24f, 24f);
    //            curX -= 24f;
    //            if (Mouse.IsOver(rect3))
    //            {
    //                TooltipHandler.TipRegion(rect3, PawnColumnWorker_Pregnant.GetTooltipText(pawn));
    //            }
    //            GUI.DrawTexture(rect3, ___PregnantIcon);
    //        }
    //    }
    //}

}


code:
using HarmonyLib;
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.IsAnimal() && !Configurations.EnableAnimalCycle) 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.Has(Quirk.ImpregnationFetish) || partner.Has(Quirk.ImpregnationFetish) || 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, 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.GetHediffs<Hediff_InsectEgg>().Any()) 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>
        private 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.IsAnimal() && !Configurations.EnableAnimalCycle) return true;
            HediffComp_Menstruation comp;
            if (pawn.Has(Quirk.ImpregnationFetish) || partner.Has(Quirk.ImpregnationFetish) || 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.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 (!Configurations.EnableAnimalCycle && pawn.IsAnimal()) 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 == null) 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.GetMenstruationComp()?.parent;
                if (vagina == null) vagina = 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.Has(Quirk.ImpregnationFetish) || !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 == null) 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 == null) throw new System.InvalidOperationException("MinimumAttractivenessToHookup not found");
            if (MinimumRelationshipToHookup == null) 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(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;
            }

        }
    }
}


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.7.2</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>
  </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="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\MenstruationUtility.cs" />
    <Compile Include="Hediff_Estrus.cs" />
    <Compile Include="IngestionOutcomeDoers.cs" />
    <Compile Include="Patch\GC_Patch.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="Patch\GetGizmos.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.3\Assemblies\AlienRace.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="AnimalGenetics">
      <HintPath>..\..\..\..\..\..\..\..\workshop\content\294100\2830943477\1.3\Assemblies\AnimalGenetics.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\..\..\..\rjw\1.3\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="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);
            if (fieldInfo != null)
            {
                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);
            if (fieldInfo != null)
            {
                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;
            }
        }
        // Sex drive
        public static Texture2D SexDriveTexture
        {
            get
            {
                if (sexdrivetexturecache == null) sexdrivetexturecache = SolidColorMaterials.NewSolidColorTexture(0.686f, 0.062f, 0.698f, 1.0f);
                return sexdrivetexturecache;
            }
        }
        // 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));
        public static readonly Texture2D GatherCum_Bucket = ContentFinder<Texture2D>.Get("UI/Icon/ToBucket");
        public static readonly Texture2D GatherCum_Pussy = ContentFinder<Texture2D>.Get("UI/Icon/ToPussy");

        private static Texture2D milktexturecache = SolidColorMaterials.NewSolidColorTexture(0.992f, 1.0f, 0.960f, 1.0f);
        private static Texture2D sexdrivetexturecache = 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.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 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 wearhours = 0;
        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() { }

        public virtual void WearEffect()
        {
            absorbedfluids += 0.1f;
            if (dirty) wearhours++;
        }

        public override Color DrawColorTwo => fluidColor;

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Values.Look(ref absorbedfluids, "absorbedfluids", absorbedfluids, true);
            Scribe_Values.Look(ref dirty, "dirty", dirty, true);
            Scribe_Values.Look(ref wearhours, "wearhours", wearhours, true);
            Scribe_Values.Look(ref fluidColor, "fluidColor", fluidColor, true);
        }

    }

    public class Absorber_Tampon : Absorber
    {

        public override void WearEffect()
        {
            if (dirty) wearhours++;
            absorbedfluids += 0.5f;
        }

        public override void DirtyEffect()
        {
            if (wearhours > MinHrstoDirtyEffect && Rand.Chance(0.01f) && !Wearer.apparel.IsLocked(this))
            {
                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, true);
        }

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