code:
using Verse;

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

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

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

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

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

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

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


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

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

        public Dialog_HybridCustom()
        {
            BuildRaceList();
        }

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

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

        }

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

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

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

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

            DoMainContents(mainRect);

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

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

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

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

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

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

        }

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

            Widgets.DrawHighlightIfMouseover(rect);
        }

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

        }

    }

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

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

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

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

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

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

            DoMainContents(mainRect);

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

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

        }

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

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

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

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

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

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

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

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

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

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

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

            }

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

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

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

            }
            buttonRect.x -= 80f;

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

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

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

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

        }

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

    }

}

license: unknown


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

namespace RJW_Menstruation
{
    public class Dialog_WombStatus : Window
    {
        public Pawn pawn;
        private HediffComp_Menstruation comp;
        private const float windowMargin = 20f;
        private const float pawnRectWidth = 150f;
        private const float pawnRectHeight = 150f;
        private const float wombRectHeight = 300f;
        private const float wombRectWidth = 300f;
        private const float fontheight = 30;
        private const float genitalRectWidth = 102;
        private const float genitalRectHeight = 140;
        private const float breastRectWidth = 102;
        private const float breastRectHeight = 100;

        private static bool naked = false;

        private Texture2D womb;
        private Texture2D cum;
        private Texture2D vagina;
        private Texture2D anal;
        private Color cumcolor;

        private static GUIStyle fontstylecenter = null;
        private static GUIStyle fontstyleright = null;
        private static GUIStyle fontstyleleft = null;
        private static GUIStyle boxstyle = null;
        private static GUIStyle buttonstyle = null;

        public Pawn Pawn
        {
            get
            {
                return pawn;
            }
        }
        public HediffComp_Menstruation Comp
        {
            get
            {
                return comp;
            }
        }

        public override Vector2 InitialSize
        {
            get
            {
                float width = 450f + 2 * windowMargin;
                float height = 780f + 2 * windowMargin;
                if (!Configurations.DrawWombStatus) height -= wombRectHeight;
                if (!Configurations.DrawVaginaStatus || pawn.IsAnimal()) width -= 150f;
                return new Vector2(width, height);
            }
        }

        public Dialog_WombStatus(Pawn pawn, HediffComp_Menstruation comp)
        {
            this.pawn = pawn;
            this.comp = comp;
        }

        public void ChangePawn(Pawn pawn, HediffComp_Menstruation comp)
        {

            if (this.pawn.IsAnimal() && !pawn.IsAnimal()) windowRect.width += 150f;
            else if (!this.pawn.IsAnimal() && pawn.IsAnimal()) windowRect.width -= 150f;
            this.pawn = pawn;
            this.comp = comp;
        }

        public static void ToggleWindow(Pawn pawn, HediffComp_Menstruation comp)
        {
            Dialog_WombStatus window = (Dialog_WombStatus)Find.WindowStack.Windows.FirstOrDefault(x => x.GetType().Equals(typeof(Dialog_WombStatus)));
            if (window != null)
            {
                List<Pawn> pawns = Find.Selector.SelectedPawns.FindAll(x => x.ShouldShowWombGizmo());
                if (Input.GetKey(VariousDefOf.OpenStatusWindowKey.MainKey) && !pawns.NullOrEmpty() && pawns.Count > 1)
                {
                    int index = pawns.IndexOf(window.pawn);
                    SoundDefOf.TabOpen.PlayOneShotOnCamera();
                    Pawn newpawn = pawns[(index + 1) % pawns.Count];
                    window.ChangePawn(newpawn, newpawn.GetFirstMenstruationComp());
                }
                else if (window.pawn != pawn)
                {
                    SoundDefOf.TabOpen.PlayOneShotOnCamera();
                    window.ChangePawn(pawn, comp);
                }
                else Find.WindowStack.TryRemove(typeof(Dialog_WombStatus), true);
            }
            else
            {
                SoundDefOf.InfoCard_Open.PlayOneShotOnCamera();
                Find.WindowStack.Add(new Dialog_WombStatus(pawn, comp));
            }

        }

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

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

            Rect windowRect = inRect.ContractedBy(windowMargin);
            Rect mainRect = new Rect(windowRect.x, windowRect.y, windowRect.width, windowRect.height);
            Rect closeRect = new Rect(windowRect.xMax, 0f, 20f, 20f);
            MainContents(mainRect);
            if (Widgets.CloseButtonFor(closeRect))
            {
                Close();
            }
            closeRect.x -= 20f;

        }

        public override void PreOpen()
        {
            base.PreOpen();
            if (fontstylecenter == null) fontstylecenter = new GUIStyle() { alignment = TextAnchor.MiddleCenter };
            if (fontstyleright == null) fontstyleright = new GUIStyle() { alignment = TextAnchor.MiddleRight };
            if (fontstyleleft == null) fontstyleleft = new GUIStyle() { alignment = TextAnchor.MiddleLeft };
            if (boxstyle == null) boxstyle = new GUIStyle(GUI.skin.textArea);
            if (buttonstyle == null) buttonstyle = new GUIStyle(GUI.skin.button);
        }

        private void MainContents(Rect mainRect)
        {

            boxstyle.hover = boxstyle.normal;
            boxstyle.onHover = boxstyle.normal;
            boxstyle.onNormal = boxstyle.normal;

            buttonstyle.onHover = buttonstyle.onNormal;
            buttonstyle.hover = buttonstyle.normal;
            boxstyle.border.left = 4; boxstyle.border.right = 4; boxstyle.border.bottom = 4; boxstyle.border.top = 4;

            fontstyleleft.normal.textColor = Color.white;

            float preginfoheight = 0f;
            Hediff_BasePregnancy hediff = comp.Pregnancy;
            if (hediff != null && Utility.ShowFetusImage(hediff))
            {
                womb = comp.GetPregnancyIcon(hediff);
                if (hediff is Hediff_MultiplePregnancy m)
                {
                    if (m.GestationProgress < 0.2f) cum = comp.GetCumIcon();
                    else cum = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                    Pawn fetus = comp.GetFetus();
                    if (fetus != null && Utility.ShowFetusInfo())
                    {
                        string feinfo = m.GetBabyInfo();
                        string fainfo = m.GetFatherInfo() + "  ";
                        if (feinfo.Length + fainfo.Length > 45)
                        {
                            preginfoheight = fontheight + 2;
                            buttonstyle.alignment = TextAnchor.UpperLeft;
                            fontstyleright.alignment = TextAnchor.LowerRight;
                        }
                        else
                        {
                            preginfoheight = fontheight;
                            buttonstyle.alignment = TextAnchor.MiddleLeft;

                        }
                        Rect preginfo = new Rect(0f, mainRect.yMax - wombRectHeight - 2, wombRectWidth, preginfoheight);
                        fontstyleright.normal.textColor = Color.white;
                        GUI.Box(preginfo, feinfo, buttonstyle);
                        GUI.Label(preginfo, fainfo, fontstyleright);
                    }

                }
                else if (hediff is Hediff_BasePregnancy b)
                {
                    if (b.GestationProgress < 0.2f) cum = comp.GetCumIcon();
                    else cum = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
                    Pawn fetus = comp.GetFetus();
                    if (fetus != null && Utility.ShowFetusInfo())
                    {
                        preginfoheight = fontheight;
                        Rect preginfo = new Rect(0f, mainRect.yMax - wombRectHeight - 2, wombRectWidth, preginfoheight);
                        fontstyleright.normal.textColor = Color.white;
                        fontstyleright.alignment = TextAnchor.MiddleRight;
                        buttonstyle.alignment = TextAnchor.MiddleLeft;

                        string father;

                        if (!b.is_parent_known && Configurations.InfoDetail != Configurations.DetailLevel.All)
                            father = Translations.Dialog_FatherUnknown;
                        else
                            father = b.father.LabelShort;

                        GUI.Box(preginfo, b.babies.Count + " " + fetus.def.label + " " + Translations.Dialog_WombInfo02, buttonstyle);
                        GUI.Label(preginfo, Translations.Dialog_WombInfo03 + ": " + father + "  ", fontstyleright);
                    }
                }
                else cum = ContentFinder<Texture2D>.Get(("Womb/Empty"), true);
            }
            else
            {
                womb = comp.GetWombIcon();
                cum = comp.GetCumIcon();
            }

            Rect pawnRect = new Rect(0, 0, pawnRectWidth, pawnRectHeight);
            Widgets.DrawTextureFitted(pawnRect, PortraitsCache.Get(pawn, pawnRect.size, Rot4.South, default, 1, true, true, false, !naked), 1.0f);
            if (Widgets.ButtonInvisible(pawnRect))
            {
                naked = !naked;
            }
            Rect pawnLabelRect = new Rect(0, pawnRectHeight, pawnRectWidth, fontheight - 10);
            Rect pawnLabel2Rect = new Rect(0, pawnRectHeight + fontheight - 10, pawnRectWidth, fontheight - 10);
            fontstylecenter.normal.textColor = pawn.DrawColor;
            GUI.Label(pawnLabelRect, pawn.Name?.ToStringFull ?? pawn.Label, fontstylecenter);
            if (pawn.story != null) GUI.Label(pawnLabel2Rect, pawn.ageTracker.AgeBiologicalYears + ", " + pawn.story.Title, fontstylecenter);
            GUI.color = Color.white;

            float wombrecth = 0;
            if (Configurations.DrawWombStatus)
            {
                wombrecth = wombRectHeight;
                cumcolor = comp.GetCumMixtureColor;
                Rect wombRect = new Rect(0f, mainRect.yMax - wombRectHeight + preginfoheight, wombRectWidth, wombRectWidth * 0.9f);
                DrawWomb(wombRect);

                if (Configurations.DrawEggOverlay)
                {
                    comp.DrawEggOverlay(wombRect, true);
                }

            }

            Rect wombInfoRect = new Rect(0f, mainRect.yMax - wombrecth - fontheight - 2, wombRectWidth, fontheight - 4f);
            Rect progressRect = new Rect(wombInfoRect.x, wombInfoRect.yMax, wombRectWidth, 4f);
            buttonstyle.normal.textColor = Color.white;
            //boxstyle.normal.background = Texture2D.whiteTexture;
            buttonstyle.alignment = TextAnchor.MiddleLeft;
            GUI.backgroundColor = new Color(0.24f, 0.29f, 0.35f, 1);
            GUI.Box(wombInfoRect, Translations.Dialog_WombInfo01 + ": " + comp.GetCurStageLabel, buttonstyle);
            Widgets.FillableBar(progressRect, comp.StageProgress, comp.GetStageTexture);
            GUI.color = Color.white;
            TooltipHandler.TipRegion(wombInfoRect, comp.GetCurStageDesc);

            fontstyleright.normal.textColor = Color.red;
            fontstyleright.alignment = TextAnchor.MiddleRight;
            //if (comp.GetFertilization) GUI.Label(wombInfoRect, Translations.Dialog_WombInfo05 + "  ", fontstyleright);
            //else if (comp.GetEggFertilizing) GUI.Label(wombInfoRect, Translations.Dialog_WombInfo06 + "  ", fontstyleright);
            //else if (comp.GetEgg) GUI.Label(wombInfoRect, Translations.Dialog_WombInfo07 + "  ", fontstyleright);
            GUI.Label(wombInfoRect, comp.GetFertilizingInfo + "  ", fontstyleright);

            //Widgets.Label(wombInfoRect,Translations.Dialog_WombInfo01 + ": " + comp.GetCurStageLabel);

            Rect cumlistTitle, cumlistRect;
            if (Configurations.DrawVaginaStatus && !pawn.IsAnimal())
            {
                Rect genitalRect = new Rect(pawnRectWidth + 24, pawnRectHeight + 2 * fontheight, genitalRectWidth, genitalRectHeight + fontheight * 2);
                Rect breastRect = new Rect(pawnRectWidth + 24, 40f, breastRectWidth, breastRectHeight + fontheight);
                DrawVagina(genitalRect, comp);
                DrawBreast(breastRect);
                cumlistTitle = new Rect(wombRectWidth + 4f, 0, 150f, fontheight);
                cumlistRect = new Rect(wombRectWidth + 4f, fontheight, 150f, mainRect.yMax - fontheight + preginfoheight + 3);
            }
            else
            {
                cumlistTitle = new Rect(pawnRectWidth, 0, 150f, fontheight);
                cumlistRect = new Rect(pawnRectWidth, fontheight, 150f, mainRect.yMax - wombRectHeight - fontheight);
            }

            Rect infoRect = new Rect(0, pawnRectHeight + 2 * fontheight, pawnRectWidth, pawnRect.yMax + 2 * fontheight - wombInfoRect.y);
            DrawInfos(infoRect);

            GUI.Label(cumlistTitle, Translations.Dialog_WombInfo04);
            DrawCumlist(cumlistRect);

        }

        private void DrawCumlist(Rect rect)
        {
            Listing_Standard cumlist = new Listing_Standard
            {
                maxOneColumn = true,
                ColumnWidth = wombRectWidth - pawnRectWidth
            };
            cumlist.Begin(rect);
            Listing_Standard cumlistsection = cumlist.BeginSection(rect.height - fontheight - 12f);
            foreach (string s in comp.GetCumsInfo)
            {
                cumlistsection.Label(s);
            }
            cumlist.EndSection(cumlistsection);
            cumlist.End();
        }

        private void DrawWomb(Rect rect)
        {
            GUI.color = new Color(1.00f, 0.47f, 0.47f, 1);
            GUI.Box(rect, "", boxstyle);
            //GUI.color = Color.white;
            //Widgets.DrawTextureFitted(wombRect, womb,1.0f);
            //GUI.color = cumcolor;
            //Widgets.DrawTextureFitted(wombRect, cum,1.0f);
            GUI.DrawTexture(rect, womb, ScaleMode.ScaleToFit, true, 0, Color.white, 0, 0);
            GUI.DrawTexture(rect, cum, ScaleMode.ScaleToFit, true, 0, cumcolor, 0, 0);

            GUI.color = Color.white;

        }

        private void DrawVagina(Rect rect, HediffComp_Menstruation comp)
        {
            Rect genitalIconRect = new Rect(rect.x, rect.y + fontheight, genitalRectWidth, genitalRectHeight);
            Rect genitalVaginaLabelRect = new Rect(rect.x, rect.y + 10f, genitalRectWidth, fontheight);
            Rect genitalAnusLabelRect = new Rect(rect.x, rect.y + fontheight + genitalRectHeight, genitalRectWidth, fontheight);
            bool showOrigin = Mouse.IsOver(genitalIconRect) && Input.GetMouseButton(0);

            vagina = pawn.GetGenitalIcon(comp, showOrigin);
            anal = pawn.GetAnalIcon(showOrigin);
            GUI.color = new Color(1.00f, 0.47f, 0.47f, 1);
            GUI.Box(rect, "", boxstyle);
            GUI.color = pawn.story.SkinColor;
            //Widgets.DrawTextureFitted(genitalIconRect, anal, 1.0f);
            //Widgets.DrawTextureFitted(genitalIconRect, vagina, 1.0f);
            GUI.DrawTexture(genitalIconRect, anal, ScaleMode.ScaleToFit);
            GUI.DrawTexture(genitalIconRect, vagina, ScaleMode.ScaleToFit);

            GUI.color = Color.white;
            GUI.Label(genitalVaginaLabelRect, pawn.GetVaginaLabel(), fontstylecenter);
            GUI.Label(genitalAnusLabelRect, pawn.GetAnusLabel(), fontstylecenter);
        }

        private void DrawBreast(Rect rect)
        {
            Rect BreastIconRect = new Rect(rect.x, rect.y + fontheight, breastRectWidth, breastRectHeight);
            Rect BreastLabelRect = new Rect(rect.x, rect.y, breastRectWidth, fontheight);
            Rect MilkGaugeRect = new Rect(rect.x, rect.yMax, breastRectWidth, 20f);

            GUI.color = new Color(1.00f, 0.47f, 0.47f, 1);
            GUI.Box(rect, "", boxstyle);

            pawn.DrawBreastIcon(BreastIconRect);

            GUI.color = Color.white;
            GUI.Label(BreastLabelRect, pawn.GetBreastLabel(), fontstylecenter);

            pawn.DrawMilkBars(MilkGaugeRect);

        }

        private void DrawInfos(Rect rect)
        {
            Rect lineRect = new Rect(rect.x, rect.y, rect.width, 20f);
            float statvalue;
            const float height = 24f;
            statvalue = pawn.GetStatValue(xxx.sex_drive_stat);
            FillableBarLabeled(lineRect, "  " + xxx.sex_drive_stat.LabelCap.CapitalizeFirst() + " " + statvalue.ToStringPercent(), statvalue / 2, TextureCache.SexDriveTexture, Texture2D.blackTexture, xxx.sex_drive_stat.description);
            lineRect.y += height;

            statvalue = pawn.GetStatValue(xxx.vulnerability_stat);
            FillableBarLabeled(lineRect, "  " + xxx.vulnerability_stat.LabelCap.CapitalizeFirst() + " " + statvalue.ToStringPercent(), statvalue / 2, TextureCache.BleedingTexture, Texture2D.blackTexture, xxx.vulnerability_stat.description);
            lineRect.y += height;

            statvalue = pawn.records.GetValue(xxx.CountOfBirthHuman);
            FillableBarLabeled(lineRect, "  " + xxx.CountOfBirthHuman.LabelCap.CapitalizeFirst() + " " + statvalue, statvalue / 10, TextureCache.FollicularTexture, Texture2D.blackTexture, xxx.CountOfBirthHuman.description);
            lineRect.y += height;

            statvalue = pawn.records.GetValue(xxx.CountOfBirthAnimal);
            FillableBarLabeled(lineRect, "  " + xxx.CountOfBirthAnimal.LabelCap.CapitalizeFirst() + " " + statvalue, statvalue / 20, TextureCache.AnimalTexture, Texture2D.blackTexture, xxx.CountOfBirthAnimal.description);
            lineRect.y += height;

            statvalue = pawn.records.GetValue(xxx.CountOfBirthEgg);
            FillableBarLabeled(lineRect, "  " + xxx.CountOfBirthEgg.LabelCap.CapitalizeFirst() + " " + statvalue, statvalue / 100, TextureCache.RecoverTexture, Texture2D.blackTexture, xxx.CountOfBirthEgg.description);
            lineRect.y += height * 4;

            statvalue = Configurations.ImplantationChance * comp.ImplantFactor;
            float fertchance = comp.GetFertilityChance();
            FillableBarLabeled(lineRect, "  " + xxx.reproduction.LabelCap.CapitalizeFirst() + " " + statvalue.ToStringPercent(), statvalue, TextureCache.LutealTexture, Texture2D.blackTexture, Translations.FertilityDesc(String.Format("{0:0.##}", fertchance * 100)));
            Rect overayRect = new Rect(lineRect.x, lineRect.y, lineRect.width * Math.Min(1.0f, fertchance), lineRect.height);
            GUI.DrawTexture(overayRect, TextureCache.FertChanceTex);
            lineRect.y += height;
        }

        private void FillableBarLabeled(Rect rect, string label, float fillPercent, Texture2D filltexture, Texture2D bgtexture, string tooltip = null)
        {
            Widgets.FillableBar(rect, Math.Min(fillPercent, 1.0f), filltexture, bgtexture, true);
            GUI.Label(rect, label, fontstyleleft);
            Widgets.DrawHighlightIfMouseover(rect);
            if (tooltip != null) TooltipHandler.TipRegion(rect, tooltip);
        }

    }
}


code:
using UnityEngine;
using Verse;

namespace RJW_Menstruation
{
    public class Gizmo_Womb : Command_Action
    {
        public Texture2D icon_overay;
        public Color cumcolor;
        public HediffComp_Menstruation comp;

        public const float progressbarHeight = 2f;

        public override void DrawIcon(Rect rect, Material buttonMat, GizmoRenderParms parms)
        {
            Texture2D badTex = icon;
            Texture2D overay = icon_overay;
            Color color = cumcolor;

            if (badTex == null)
            {
                badTex = BaseContent.BadTex;
            }
            if (overay == null)
            {
                overay = BaseContent.BadTex;
            }
            if (color == null) color = Color.white;
            rect.position += new Vector2(iconOffset.x * rect.size.x, iconOffset.y * rect.size.y);
            GUI.color = IconDrawColor;
            Widgets.DrawTextureFitted(rect, badTex, this.iconDrawScale * 0.85f, this.iconProportions, this.iconTexCoords, this.iconAngle, buttonMat);
            GUI.color = color;
            Widgets.DrawTextureFitted(rect, overay, iconDrawScale * 0.85f, iconProportions, iconTexCoords, iconAngle, buttonMat);
            GUI.color = Color.white;
            if (Configurations.DrawEggOverlay) comp.DrawEggOverlay(rect, false);
            Rect progressRect = new Rect(rect.x + 2f, rect.y, rect.width - 4f, progressbarHeight);
            Widgets.FillableBar(progressRect, comp.StageProgress, comp.GetStageTexture);

        }

        public override bool GroupsWith(Gizmo other)
        {
            if (other is Gizmo_Womb womb && womb.comp != this.comp) return false;
            else return base.GroupsWith(other);
        }

    }
}


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

namespace RJW_Menstruation
{
    public static class Colors
    {
        public static Color blood = new Color(0.78f, 0, 0);
        //public static Color nippleblack = new Color(0.215f, 0.078f, 0); // 81,20,0
        public static ColorInt white = new ColorInt(255, 255, 255, 255);

        public static Color CMYKLerp(Color a, Color b, float t)
        {
            RGBtoCMYK(a, out float ac, out float am, out float ay, out float ak);
            RGBtoCMYK(b, out float bc, out float bm, out float by, out float bk);

            return CMYKtoRGB(Mathf.Lerp(ac, bc, t), Mathf.Lerp(am, bm, t), Mathf.Lerp(ay, by, t), Mathf.Lerp(ak, bk, t));
        }

        public static void RGBtoCMYK(Color rgb, out float c, out float m, out float y, out float k)
        {
            k = 1 - Math.Max(rgb.r, Math.Max(rgb.g, rgb.b));
            c = (1 - rgb.r - k) / (1 - k);
            m = (1 - rgb.g - k) / (1 - k);
            y = (1 - rgb.b - k) / (1 - k);

        }

        public static Color CMYKtoRGB(float c, float m, float y, float k)
        {
            return new Color((1 - c) * (1 - k), (1 - m) * (1 - k), (1 - y) * (1 - k));
        }

    }

    public static class Utility
    {
        public static PawnKindDef GetRacesPawnKind(Pawn pawn)
        {
            if (pawn == null) return null;
            if (pawn.kindDef?.race == pawn.def) return pawn.kindDef;
            return VariousDefOf.AllKinds.Find(kind => kind.race == pawn.def && kind.defName.Contains("Colonist")) ??
                VariousDefOf.AllKinds.Find(kind => kind.race == pawn.def) ??
                pawn.def.race?.AnyPawnKind ??
                pawn.kindDef;
        }

        public static float GetCumVolume(this Pawn pawn)
        {
            List<Hediff> hediffs = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));
            if (hediffs.NullOrEmpty()) return 0;
            else return pawn.GetCumVolume(hediffs);
        }

        public static float GetCumVolume(this Pawn pawn, List<Hediff> hediffs)
        {
            CompHediffBodyPart part = hediffs?.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("penis")).InRandomOrder().FirstOrDefault()?.TryGetComp<CompHediffBodyPart>();
            if (part == null) part = hediffs?.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("ovipositorf")).InRandomOrder().FirstOrDefault()?.TryGetComp<CompHediffBodyPart>();
            if (part == null) part = hediffs?.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("ovipositorm")).InRandomOrder().FirstOrDefault()?.TryGetComp<CompHediffBodyPart>();
            if (part == null) part = hediffs?.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("tentacle")).InRandomOrder().FirstOrDefault()?.TryGetComp<CompHediffBodyPart>();

            return pawn.GetCumVolume(part);
        }

        public static float GetCumVolume(this Pawn pawn, CompHediffBodyPart part)
        {
            float res;

            try
            {
                res = part.FluidAmmount * part.FluidModifier * pawn.BodySize / pawn.RaceProps.baseBodySize * Rand.Range(0.8f, 1.2f);
            }
            catch (NullReferenceException)
            {
                res = 0.0f;
            }
            if (pawn.Has(Quirk.Messy)) res *= Rand.Range(4.0f, 8.0f);

            return res;
        }

        public static HediffComp_Breast GetBreastComp(this Pawn pawn)
        {
            List<Hediff> hedifflist = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_breastsBPR(pawn))?.FindAll(h => VariousDefOf.AllBreasts.Contains(h.def));
            if (hedifflist.NullOrEmpty()) hedifflist = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_uddersBPR(pawn))?.FindAll(h => VariousDefOf.AllBreasts.Contains(h.def));
            if (hedifflist.NullOrEmpty()) return null;
            HediffComp_Breast result;
            foreach (Hediff h in hedifflist)
            {
                result = h.TryGetComp<HediffComp_Breast>();
                if (result != null) return result;
            }
            return null;
        }

        public static HediffComp_Breast GetBreastComp(this Hediff hediff)
        {
            if (hediff is Hediff_PartBaseNatural)
            {
                return hediff.TryGetComp<HediffComp_Breast>();
            }
            return null;
        }

        public static List<ThingComp> GetMilkComps(this Pawn pawn)
        {
            List<ThingComp> milkcomp = pawn.AllComps.FindAll(x => x is CompMilkable || x.GetType().ToString().ToLower().Contains("milkable"));
            return milkcomp;
        }

        public static bool HasMenstruationComp(this Pawn pawn)
        {
            return pawn.GetMenstruationComps().Any();
        }

        public static bool HasMenstruationComp(this Hediff hediff)
        {
            if ((hediff is Hediff_PartBaseNatural || hediff is Hediff_PartBaseArtifical) && hediff.TryGetComp<HediffComp_Menstruation>() != null)
                return true;
            else return false;
        }

        public static bool IsRJWPregnant(this Pawn pawn)
        {
            return pawn.health.hediffSet.GetHediffs<Hediff_BasePregnancy>().Any();
        }

        public static float GetFarthestPregnancyProgress(this Pawn pawn)
        {
            return pawn.health.hediffSet.GetHediffs<Hediff_BasePregnancy>().MaxByWithFallback(hediff => hediff.GestationProgress)?.GestationProgress ?? 0;
        }

        public static float GetPregnancyProgress(this HediffComp_Menstruation comp)
        {
            if (comp.Pregnancy == null) return 0;
            else return comp.Pregnancy.GestationProgress;
        }

        public static Pawn GetFetus(this HediffComp_Menstruation comp)
        {
            if (comp.Pregnancy == null) return null;
            if (!comp.Pregnancy.babies.NullOrEmpty()) return comp.Pregnancy.babies.First();
            else
            {
                Log.Error("Baby not exist: baby was not created or removed. Remove pregnancy.");
                comp.Pregnancy.Miscarry();
                return null;
            }
        }

        public static void DrawBreastIcon(this Pawn pawn, Rect rect)
        {
            Hediff hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_breastsBPR(pawn)).FirstOrDefault(h => VariousDefOf.AllBreasts.Contains(h.def));
            if (hediff == null)
                hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_uddersBPR(pawn)).FirstOrDefault(h => VariousDefOf.AllBreasts.Contains(h.def));
            Texture2D breast, nipple, areola;
            if (hediff != null)
            {
                HediffComp_Breast comp = hediff.TryGetComp<HediffComp_Breast>();
                string icon;
                if (comp != null) icon = comp.Props?.BreastTex ?? "Breasts/Breast_Breast";
                else
                {
                    breast = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00", false);
                    nipple = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00_Nipple00", false);
                    areola = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00_Areola00", false);

                    GUI.color = pawn.story?.SkinColor ?? Color.white;
                    GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit);
                    GUI.color = Color.white;
                    GUI.DrawTexture(rect, areola, ScaleMode.ScaleToFit);
                    GUI.DrawTexture(rect, nipple, ScaleMode.ScaleToFit);
                    return;
                }

                if (hediff.Severity < 0.20f) icon += "_Breast00";
                else if (hediff.Severity < 0.40f) icon += "_Breast01";
                else if (hediff.Severity < 0.60f) icon += "_Breast02";
                else if (hediff.Severity < 0.80f) icon += "_Breast03";
                else if (hediff.Severity < 1.00f) icon += "_Breast04";
                else icon += "_Breast05";

                string nippleicon, areolaicon;
                float nipplesize, areolasize;
                nipplesize = comp.NippleSize;
                areolasize = comp.AreolaSize;

                nippleicon = icon + "_Nipple0" + GetNippleIndex(nipplesize);
                areolaicon = icon + "_Areola0" + GetAreolaIndex(areolasize);

                breast = ContentFinder<Texture2D>.Get(icon, false);
                areola = ContentFinder<Texture2D>.Get(areolaicon, false);
                nipple = ContentFinder<Texture2D>.Get(nippleicon, false);
                GUI.color = pawn.story.SkinColor;
                GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit);

                GUI.color = comp.NippleColor;

                GUI.DrawTexture(rect, areola, ScaleMode.ScaleToFit);

                GUI.DrawTexture(rect, nipple, ScaleMode.ScaleToFit);

                if (Configurations.Debug) TooltipHandler.TipRegion(rect, comp.DebugInfo());

            }
            else
            {
                breast = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00", false);
                nipple = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00_Nipple00", false);
                areola = ContentFinder<Texture2D>.Get("Breasts/Breast_Breast00_Areola00", false);

                GUI.color = pawn.story.SkinColor;
                GUI.DrawTexture(rect, breast, ScaleMode.ScaleToFit);
                GUI.color = Color.white;
                GUI.DrawTexture(rect, areola, ScaleMode.ScaleToFit);
                GUI.DrawTexture(rect, nipple, ScaleMode.ScaleToFit);
            }

        }

        public static int GetNippleIndex(float nipplesize)
        {
            if (nipplesize < 0.25f) return 0;
            else if (nipplesize < 0.50f) return 1;
            else if (nipplesize < 0.75f) return 2;
            else return 3;
        }

        public static int GetAreolaIndex(float nipplesize)
        {
            if (nipplesize < 0.15f) return 0;
            else if (nipplesize < 0.30f) return 1;
            else if (nipplesize < 0.45f) return 2;
            else if (nipplesize < 0.70f) return 3;
            else return 4;
        }

        public static void DrawMilkBars(this Pawn pawn, Rect rect)
        {
            //List<ThingComp> milkcomp = pawn.AllComps.FindAll(x => x is CompMilkable || x.GetType().ToString().ToLower().Contains("milkable"));
            ThingComp milkcomp = null;
            float res = 0;
            if (VariousDefOf.Hediff_Heavy_Lactating_Permanent != null)
            {
                if (pawn.health.hediffSet.HasHediff(VariousDefOf.Hediff_Heavy_Lactating_Permanent)) milkcomp = pawn.AllComps.FirstOrDefault(x => x.GetType().ToString().ToLower().Contains("hypermilkable"));
                else milkcomp = pawn.AllComps.FirstOrDefault(x => x.GetType().ToString().ToLower().Contains("milkable"));
            }
            else
            {
                milkcomp = pawn.GetComp<CompMilkable>();
            }

            if (milkcomp == null) return;
            if (milkcomp is CompMilkable milkable)
            {
                bool active = (bool)milkcomp.GetPropertyValue("Active");
                if (active)
                {
                    CompMilkable m = milkable;
                    res = Math.Max(m.Fullness, res);
                    Widgets.FillableBar(rect, Math.Min(res, 1.0f), TextureCache.MilkTexture, Texture2D.blackTexture, true);
                    DrawMilkBottle(rect, pawn, VariousDefOf.Job_LactateSelf, m.Fullness);
                }
            }
            else
            {
                bool active = (bool)milkcomp.GetPropertyValue("Active");
                if (active)
                {
                    float fullness = (float)milkcomp.GetMemberValue("fullness");
                    res = Math.Max(fullness, res);
                    Widgets.FillableBar(rect, Math.Min(res, 1.0f), TextureCache.MilkTexture, Texture2D.blackTexture, true);
                    DrawMilkBottle(rect, pawn, VariousDefOf.Job_LactateSelf_MC, fullness);
                }
            }
        }

        public static void DrawMilkBottle(Rect rect, Pawn pawn, JobDef milkjob, float fullness)
        {
            Texture2D texture;
            Rect buttonrect = new Rect(rect.x, rect.y, rect.height, rect.height);
            if (fullness <= 0.0f) return;

            if (fullness < 0.3f) texture = ContentFinder<Texture2D>.Get("Milk/Milkbottle_Small", false);
            else if (fullness < 0.7f) texture = ContentFinder<Texture2D>.Get("Milk/Milkbottle_Medium", false);
            else texture = ContentFinder<Texture2D>.Get("Milk/Milkbottle_Large", false);
            GUIContent icon = new GUIContent(texture);
            GUIStyle style = GUIStyle.none;
            style.normal.background = texture;
            string tooltip = Translations.Button_MilkTooltip;

            TooltipHandler.TipRegion(buttonrect, tooltip);
            if (GUI.Button(buttonrect, icon, style))
            {
                if (fullness < 0.1f
                    || !pawn.IsColonistPlayerControlled
                    || pawn.Downed) SoundDefOf.ClickReject.PlayOneShotOnCamera();
                else
                {
                    SoundDefOf.Click.PlayOneShotOnCamera();
                    pawn.jobs.TryTakeOrderedJob(new Verse.AI.Job(milkjob, pawn));
                }
            }
            Widgets.DrawHighlightIfMouseover(buttonrect);
        }

        public static string GetVaginaLabel(this Pawn pawn)
        {
            Hediff hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn)).Find(h => VariousDefOf.AllVaginas.Contains(h.def));
            return hediff.LabelBase.CapitalizeFirst() + "\n(" + hediff.LabelInBrackets + ")" + "\n" + xxx.CountOfSex.LabelCap.CapitalizeFirst() + ": " + pawn.records.GetAsInt(xxx.CountOfSex);
        }
        public static string GetAnusLabel(this Pawn pawn)
        {
            Hediff hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_anusBPR(pawn)).FirstOrDefault(h => VariousDefOf.AllAnuses.Contains(h.def));
            if (hediff == null) hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_anusBPR(pawn)).FirstOrDefault(h => h.def.defName.ToLower().Contains("anus"));
            if (hediff != null) return hediff.LabelBase.CapitalizeFirst() + "\n(" + hediff.LabelInBrackets + ")";
            else return "";
        }
        public static string GetBreastLabel(this Pawn pawn)
        {
            Hediff hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_breastsBPR(pawn)).FirstOrDefault(h => VariousDefOf.AllBreasts.Contains(h.def));
            if (hediff == null) hediff = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_uddersBPR(pawn)).FirstOrDefault(h => VariousDefOf.AllBreasts.Contains(h.def));
            if (hediff != null) return hediff.LabelBase.CapitalizeFirst() + "\n(" + hediff.LabelInBrackets + ")";
            else return "";
        }

        public static bool ShowFetusImage(this Hediff_BasePregnancy hediff)
        {
            if (Configurations.InfoDetail == Configurations.DetailLevel.All) return true;
            else if (Configurations.InfoDetail == Configurations.DetailLevel.Hide) return false;
            else if (hediff.Visible) return true;
            else return false;
        }

        public static bool ShowFetusInfo()
        {
            if (Configurations.InfoDetail == Configurations.DetailLevel.All || Configurations.InfoDetail == Configurations.DetailLevel.OnReveal) return true;
            else return false;
        }

        public static bool ShowStatus(this Pawn pawn)
        {
            if (pawn.Faction != null && Configurations.ShowFlag != Configurations.PawnFlags.None)
            {
                if (pawn.Faction.IsPlayer)
                {
                    if ((Configurations.ShowFlag & Configurations.PawnFlags.Colonist) != 0) return true;
                }
                else if (pawn.IsPrisonerOfColony)
                {
                    if ((Configurations.ShowFlag & Configurations.PawnFlags.Prisoner) != 0) return true;
                }
                else if (pawn.Faction.PlayerRelationKind == FactionRelationKind.Ally)
                {
                    if ((Configurations.ShowFlag & Configurations.PawnFlags.Ally) != 0) return true;
                }
                else if (pawn.Faction.PlayerRelationKind == FactionRelationKind.Hostile)
                {
                    if ((Configurations.ShowFlag & Configurations.PawnFlags.Hostile) != 0) return true;
                }
                else if ((Configurations.ShowFlag & Configurations.PawnFlags.Neutral) != 0) return true;
                else return false;
            }
            else if ((Configurations.ShowFlag & Configurations.PawnFlags.Neutral) != 0) return true;

            return false;
        }

        public static Pawn GetFather(Pawn pawn, Pawn mother)
        {
            Pawn res = pawn.GetFather();
            if (res != null) return res;
            else res = pawn.relations?.GetFirstDirectRelationPawn(PawnRelationDefOf.Parent, x => x != mother) ?? null;
            return res;
        }

        public static float RandGaussianLike(float min, float max, int iterations = 3)
        {
            float res = 0;
            for (int i = 0; i < iterations; i++)
            {
                res += Rand.Value;
            }
            res /= iterations;

            return res * (max - min) + min;

        }

        public static float LerpMultiple(this float a, float b, float t, int num)
        {
            float tmult = Mathf.Pow(1 - t, num);
            return tmult * a + (1 - tmult) * b;
        }

        public static float VariationRange(this float num, float variant)
        {
            return num * Rand.Range(1.0f - variant, 1.0f + variant);
        }

        public static bool ShouldShowWombGizmo(this Pawn pawn)
        {
            return Configurations.EnableWombIcon && (!pawn.IsAnimal() || Configurations.EnableAnimalCycle);
        }
    }
}


code:
using RimWorld;
using rjw;
using rjw.Modules.Interactions.Rules.PartKindUsageRules.Implementation;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;

namespace RJW_Menstruation
{
    public static class VariousDefOf
    {

        public static readonly ThingDef CumFilth = DefDatabase<ThingDef>.GetNamed("FilthCum");
        public static readonly ThingDef GirlCumFilth = DefDatabase<ThingDef>.GetNamed("FilthGirlCum");
        public static readonly ThingDef Tampon = DefDatabase<ThingDef>.GetNamed("Absorber_Tampon");
        public static readonly ThingDef Tampon_Dirty = DefDatabase<ThingDef>.GetNamed("Absorber_Tampon_Dirty");
        public static readonly ThingDef FilthMixture = DefDatabase<ThingDef>.GetNamed("FilthMixture");
        public static readonly HediffDef RJW_IUD = DefDatabase<HediffDef>.GetNamed("RJW_IUD");
        public static readonly HediffDef Hediff_MenstrualCramp = DefDatabase<HediffDef>.GetNamed("Hediff_MenstrualCramp");
        public static readonly HediffDef Hediff_Estrus = DefDatabase<HediffDef>.GetNamed("Hediff_Estrus");
        public static readonly HediffDef Hediff_Estrus_Concealed = DefDatabase<HediffDef>.GetNamed("Hediff_Estrus_Concealed");
        public static readonly HediffDef Hediff_ASA = DefDatabase<HediffDef>.GetNamed("Hediff_ASA");
        public static readonly StatDef MaxAbsorbable = DefDatabase<StatDef>.GetNamed("MaxAbsorbable");
        public static readonly NeedDef SexNeed = DefDatabase<NeedDef>.GetNamed("Sex");
        public static readonly JobDef VaginaWashing = DefDatabase<JobDef>.GetNamed("VaginaWashing");
        public static readonly JobDef Job_LactateSelf = DefDatabase<JobDef>.GetNamed("LactateSelf");
        public static readonly ThoughtDef LeakingFluids = DefDatabase<ThoughtDef>.GetNamed("LeakingFluids");
        public static readonly ThoughtDef CameInsideF = DefDatabase<ThoughtDef>.GetNamed("CameInsideF");
        public static readonly ThoughtDef CameInsideFLowFert = DefDatabase<ThoughtDef>.GetNamed("CameInsideFLowFert");
        public static readonly ThoughtDef CameInsideFFetish = DefDatabase<ThoughtDef>.GetNamed("CameInsideFFetish");
        public static readonly ThoughtDef CameInsideFFetishSafe = DefDatabase<ThoughtDef>.GetNamed("CameInsideFFetishSafe");
        public static readonly ThoughtDef HaterCameInsideFSafe = DefDatabase<ThoughtDef>.GetNamed("HaterCameInsideFSafe");
        public static readonly ThoughtDef HaterCameInsideF = DefDatabase<ThoughtDef>.GetNamed("HaterCameInsideF");
        public static readonly ThoughtDef HaterCameInsideFEstrus = DefDatabase<ThoughtDef>.GetNamed("HaterCameInsideFEstrus");
        public static readonly ThoughtDef CameInsideM = DefDatabase<ThoughtDef>.GetNamed("CameInsideM");
        public static readonly ThoughtDef HaterCameInsideM = DefDatabase<ThoughtDef>.GetNamed("HaterCameInsideM");
        public static readonly ThoughtDef UnwantedPregnancy = DefDatabase<ThoughtDef>.GetNamed("UnwantedPregnancy");
        public static readonly ThoughtDef UnwantedPregnancyMild = DefDatabase<ThoughtDef>.GetNamed("UnwantedPregnancyMild");
        public static readonly ThoughtDef TookContraceptivePill = DefDatabase<ThoughtDef>.GetNamed("TookContraceptivePill");
        public static readonly ThoughtDef HateTookContraceptivePill = DefDatabase<ThoughtDef>.GetNamed("HateTookContraceptivePill");
        public static readonly CompProperties_Menstruation HumanVaginaCompProperties = (CompProperties_Menstruation)Genital_Helper.average_vagina.comps.FirstOrDefault(x => x is CompProperties_Menstruation);
        public static readonly KeyBindingDef OpenStatusWindowKey = DefDatabase<KeyBindingDef>.GetNamed("OpenStatusWindow");
        public static readonly RecordDef AmountofCreampied = DefDatabase<RecordDef>.GetNamed("AmountofCreampied");
        public static readonly RecordDef AmountofFertilizedEggs = DefDatabase<RecordDef>.GetNamed("AmountofFertilizedEggs");
        public static readonly TaleDef TaleCameInside = DefDatabase<TaleDef>.GetNamed("CameInside");

        private static List<ThingDef> allraces = null;
        private static List<PawnKindDef> allkinds = null;
        private static HashSet<HediffDef> allvaginas = null;
        private static HashSet<HediffDef> allanuses = null;
        private static HashSet<HediffDef> allbreasts = null;

        public static List<ThingDef> AllRaces
        {
            get
            {
                if (allraces != null) return allraces;

                List<ThingDef> allThings = DefDatabase<ThingDef>.AllDefsListForReading;
                allraces = allThings.FindAll(x => x.race != null && x.race.IsFlesh);
                return allraces;
            }
        }
        public static List<PawnKindDef> AllKinds
        {
            get
            {
                if (allkinds != null) return allkinds;

                List<PawnKindDef> allKinds = DefDatabase<PawnKindDef>.AllDefsListForReading;
                allkinds = allKinds.FindAll(x => x.race != null);
                return allkinds;
            }
        }
        public static HashSet<HediffDef> AllVaginas
        {
            get
            {
                if (allvaginas != null) return allvaginas;
                allvaginas = new HashSet<HediffDef>();

                foreach(HediffDef hediffDef in DefDatabase<HediffDef>.AllDefsListForReading)
                {
                    if (hediffDef.comps.NullOrEmpty()) continue;
                    foreach (HediffCompProperties comp in hediffDef.comps)
                    {
                        if (comp.compClass == typeof(HediffComp_Menstruation) || (comp.compClass?.IsSubclassOf(typeof(HediffComp_Menstruation)) ?? false))
                        {
                            allvaginas.Add(hediffDef);
                            break;
                        }
                    }
                }

                return allvaginas;
            }
        }
        public static HashSet<HediffDef> AllAnuses
        {
            get
            {
                if (allanuses != null) return allanuses;
                allanuses = new HashSet<HediffDef>();

                foreach (HediffDef hediffDef in DefDatabase<HediffDef>.AllDefsListForReading)
                {
                    if (hediffDef.comps.NullOrEmpty()) continue;
                    foreach (HediffCompProperties comp in hediffDef.comps)
                    {
                        if (comp.compClass == typeof(HediffComp_Anus) || (comp.compClass?.IsSubclassOf(typeof(HediffComp_Anus)) ?? false))
                        {
                            allanuses.Add(hediffDef);
                            break;
                        }
                    }
                }

                return allanuses;
            }
        }
        public static HashSet<HediffDef> AllBreasts
        {
            get
            {
                if (allbreasts != null) return allbreasts;
                allbreasts = new HashSet<HediffDef>();

                foreach(HediffDef hediffDef in DefDatabase<HediffDef>.AllDefsListForReading)
                {
                    if (hediffDef.comps.NullOrEmpty()) continue;
                    foreach(HediffCompProperties comp in hediffDef.comps)
                    {
                        if (comp.compClass == typeof(HediffComp_Breast) || (comp.compClass?.IsSubclassOf(typeof(HediffComp_Breast)) ?? false))
                        {
                            allbreasts.Add(hediffDef);
                            break;
                        }
                    }
                }

                return allbreasts;
            }
        }

        // Defs from Milkable Colonists
        public static readonly HediffDef Hediff_Lactating_Drug = DefDatabase<HediffDef>.GetNamedSilentFail("Lactating_Drug");
        public static readonly HediffDef Hediff_Lactating_Natural = DefDatabase<HediffDef>.GetNamedSilentFail("Lactating_Natural");
        public static readonly HediffDef Hediff_Lactating_Permanent = DefDatabase<HediffDef>.GetNamedSilentFail("Lactating_Permanent");
        public static readonly HediffDef Hediff_Heavy_Lactating_Permanent = DefDatabase<HediffDef>.GetNamedSilentFail("Heavy_Lactating_Permanent");
        public static readonly JobDef Job_LactateSelf_MC = DefDatabase<JobDef>.GetNamedSilentFail("LactateSelf_MC");
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<packages />


code:
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Verse;

namespace RJW_Menstruation.Sexperience
{
    public class GatheredCumMixture : ThingWithComps
    {
        public Color cumColor = Color.white;
        public List<string> ingredients = new List<string>();

        public override Color DrawColor => cumColor;

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Values.Look(ref cumColor, "cumColor", Color.white, true);
            Scribe_Collections.Look(ref ingredients, "ingredients");
        }

        public override bool TryAbsorbStack(Thing other, bool respectStackLimit)
        {
            float amount = stackCount;
            float count = ThingUtility.TryAbsorbStackNumToTake(this, other, respectStackLimit);
            bool res = base.TryAbsorbStack(other, respectStackLimit);
            if (res && other is GatheredCumMixture mixture)
            {
                GatheredCumMixture othercum = mixture;
                cumColor = Colors.CMYKLerp(cumColor, othercum.cumColor, count / (amount + count));
                if (!othercum.ingredients.NullOrEmpty()) for (int i = 0; i < othercum.ingredients.Count; i++)
                    {
                        if (!ingredients.Contains(othercum.ingredients[i])) ingredients.Add(othercum.ingredients[i]);
                    }
            }
            return res;
        }

        public override string GetInspectString()
        {
            StringBuilder res = new StringBuilder();
            if (!ingredients.NullOrEmpty()) for (int i = 0; i < ingredients.Count; i++)
                {
                    res.Append(ingredients[i]);
                    if (i != ingredients.Count - 1) res.Append(", ");
                }
            return res.ToString();
        }

        public void InitwithCum(CumMixture cum)
        {
            ingredients.AddRange(cum.Getingredients);
            cumColor = cum.Color;
        }

    }
}


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

namespace RJW_Menstruation.Sexperience
{
    public class JobDriver_VaginaWashingWithBucket : JobDriver_CleanSelfWithBucket
    {
        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()
        {
            //this.FailOn(delegate
            //{
            //    return !(Comp.TotalCumPercent > 0.001);
            //});
            yield return Toils_Goto.GotoThing(TargetIndex.B, PathEndMode.ClosestTouch);
            Toil excreting = Toils_General.Wait(excretingTime, TargetIndex.None);//duration of

            excreting.WithProgressBarToilDelay(TargetIndex.A);
            yield return excreting;
            yield return new Toil()
            {
                initAction = delegate ()
                {
                    bool anyExcreted = false;
                    foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
                        if (comp.TotalCumPercent > 0.001)
                        {
                            CumMixture mixture = comp.MixtureOut(RJWSexperience.VariousDefOf.GatheredCum, 0.5f);
                            float amount = mixture.Volume;
                            if (mixture.ispurecum)
                            {
                                Bucket.AddCum(amount);
                            }
                            else
                            {
                                GatheredCumMixture cummixture = (GatheredCumMixture)ThingMaker.MakeThing(VariousDefOf.GatheredCumMixture);
                                cummixture.InitwithCum(mixture);
                                Bucket.AddCum(amount, cummixture);
                            }
                            anyExcreted = true;
                        }
                    if (!anyExcreted) ReadyForNextToil();
                    if (pawn.GetMenstruationComps().Any(c => c.TotalCumPercent > 0.001)) JumpToToil(excreting);
                }
            };

            Toil cleaning = new Toil
            {
                initAction = CleaningInit,
                tickAction = CleaningTick
            };
            cleaning.AddFinishAction(Finish);
            cleaning.defaultCompleteMode = ToilCompleteMode.Never;
            cleaning.WithProgressBar(TargetIndex.A, () => progress / CleaningTime);

            yield return cleaning;

            //yield return excreting;
            yield break;
        }
    }
}


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

namespace RJW_Menstruation.Sexperience
{
    [HarmonyPatch(typeof(Pawn_GetGizmos), nameof(Pawn_GetGizmos.GetMenstruationGizmos))]
    public static class GetGizmos_Patch
    {
        public static void Postfix(HediffComp_Menstruation comp, ref List<Gizmo> __result)
        {
            if (Configurations.EnableGatherCumGizmo)
                __result.Add(CreateGizmo_GatherCum(comp));
        }

        private static Gizmo CreateGizmo_GatherCum(HediffComp_Menstruation comp)
        {
            Texture2D icon = TextureCache.GatherCum_Bucket;
            string label = Translations.Gizmo_GatherCum;
            string description = Translations.Gizmo_GatherCum;
            Gizmo gizmo = new Command_Toggle
            {
                defaultLabel = label,
                defaultDesc = description,
                icon = icon,
                isActive = delegate () { return comp.DoCleanWomb; },
                toggleAction = delegate
                {
                    comp.DoCleanWomb = !comp.DoCleanWomb;
                }
            };

            return gizmo;
        }

    }
}


code:
using HarmonyLib;
using System.Reflection;
using Verse;

namespace RJW_Menstruation.Sexperience
{
    [StaticConstructorOnStartup]
    internal static class First
    {
        static First()
        {
            Harmony har = new Harmony("RJW_Menstruation.Sexperience");
            har.PatchAll(Assembly.GetExecutingAssembly());
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.Sound;

namespace RJW_Menstruation.Sexperience
{
    [HarmonyPatch(typeof(Dialog_WombStatus), "DrawWomb")]
    public static class Menstruation_Patch_Dialog_WombStatus
    {
        public const float ICONSIZE = 42f;

        public static void Postfix(Rect rect, Dialog_WombStatus __instance)
        {
            Rect buttonRect = new Rect(rect.x, rect.yMax - ICONSIZE, ICONSIZE, ICONSIZE).ContractedBy(2f);
            if (__instance.Comp.DoCleanWomb)
            {
                Widgets.DrawTextureFitted(buttonRect, TextureCache.GatherCum_Bucket, 1.0f);
                TooltipHandler.TipRegion(buttonRect, Translations.Dialog_DoCleanWomb_Tooltip);
            }
            else
            {
                Widgets.DrawTextureFitted(buttonRect, TextureCache.GatherCum_Pussy, 1.0f);
                TooltipHandler.TipRegion(buttonRect, Translations.Dialog_DontCleanWomb_Tooltip);
            }

            if (Widgets.ButtonInvisible(buttonRect))
            {
                SoundDefOf.Click.PlayOneShotOnCamera();
                __instance.Comp.DoCleanWomb = !__instance.Comp.DoCleanWomb;
            }
        }
    }

}


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

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

            if (pawn.GetMenstruationComps().Any(comp => comp.TotalCumPercent > 0.001f))
                foreach (LocalTargetInfo t in targets)
                {
                    if (t.Thing is Building building)
                    {
                        if (building is Building_CumBucket)
                        {
                            opts.AddDistinct(MakeMenu(pawn, building));
                            break;
                        }
                    }
                }
        }

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

            return option;
        }
    }
}


code:
using HarmonyLib;
using rjwcum;
using RJWSexperience;
using System.Linq;
using Verse;
using Verse.AI;

namespace RJW_Menstruation.Sexperience
{
    [HarmonyPatch(typeof(WorkGiver_CleanSelf))]
    public static class RJW_Patch_WorkGiver_CleanSelf
    {
        [HarmonyPrefix]
        [HarmonyPatch("HasJobOnThing")]
        public static bool HasJobOnThing(Pawn pawn, ref bool __result)
        {
            if (pawn.GetMenstruationComps().Any(comp => comp.DoCleanWomb && comp.TotalCumPercent > 0.001f) && (pawn.Map?.listerBuildings.ColonistsHaveBuilding(VariousDefOf.CumBucket) ?? false))
            {
                __result = true;
                return false;
            }
            return true;
        }

        [HarmonyPostfix]
        [HarmonyPatch("JobOnThing")]
        public static void JobOnThing(Pawn pawn, ref Job __result)
        {
            if (pawn.GetMenstruationComps().Any(comp => comp.DoCleanWomb && comp.TotalCumPercent > 0.001f))
            {

                Building_CumBucket bucket = pawn.FindClosestBucket();
                if (bucket != null)
                {
                    __result = JobMaker.MakeJob(VariousDefOf.VaginaWashingwithBucket, null, bucket, bucket.Position);
                }
            }
        }
    }
}


code:
using System.Reflection;
using System.Runtime.InteropServices;

// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
// 이러한 특성 값을 변경하세요.
[assembly: AssemblyTitle("SexperienceModule")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SexperienceModule")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
[assembly: ComVisible(false)]

// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
[assembly: Guid("7db664d7-d4b4-4673-9ff1-62ec18305715")]

// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
//
//      주 버전
//      부 버전
//      빌드 번호
//      수정 버전
//
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
// 기본값으로 할 수 있습니다.
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


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>{7DB664D7-D4B4-4673-9FF1-62EC18305715}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>RJW_Menstruation.Sexperience</RootNamespace>
    <AssemblyName>SexperienceModule</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>..\..\..\SexperienceModule\Assemblies\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>..\..\..\SexperienceModule\Assemblies\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Assembly-CSharp">
      <HintPath>..\..\..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RimJobWorldCum">
      <HintPath>..\..\..\..\..\rjw-cum\1.3\Assemblies\RimJobWorldCum.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW">
      <HintPath>..\..\..\..\..\rjw\1.3\Assemblies\RJW.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJWSexperience">
      <HintPath>..\..\..\..\..\RJW-Sexperience\Assemblies\RJWSexperience.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJWSexperienceCum">
      <HintPath>..\..\..\..\..\RJW-Sexperience\Mod Compatibility\RJW Cum\Assemblies\RJWSexperienceCum.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RJW_Menstruation">
      <HintPath>..\..\..\Assemblies\RJW_Menstruation.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <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>
    <Compile Include="GatheredCumMixture.cs" />
    <Compile Include="JobDrivers.cs" />
    <Compile Include="Patch\GetGizmos.cs" />
    <Compile Include="Patch\Harmony.cs" />
    <Compile Include="Patch\Menstruation_Patch.cs" />
    <Compile Include="Patch\Pawn_Patch.cs" />
    <Compile Include="Patch\RJW_Patch.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="VariousDefOf.cs" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Lib.Harmony">
      <Version>2.2.1</Version>
      <ExcludeAssets>runtime</ExcludeAssets>
    </PackageReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


code:
using Verse;

namespace RJW_Menstruation.Sexperience
{
    public static class VariousDefOf
    {

        public static readonly JobDef VaginaWashingwithBucket = DefDatabase<JobDef>.GetNamed("VaginaWashingwithBucket");
        public static readonly ThingDef GatheredCumMixture = DefDatabase<ThingDef>.GetNamed("GatheredCumMixture");
        public static readonly ThingDef CumBucket = DefDatabase<ThingDef>.GetNamed("CumBucket");
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
    </startup>
</configuration>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>OvaryRegenerationPill</defName>
		Ovary regeneration pill
		<description>Recovers the ovaries' health, restoring some quantity of eggs.
Less effective on ovaries with few eggs remaining.
Cannot recover menopause.</description>
		<graphicData>
			<texPath>Things/Item/ORPill</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>2400</WorkToMake>
			<MarketValue>160</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<effectOffset>0.3</effectOffset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>10</MedicineHerbal>
			<Neutroamine>4</Neutroamine>
			<InsectJelly>8</InsectJelly>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>SuperovulationInducingAgent</defName>
		Superovulation inducing agent
		<description>Induce superovulation, causing the next ovulation to produce 1-4 extra eggs.

May hasten the onset of menopause.</description>
		<graphicData>
			<texPath>Things/Item/SIA</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>2400</WorkToMake>
			<MarketValue>140</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>12</MedicineHerbal>
			<Neutroamine>6</Neutroamine>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>PainReliever</defName>
		Pain reliever
		<description>Relieves menstrual pain for 24 hours.

Also effective on other sources of pain.</description>
		<graphicData>
			<texPath>Things/Item/PainReliever</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>1000</WorkToMake>
			<MarketValue>12</MarketValue>
			<Mass>0.01</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>Hediff_PainReliever</hediffDef>
					<severity>1.0</severity>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>Cyclosporine</defName>
		cyclosporine
		<description>An immunosuppressant.
Can cure antisperm antibody, but reduces the body's ability to fight off infection and disease for 24 hours.</description>
		<graphicData>
			<texPath>Things/Item/Cyclosporine</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>1000</WorkToMake>
			<MarketValue>12</MarketValue>
			<Mass>0.01</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>Hediff_Cyclosporine</hediffDef>
					<severity>1.0</severity>

					<hediffDef>Hediff_ASA</hediffDef>
					<severity>-0.20</severity>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

</Defs>