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

namespace RJW_Menstruation
{
    public class CompProperties_Breast : HediffCompProperties
    {
        public string BreastTex = "Breasts/Breast";
        public ColorInt BlacknippleColor = new ColorInt(55,20,0);

        public Color BlackNippleColor
        {
            get
            {
                return BlacknippleColor.ToColor;
            }
        }

        public CompProperties_Breast()
        {
            compClass = typeof(HediffComp_Breast);
        }
    }

    public class HediffComp_Breast : HediffComp
    {
        public const float DEFAULTALPHA = -1;
        public const float DEFAULTAREOLA = -1;
        public const float DEFAULTNIPPLE = -1;
        public const float VARIANT = 0.2f;
        public const int TICKINTERVAL = 3750;
        public const float MAX_BREAST_INCREMENT = 0.10f;

        public CompProperties_Breast Props;

        protected float alphaPermanent = -1;
        protected float alphaCurrent = -1;
        protected float alpha = -1;
        protected float areolaSizePermanent = -1f;
        protected float areolaSizeCurrent = -1f;
        protected float areolaSize = -1f;
        protected float nippleSizePermanent = -1f;
        protected float nippleSizeCurrent = -1f;
        protected float nippleSize = -1f;
        protected float breastSizeIncreased = 0f;
        protected float originalpha = -1f;
        protected float originareola = -1f;
        protected float originnipple = -1f;
        protected Color cachedcolor;
        protected bool loaded = false;
        protected bool pregnant = false;
        public Action action;

        public float MaxAlpha
        {
            get
            {
                return originalpha + Configurations.NippleMaximumTransition;
            }
        }
        public float MaxAreola
        {
            get
            {
                return originareola + Configurations.NippleMaximumTransition;
            }
        }
        public float MaxNipple
        {
            get
            {
                return originnipple + Configurations.NippleMaximumTransition;
            }
        }

        public Color NippleColor
        {
            get
            {
                return cachedcolor;
            }
        }
        public float Alpha
        {
            get
            {
                return alphaCurrent;
            }
        }
        public float NippleSize
        {
            get
            {
                return nippleSizeCurrent;
            }
        }
        public float AreolaSize
        {
            get
            {
                return areolaSizeCurrent;
            }
        }

        public override void CompExposeData()
        {
            base.CompExposeData();
            Scribe_Values.Look(ref alphaPermanent, "alphaPermanent", DEFAULTALPHA, true);
            Scribe_Values.Look(ref alphaCurrent, "alphaCurrent", DEFAULTALPHA, true);
            Scribe_Values.Look(ref alpha, "alpha", DEFAULTALPHA, true);
            Scribe_Values.Look(ref areolaSizePermanent, "areolaSizePermanent", DEFAULTAREOLA, true);
            Scribe_Values.Look(ref areolaSizeCurrent, "areolaSizeCurrent", DEFAULTAREOLA, true);
            Scribe_Values.Look(ref areolaSize, "areolaSize", DEFAULTAREOLA, true);
            Scribe_Values.Look(ref nippleSizePermanent, "nippleSizePermanent", DEFAULTNIPPLE, true);
            Scribe_Values.Look(ref nippleSizeCurrent, "nippleSizeCurrent", DEFAULTNIPPLE, true);
            Scribe_Values.Look(ref nippleSize, "nippleSize", DEFAULTNIPPLE, true);
            Scribe_Values.Look(ref breastSizeIncreased, "breastSizeIncreased", breastSizeIncreased, true);
            Scribe_Values.Look(ref originalpha, "originalpha", originalpha, true);
            Scribe_Values.Look(ref originareola, "originareola", originareola, true);
            Scribe_Values.Look(ref originnipple, "originnipple", originnipple, true);
            Scribe_Values.Look(ref pregnant, "pregnant", pregnant, true);

        }

        public override void CompPostTick(ref float severityAdjustment) { }

        public override void CompPostPostAdd(DamageInfo? dinfo)
        {
            if (!loaded) Initialize();
        }

        public override void CompPostPostRemoved()
        {
            HugsLibController.Instance.TickDelayScheduler.TryUnscheduleCallback(action);
            ModLog.Message(parent.pawn.Label + "breast tick scheduler removed");
            base.CompPostPostRemoved();
        }

        public void Initialize()
        {
            Props = (CompProperties_Breast)props;
            action = Transition;
            if (alphaPermanent < 0f)
            {
                alphaPermanent = (Utility.RandGaussianLike(0.0f, 0.3f) + Rand.Range(0.0f,0.5f))/2;
                originalpha = alphaPermanent;
                alpha = alphaPermanent;
                alphaCurrent = alphaPermanent;
            }
            if (areolaSizePermanent < 0f)
            {
                areolaSizePermanent = Utility.RandGaussianLike(0f, parent.Severity);
                originareola = areolaSizePermanent;
                areolaSize = areolaSizePermanent;
                areolaSizeCurrent = areolaSizePermanent;
            }
            if (nippleSizePermanent < 0f)
            {
                nippleSizePermanent = Utility.RandGaussianLike(0f, parent.Severity);
                originnipple = nippleSizePermanent;
                nippleSize = nippleSizePermanent;
                nippleSizeCurrent = nippleSizePermanent;
            }
            UpdateColor();
            loaded = true;
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(action, TICKINTERVAL, parent.pawn);
        }

        public void Transition()
        {
            alphaCurrent = Mathf.Lerp(alphaCurrent, alpha, Configurations.NippleTransitionRatio);
            areolaSizeCurrent = Mathf.Lerp(areolaSizeCurrent, areolaSize, Configurations.NippleTransitionRatio);
            nippleSizeCurrent = Mathf.Lerp(nippleSizeCurrent, nippleSize, Configurations.NippleTransitionRatio);
            UpdateColor();
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(action, TICKINTERVAL, parent.pawn);
            if (pregnant)
            {
                if (breastSizeIncreased < MAX_BREAST_INCREMENT)
                {
                    breastSizeIncreased += 0.02f;
                    parent.Severity += 0.02f;
                }
            }
            else
            {
                if (breastSizeIncreased > 0)
                {
                    breastSizeIncreased -= 0.02f;
                    parent.Severity -= 0.02f;
                }
            }

        }

        public void ChangeColorFermanant(float alpha)
        {
            alphaPermanent = alpha;
        }

        public void ChangeColor(float alpha)
        {
            this.alpha = alpha;
        }

        public void PregnancyTransition()
        {
            alphaPermanent = Math.Min(MaxAlpha, alphaPermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT));
            areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT));
            nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT));
            alpha = Math.Min(MaxAlpha, alpha + Configurations.NippleTransitionVariance.VariationRange(VARIANT));
            areolaSize = Math.Min(MaxAreola, areolaSize + Configurations.NippleTransitionVariance.VariationRange(VARIANT));
            nippleSize = Math.Min(MaxNipple, nippleSize + Configurations.NippleTransitionVariance.VariationRange(VARIANT));
            pregnant = true;
        }

        public void BirthTransition()
        {
            alpha = alphaPermanent;
            areolaSize = areolaSizePermanent;
            nippleSize = nippleSizePermanent;
            pregnant = false;
        }

        public void AdjustBreastSize(float amount)
        {
            parent.Severity += amount;
            breastSizeIncreased += amount;
        }

        public void AdjustNippleSize(float amount)
        {
            nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + amount);
            nippleSize = Math.Min(MaxNipple, nippleSize + amount);
        }

        public void AdjustAreolaSize(float amount)
        {
            areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + amount);
            areolaSize = Math.Min(MaxAreola, areolaSize + amount);
        }

        public void RestoreBreastSize(float ratio)
        {
            float variance = breastSizeIncreased * Math.Min(ratio, 1.0f);
            breastSizeIncreased -= variance;
            parent.Severity -= variance;
        }

        public void AdjustNippleSizeImmidiately(float amount)
        {
            originnipple = Math.Max(0, originnipple + amount);
            nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + amount);
            nippleSize = Math.Min(MaxNipple, nippleSize + amount);
            nippleSizeCurrent = nippleSize;
        }

        public void AdjustAreolaSizeImmidiately(float amount)
        {
            originareola = Math.Max(0, originareola + amount);
            areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + amount);
            areolaSize = Math.Min(MaxAreola, areolaSize + amount);
            areolaSizeCurrent = areolaSize;
        }

        public void UpdateColor()
        {
            cachedcolor = Colors.CMYKLerp(parent?.pawn?.story?.SkinColor ?? Color.white, Props.BlackNippleColor, Alpha);
        }

        public string DebugInfo()
        {
            return "Alpha: " + alpha +
                "\nNippleSize: " + nippleSize +
                "\nAreolaSize: " + areolaSize +
                "\nAlphaCurrent: " + alphaCurrent +
                "\nNippleSizeCurrent: " + nippleSizeCurrent +
                "\nAreolaSizeCurrent: " + areolaSizeCurrent +
                "\nAlphaOrigin: " + originalpha +
                "\nNippleSizeOrigin: " + originnipple +
                "\nAreolaSizeOrigin: " + originareola +
                "\nAlphaMax: " + MaxAlpha +
                "\nNippleSizeMax: " + MaxNipple +
                "\nAreolaSizeMax: " + MaxAreola +
                "\nPermanentAlpha:" + alphaPermanent +
                "\nPermanentNipple:" + nippleSizePermanent +
                "\nPermanentAreola:" + areolaSizePermanent;
        }

    }

}


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

namespace RJW_Menstruation
{
    public class CompProperties_InducedOvulator : CompProperties_Menstruation
    {
        public CompProperties_InducedOvulator()
        {
            compClass = typeof(HediffComp_InducedOvulator);
        }

    }

    public class HediffComp_InducedOvulator : HediffComp_Menstruation
    {
        protected override void FollicularAction()
        {
            if (!IsBreedingSeason())
            {
                GoNextStage(Stage.Anestrus);
                return;
            }
            if (curStageHrs >= FollicularIntervalHours)
            {
                GoNextStage(Stage.Luteal);
            }
            else
            {
                curStageHrs += Configurations.CycleAcceleration;
                if (!estrusflag && curStageHrs > FollicularIntervalHours - Props.estrusDaysBeforeOvulation * 24)
                {
                    estrusflag = true;
                    SetEstrus(Props.eggLifespanDays + Props.estrusDaysBeforeOvulation);
                }
                StayCurrentStage();
            }
        }

        protected override void AfterCumIn(Pawn cummer)
        {
            base.AfterCumIn(cummer);
            if (curStage == Stage.Follicular) curStage = Stage.Ovulatory;
        }

    }

}

license: unknown


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

namespace RJW_Menstruation
{
    [Flags]
    public enum SeasonalBreed
    {
        Always = 0,
        Spring = 1,
        Summer = 2,
        Fall = 4,
        Winter = 8,
        FirstHalf = Spring | Summer,
        SecondHalf = Fall | Winter
    }

    public class CompProperties_Menstruation : HediffCompProperties
    {
        public float maxCumCapacity; // ml
        public float baseImplantationChanceFactor;
        public float basefertilizationChanceFactor;
        public float deviationFactor;
        public int folicularIntervalDays = 14; //before ovulation including beginning of bleeding
        public int lutealIntervalDays = 14; //after ovulation until bleeding
        public int bleedingIntervalDays = 6; //must be less than folicularIntervalDays
        public int recoveryIntervalDays = 10; //additional infertile days after gave birth
        public int eggLifespanDays = 2; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
        public string wombTex = "Womb/Womb"; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
        public string vagTex = "Genitals/Vagina"; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
        public bool infertile = false;
        public int ovaryPower = 600000000; // default: almost unlimited ovulation
        public bool consealedEstrus = false;
        public SeasonalBreed breedingSeason = SeasonalBreed.Always;
        public int estrusDaysBeforeOvulation = 3;

        public CompProperties_Menstruation()
        {

            compClass = typeof(HediffComp_Menstruation);
        }
    }

    public class CompProperties_Anus : HediffCompProperties
    {
        public string analTex = "Genitals/Anal";

        public CompProperties_Anus()
        {
            compClass = typeof(HediffComp_Anus);
        }
    }

    public class HediffComp_Menstruation : HediffComp
    {
        const float minmakefilthvalue = 1.0f;
        //const int ovarypowerthreshold = 72;

        public static readonly int tickInterval = 2500; // an hour
        public CompProperties_Menstruation Props;
        public Stage curStage = Stage.Follicular;
        public int curStageHrs = 0;
        public Action actionref;
        public bool loaded = false;
        public int ovarypower = -100000;
        public int eggstack = 0;

        public enum Stage
        {
            Follicular,
            Ovulatory,
            Luteal,
            Bleeding,
            Fertilized, //Obsoleted
            Pregnant,
            Recover,
            None,
            Young,
            ClimactericFollicular,
            ClimactericLuteal,
            ClimactericBleeding,
            Anestrus
        }

        protected List<Cum> cums;
        protected List<Egg> eggs;
        protected int follicularIntervalhours = -1;
        protected int lutealIntervalhours = -1;
        protected int bleedingIntervalhours = -1;
        protected int recoveryIntervalhours = -1;
        protected float crampPain = -1;
        protected Need sexNeed = null;
        protected string customwombtex = null;
        protected string customvagtex = null;
        protected bool estrusflag = false;
        protected int opcache = -1;
        protected HediffComp_Breast breastcache = null;
        protected float antisperm = 0.0f;

        public int ovarypowerthreshold
        {
            get
            {
                if (opcache < 0) opcache = (int)(72f * ThingDefOf.Human.race.lifeExpectancy / parent.pawn.def.race.lifeExpectancy);
                return opcache;
            }
        }

        public int FollicularIntervalHours
        {
            get
            {
                return (int)((follicularIntervalhours - bleedingIntervalhours) * CycleFactor);
            }
        }

        public float TotalCum
        {
            get
            {
                float res = 0;
                if (cums.NullOrEmpty()) return 0;
                foreach (Cum cum in cums)
                {
                    res += cum.Volume;
                }
                return res;
            }
        }
        public float TotalFertCum
        {
            get
            {
                float res = 0;
                if (cums.NullOrEmpty()) return 0;
                foreach (Cum cum in cums)
                {
                    if (!cum.notcum) res += cum.FertVolume;
                }
                return res;
            }
        }
        public float TotalCumPercent
        {
            get
            {
                float res = 0;
                if (cums.NullOrEmpty()) return 0;
                foreach (Cum cum in cums)
                {
                    res += cum.Volume;
                }
                return res / Props.maxCumCapacity;
            }
        }
        public float CumCapacity
        {
            get
            {
                float res = Props.maxCumCapacity * parent.pawn.BodySize;
                if (curStage != Stage.Pregnant) res *= 500f;
                return res;
            }
        }
        public float CumInFactor
        {
            get
            {
                float res = 1.0f;
                if (parent.pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) res = 0.01f;
                return res;
            }
        }
        //make follicular interval into half and double egg lifespan
        public float CycleFactor
        {
            get
            {
                if (xxx.has_quirk(parent.pawn, "Breeder")) return 0.5f;

                return 1.0f;
            }
        }
        //effect on implant chance
        public float ImplantFactor
        {
            get
            {
                float factor = 1.0f;
                if (parent.pawn.Has(Quirk.Breeder)) factor = 10.0f;
                //if (xxx.is_animal(parent.pawn)) factor *= RJWPregnancySettings.animal_impregnation_chance / 100f;
                //else factor *= RJWPregnancySettings.humanlike_impregnation_chance / 100f;
                return parent.pawn.health.capacities.GetLevel(xxx.reproduction) * factor;
            }
        }
        public IEnumerable<string> GetCumsInfo
        {
            get
            {
                if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                    {
                        if (!cum.notcum) yield return String.Format(cum.pawn?.Label + ": {0:0.##}ml", cum.Volume);
                        else yield return String.Format(cum.notcumLabel + ": {0:0.##}ml", cum.Volume);
                    }
                else yield return Translations.Info_noCum;
            }
        }
        public Color GetCumMixtureColor
        {
            get
            {
                Color mixedcolor = Color.white;

                if (!cums.NullOrEmpty())
                {
                    float mixedsofar = 0;
                    foreach (Cum cum in cums)
                    {
                        if (cum.Volume > 0)
                        {
                            mixedcolor = Colors.CMYKLerp(mixedcolor, cum.color, cum.Volume / (mixedsofar + cum.Volume));
                            mixedsofar += cum.Volume;
                        }
                    }
                }
                return mixedcolor;
            }
        }
        public string GetCurStageLabel
        {
            get
            {
                switch (curStage)
                {
                    case Stage.Follicular:
                        return Translations.Stage_Follicular;
                    case Stage.Ovulatory:
                        return Translations.Stage_Ovulatory;
                    case Stage.Luteal:
                        return Translations.Stage_Luteal;
                    case Stage.Bleeding:
                        return Translations.Stage_Bleeding;
                    case Stage.Fertilized:
                        return Translations.Stage_Fertilized;
                    case Stage.Pregnant:
                        if (Configurations.InfoDetail == Configurations.DetailLevel.All || (PregnancyHelper.GetPregnancy(parent.pawn)?.Visible ?? false)) return Translations.Stage_Pregnant;
                        else return Translations.Stage_Luteal;
                    case Stage.Recover:
                        return Translations.Stage_Recover;
                    case Stage.None:
                    case Stage.Young:
                        return Translations.Stage_None;
                    case Stage.ClimactericFollicular:
                        return Translations.Stage_Follicular + " - " + Translations.Stage_Climacteric;
                    case Stage.ClimactericLuteal:
                        return Translations.Stage_Luteal + " - " + Translations.Stage_Climacteric;
                    case Stage.ClimactericBleeding:
                        return Translations.Stage_Bleeding + " - " + Translations.Stage_Climacteric;
                    case Stage.Anestrus:
                        return Translations.Stage_Anestrus;
                    default:
                        return "";
                }
            }

        }
        public string wombTex
        {
            get
            {
                if (customwombtex == null) return Props.wombTex;
                else return customwombtex;
            }
            set
            {
                customwombtex = value;
            }
        }
        public string vagTex
        {
            get
            {
                if (customvagtex == null) return Props.vagTex;
                else return customvagtex;
            }
            set
            {
                customvagtex = value;
            }
        }
        public string GetFertilizingInfo
        {
            get
            {
                string res = "";
                if (!eggs.NullOrEmpty())
                {
                    int fertilized = 0;
                    foreach (Egg egg in eggs)
                    {
                        if (egg.fertilized) fertilized++;
                    }
                    if (fertilized != 0) res += fertilized + " " + Translations.Dialog_WombInfo05;
                    if (fertilized != 0 && eggs.Count - fertilized != 0) res += ", ";
                    if (cums.NullOrEmpty() || TotalFertCum == 0)
                    {
                        if (eggs.Count - fertilized != 0) res += eggs.Count - fertilized + " " + Translations.Dialog_WombInfo07;
                    }
                    else
                    {
                        if (eggs.Count - fertilized != 0) res += eggs.Count - fertilized + " " + Translations.Dialog_WombInfo06;
                    }
                }
                return res;
            }
        }
        public bool IsEggFertilizing
        {
            get
            {
                if (!eggs.NullOrEmpty())
                {
                    if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                        {
                            if (cum.FertVolume > 0) return true;
                        }
                    return false;

                }
                else return false;
            }
        }
        /// <summary>
        /// returns fertstage. if not fertilized returns -1
        /// </summary>
        public int IsFertilized
        {
            get
            {
                if (!eggs.NullOrEmpty()) foreach (Egg egg in eggs)
                    {
                        if (egg.fertilized) return egg.fertstage;
                    }
                return -1;
            }
        }
        public bool IsEggExist
        {
            get
            {
                return !eggs.NullOrEmpty();
            }
        }
        public bool IsDangerDay
        {
            get
            {
                if (parent.pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) return false;

                if (curStage == Stage.Follicular || curStage == Stage.ClimactericFollicular)
                {
                    if (curStageHrs > 0.7f * (follicularIntervalhours - bleedingIntervalhours)) return true;
                }
                else if (curStage == Stage.Luteal || curStage == Stage.ClimactericLuteal)
                {
                    if (curStageHrs < Props.eggLifespanDays * 24) return true;
                }
                else if (curStage == Stage.Ovulatory) return true;
                return false;
            }
        }
        public int GetNumofEggs
        {
            get
            {
                if (eggs.NullOrEmpty()) return 0;
                else return eggs.Count;
            }
        }
        public Color BloodColor
        {
            get
            {
                try
                {
                    Color c = parent.pawn.def.race.BloodDef.graphicData.color;
                    return c;
                }
                catch
                {
                    return Colors.blood;
                }

            }
        }
        public HediffComp_Breast Breast
        {
            get
            {
                if (breastcache == null)
                {
                    breastcache = parent.pawn.GetBreastComp();
                }
                return breastcache;
            }
        }

        public override void CompExposeData()
        {
            base.CompExposeData();
            Scribe_Collections.Look(ref cums, saveDestroyedThings: true, label: "cums", lookMode: LookMode.Deep, ctorArgs: new object[0]);
            Scribe_Collections.Look(ref eggs, saveDestroyedThings: true, label: "eggs", lookMode: LookMode.Deep, ctorArgs: new object[0]);
            Scribe_Values.Look(ref curStage, "curStage", curStage, true);
            Scribe_Values.Look(ref curStageHrs, "curStageHrs", curStageHrs, true);
            Scribe_Values.Look(ref follicularIntervalhours, "follicularIntervalhours", follicularIntervalhours, true);
            Scribe_Values.Look(ref lutealIntervalhours, "lutealIntervalhours", lutealIntervalhours, true);
            Scribe_Values.Look(ref bleedingIntervalhours, "bleedingIntervalhours", bleedingIntervalhours, true);
            Scribe_Values.Look(ref recoveryIntervalhours, "recoveryIntervalhours", recoveryIntervalhours, true);
            Scribe_Values.Look(ref crampPain, "crampPain", crampPain, true);
            Scribe_Values.Look(ref ovarypower, "ovarypower", ovarypower, true);
            Scribe_Values.Look(ref eggstack, "eggstack", eggstack, true);
            Scribe_Values.Look(ref estrusflag, "estrusflag", estrusflag, true);

        }

        public override void CompPostPostAdd(DamageInfo? dinfo)
        {
            if (!loaded)
            {
                Initialize();
            }
        }

        public override void CompPostTick(ref float severityAdjustment)
        {
            //initializer moved to SpawnSetup
            //if (!loaded)
            //{
            //    Initialize();
            //}
        }

        public override void CompPostPostRemoved()
        {
            HugsLibController.Instance.TickDelayScheduler.TryUnscheduleCallback(actionref);
            Log.Message(parent.pawn.Label + "tick scheduler removed");
            base.CompPostPostRemoved();
        }

        /// <summary>
        /// Get fluid in womb that not a cum
        /// </summary>
        /// <param name="notcumlabel"></param>
        /// <returns></returns>
        public Cum GetNotCum(string notcumlabel)
        {
            if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                {
                    if (cum.notcum && cum.notcumLabel.Equals(notcumlabel)) return cum;
                }
            return null;
        }

        /// <summary>
        /// Get pawn's cum in womb
        /// </summary>
        /// <param name="pawn"></param>
        /// <returns></returns>
        public Cum GetCum(Pawn pawn)
        {
            if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                {
                    if (!cum.notcum && cum.pawn.Equals(pawn)) return cum;
                }
            return null;
        }

        /// <summary>
        /// Inject pawn's cum into womb
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="injectedvolume"></param>
        /// <param name="fertility"></param>
        /// <param name="filthdef"></param>
        public void CumIn(Pawn pawn, float injectedvolume, float fertility = 1.0f, ThingDef filthdef = null)
        {
            float volume = injectedvolume * CumInFactor;
            float cumd = TotalCumPercent;
            float tmp = TotalCum + volume;
            if (tmp > CumCapacity)
            {
                float cumoutrate = 1 - (CumCapacity / tmp);
                bool merged = false;
                if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                    {
                        if (cum.pawn.Equals(pawn))
                        {
                            cum.MergeWithCum(volume, fertility, filthdef);
                            merged = true;
                        }
                        cum.DismishForce(cumoutrate);
                    }
                if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate), fertility, filthdef));
            }
            else
            {

                bool merged = false;
                if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                    {
                        if (cum.pawn.Equals(pawn))
                        {
                            cum.MergeWithCum(volume, fertility, filthdef);
                            merged = true;
                        }
                    }
                if (!merged) cums.Add(new Cum(pawn, volume, fertility, filthdef));
            }
            cumd = TotalCumPercent - cumd;

            parent.pawn.records.AddTo(VariousDefOf.AmountofCreampied, injectedvolume);
            AfterCumIn(pawn);
            AfterFluidIn(cumd);
        }

        /// <summary>
        /// Inject pawn's fluid into womb
        /// </summary>
        /// <param name="pawn"></param>
        /// <param name="volume"></param>
        /// <param name="notcumlabel"></param>
        /// <param name="decayresist"></param>
        /// <param name="filthdef"></param>
        public void CumIn(Pawn pawn, float volume, string notcumlabel, float decayresist = 0, ThingDef filthdef = null)
        {
            float tmp = TotalCum + volume;
            float cumd = TotalCumPercent;
            if (tmp > CumCapacity)
            {
                float cumoutrate = 1 - (CumCapacity / tmp);
                bool merged = false;
                if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                    {
                        if (cum.notcum && cum.pawn.Equals(pawn) && cum.notcumLabel.Equals(notcumlabel))
                        {
                            cum.MergeWithFluid(volume, decayresist, filthdef);
                            merged = true;
                        }
                        cum.DismishForce(cumoutrate);
                    }
                if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate), notcumlabel, decayresist, filthdef));
            }
            else
            {

                bool merged = false;
                if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
                    {
                        if (cum.notcum && cum.pawn.Equals(pawn) && cum.notcumLabel.Equals(notcumlabel))
                        {
                            cum.MergeWithFluid(volume, decayresist, filthdef);
                            merged = true;
                        }
                    }
                if (!merged) cums.Add(new Cum(pawn, volume, notcumlabel, decayresist, filthdef));
            }
            cumd = TotalCumPercent - cumd;
            AfterNotCumIn();
            AfterFluidIn(cumd);
        }

        protected virtual void AfterCumIn(Pawn cummer)
        {
            ThoughtCumInside(cummer);

        }

        protected virtual void AfterNotCumIn()
        {

        }

        /// <summary>
        /// Action for both Cum and NotCum
        /// </summary>
        /// <param name="fd">Fluid deviation</param>
        protected virtual void AfterFluidIn(float fd)
        {

        }

        protected void BeforeCumOut(out Absorber absorber)
        {

            if (parent.pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) antisperm = 0.70f;
            else antisperm = 0.0f;

            absorber = (Absorber)parent.pawn.apparel?.WornApparel?.Find(x => x is Absorber);
            if (absorber != null)
            {
                absorber.WearEffect();
                if (absorber.dirty && absorber.EffectAfterDirty) absorber.DirtyEffect();
            }
        }

        /// <summary>
        /// For natural leaking
        /// </summary>
        protected virtual void AfterCumOut()
        {
            parent.pawn.needs?.mood?.thoughts?.memories?.TryGainMemory(VariousDefOf.LeakingFluids);
        }

        /// <summary>
        /// For all type of leaking
        /// </summary>
        /// <param name="fd"></param>
        protected virtual void AfterFluidOut(float fd)
        {

        }

        /// <summary>
        /// Excrete cums in womb naturally
        /// </summary>
        public void CumOut()
        {
            float leakfactor = 1.0f;
            float totalleak = 0f;
            float cumd = TotalCumPercent;
            List<string> filthlabels = new List<string>();
            BeforeCumOut(out Absorber absorber);
            if (cums.NullOrEmpty()) return;
            else if (absorber != null && absorber.dirty && !absorber.LeakAfterDirty) leakfactor = 0f;
            List<Cum> removecums = new List<Cum>();
            foreach (Cum cum in cums)
            {
                float vd = cum.DismishNatural(leakfactor, antisperm);
                cum.MakeThinner(Configurations.CycleAcceleration);
                totalleak += AbsorbCum(cum, vd, absorber);
                string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
                filthlabels.Add(tmp.Replace(" x1", ""));
                if (cum.ShouldRemove()) removecums.Add(cum);
            }
            if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
            else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
            foreach (Cum cum in removecums)
            {
                cums.Remove(cum);
            }
            removecums.Clear();
            cumd = TotalCumPercent - cumd;
            if (totalleak >= 1.0f) AfterCumOut();
            AfterFluidOut(cumd);

        }

        /// <summary>
        /// Force excrete cums in womb and get excreted amount of specific cum.
        /// </summary>
        /// <param name="targetcum"></param>
        /// <param name="portion"></param>
        /// <returns></returns>
        public float CumOut(Cum targetcum, float portion = 0.1f)
        {
            if (cums.NullOrEmpty()) return 0;
            float totalleak = 0;
            List<string> filthlabels = new List<string>();
            float outcum = 0;
            float cumd = TotalCumPercent;
            List<Cum> removecums = new List<Cum>();
            foreach (Cum cum in cums)
            {
                float vd = cum.DismishForce(portion);
                if (cum.Equals(targetcum)) outcum = vd;
                //MakeCumFilth(cum, vd - cum.volume);
                string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
                filthlabels.Add(tmp.Replace(" x1", ""));
                totalleak += vd;
                if (cum.ShouldRemove()) removecums.Add(cum);
            }
            if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
            else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
            foreach (Cum cum in removecums)
            {
                cums.Remove(cum);
            }
            removecums.Clear();
            cumd = TotalCumPercent - cumd;
            AfterFluidOut(cumd);
            return outcum;
        }

        /// <summary>
        /// Force excrete cums in womb and get mixture of cum.
        /// </summary>
        /// <param name="mixtureDef"></param>
        /// <param name="portion"></param>
        /// <returns></returns>
        public Cum MixtureOut(ThingDef mixtureDef ,float portion = 0.1f)
        {
            if (cums.NullOrEmpty()) return null;
            Color color = GetCumMixtureColor;
            float totalleak = 0;
            List<string> cumlabels = new List<string>();
            float cumd = TotalCumPercent;
            List<Cum> removecums = new List<Cum>();
            foreach (Cum cum in cums)
            {
                float vd = cum.DismishForce(portion);
                string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
                cumlabels.Add(tmp.Replace(" x1", ""));
                totalleak += vd;
                if (cum.ShouldRemove()) removecums.Add(cum);
            }
            foreach (Cum cum in removecums)
            {
                cums.Remove(cum);
            }
            removecums.Clear();
            return new CumMixture(parent.pawn, totalleak, cumlabels, color, mixtureDef);
        }

        /// <summary>
        /// Fertilize eggs and return the result
        /// </summary>
        /// <returns></returns>
        protected bool FertilizationCheck()
        {
            if (!eggs.NullOrEmpty())
            {
                bool onefertilized = false;
                foreach (Egg egg in eggs)
                {
                    if (!egg.fertilized) egg.fertilizer = Fertilize();
                    if (egg.fertilizer != null)
                    {
                        egg.fertilized = true;
                        egg.lifespanhrs += 240;
                        onefertilized = true;
                    }
                }
                return onefertilized;
            }
            else return false;
        }

        public void Initialize()
        {
            Props = (CompProperties_Menstruation)props;

            if (!Props.infertile)
            {
                if (follicularIntervalhours < 0)
                {
                    follicularIntervalhours = PeriodRandomizer(Props.folicularIntervalDays * 24, Props.deviationFactor);
                    curStage = RandomStage();
                }

                if (lutealIntervalhours < 0) lutealIntervalhours = PeriodRandomizer(Props.lutealIntervalDays * 24, Props.deviationFactor);
                if (bleedingIntervalhours < 0) bleedingIntervalhours = PeriodRandomizer(Props.bleedingIntervalDays * 24, Props.deviationFactor);
                if (recoveryIntervalhours < 0) recoveryIntervalhours = PeriodRandomizer(Props.recoveryIntervalDays * 24, Props.deviationFactor);
                if (crampPain < 0) crampPain = PainRandomizer();
                if (cums == null) cums = new List<Cum>();
                if (eggs == null) eggs = new List<Egg>();

                InitOvary(parent.pawn.ageTracker.AgeBiologicalYears);

                if (parent.pawn.IsPregnant()) curStage = Stage.Pregnant;
                if (parent.pawn.IsAnimal())
                {
                    if (Configurations.EnableAnimalCycle)
                    {
                        HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
                    }
                }
                else
                {
                    if (!parent.pawn.IsPregnant() && parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0) HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(Stage.Young), tickInterval, parent.pawn, false);
                    else HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
                }
            }
            else
            {
                if (cums == null) cums = new List<Cum>();
                curStage = Stage.None;
                HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
            }
            //Log.Message(parent.pawn.Label + " - Initialized menstruation comp");
            loaded = true;
        }

        protected void InitOvary(int ageYear)
        {
            if (!Configurations.EnableMenopause)
            {
                RemoveClimactericEffect();
            }
            else if (ovarypower < -50000)
            {
                if (Props.ovaryPower > 10000000) ovarypower = Props.ovaryPower;
                else
                {
                    float avglittersize;
                    try
                    {
                        avglittersize = Rand.ByCurveAverage(parent.pawn.def.race.litterSizeCurve);
                    }
                    catch (NullReferenceException)
                    {
                        avglittersize = 1;
                    }

                    ovarypower = (int)(((Props.ovaryPower * Utility.RandGaussianLike(0.70f, 1.30f) * parent.pawn.def.race.lifeExpectancy / ThingDefOf.Human.race.lifeExpectancy)
                        - (Math.Max(0, ageYear - RJWSettings.sex_minimum_age * parent.pawn.def.race.lifeExpectancy / ThingDefOf.Human.race.lifeExpectancy))
                        * (60 / (Props.folicularIntervalDays + Props.lutealIntervalDays) * Configurations.CycleAcceleration)) * avglittersize);
                    if (ovarypower < 1)
                    {
                        Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Menopause, parent.pawn);
                        hediff.Severity = 0.2f;
                        parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
                        curStage = Stage.Young;
                    }
                    else if (ovarypower < ovarypowerthreshold)
                    {
                        Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Climacteric, parent.pawn);
                        hediff.Severity = 0.008f * (ovarypowerthreshold - ovarypower);
                        parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
                    }
                }
            }
        }

        public void RecoverOvary(float multiply = 0.2f)
        {
            ovarypower = Math.Max(0, (int)(ovarypower * multiply));
            if (ovarypower >= ovarypowerthreshold)
            {
                RemoveClimactericEffect();
            }
        }

        protected void AfterSimulator()
        {
            if (Configurations.EnableMenopause && ovarypower < ovarypowerthreshold)
            {
                if (sexNeed == null) sexNeed = parent.pawn.needs.TryGetNeed(VariousDefOf.SexNeed);
                else
                {
                    if (sexNeed.CurLevel < 0.5) sexNeed.CurLevel += 0.01f;
                }
            }
        }

        public void SetEstrus(int days)
        {
            HediffDef estrusdef;
            if (Props.consealedEstrus) estrusdef = VariousDefOf.Hediff_Estrus_Consealed;
            else estrusdef = VariousDefOf.Hediff_Estrus;

            HediffWithComps hediff = (HediffWithComps)parent.pawn.health.hediffSet.GetFirstHediffOfDef(estrusdef);
            if (hediff != null)
            {
                hediff.Severity = (float)days / Configurations.CycleAcceleration + 0.2f;
            }
            else
            {
                hediff = (HediffWithComps)HediffMaker.MakeHediff(estrusdef, parent.pawn);
                hediff.Severity = (float)days / Configurations.CycleAcceleration + 0.2f;
                parent.pawn.health.AddHediff(hediff);
            }
        }

        public bool IsBreedingSeason()
        {
            if (Props.breedingSeason == SeasonalBreed.Always) return true;
            switch (GenLocalDate.Season(parent.pawn.Map))
            {
                case Season.Spring:
                    if ((Props.breedingSeason & SeasonalBreed.Spring) != 0) return true;
                    break;
                case Season.Summer:
                case Season.PermanentSummer:
                    if ((Props.breedingSeason & SeasonalBreed.Summer) != 0) return true;
                    break;
                case Season.Fall:
                    if ((Props.breedingSeason & SeasonalBreed.Fall) != 0) return true;
                    break;
                case Season.Winter:
                case Season.PermanentWinter:
                    if ((Props.breedingSeason & SeasonalBreed.Winter) != 0) return true;
                    break;
                default:
                    return false;
            }
            return false;
        }

        protected Pawn Fertilize()
        {
            if (cums.NullOrEmpty()) return null;
            foreach (Cum cum in cums)
            {
                float rand = Rand.Range(0.0f, 1.0f);
                if (cum.pawn != null && !cum.notcum && rand < cum.FertVolume * cum.fertFactor * Configurations.FertilizeChance * Props.basefertilizationChanceFactor)
                {
                    if (!RJWPregnancySettings.bestial_pregnancy_enabled && (xxx.is_animal(parent.pawn) ^ xxx.is_animal(cum.pawn))) continue;
                    parent.pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1);
                    return cum.pawn;
                }
            }
            return null;
        }

        //for now, only one egg can be implanted
        protected bool Implant()
        {
            if (!eggs.NullOrEmpty())
            {
                List<Egg> deadeggs = new List<Egg>();
                bool pregnant = false;
                foreach (Egg egg in eggs)
                {
                    if (!egg.fertilized || egg.fertstage < 168) continue;
                    else if (Rand.Range(0.0f, 1.0f) <= Configurations.ImplantationChance * Props.baseImplantationChanceFactor * ImplantFactor * InterspeciesImplantFactor(egg.fertilizer))
                    {
                        if (!parent.pawn.IsPregnant())
                        {
                            if (!Configurations.UseMultiplePregnancy)
                            {
                                PregnancyHelper.PregnancyDecider(parent.pawn, egg.fertilizer);
                                pregnant = true;
                                break;
                            }
                            else
                            {
                                Hediff_BasePregnancy.Create<Hediff_MultiplePregnancy>(parent.pawn, egg.fertilizer);
                                Hediff hediff = PregnancyHelper.GetPregnancy(parent.pawn);
                                //if (hediff is Hediff_BasePregnancy)
                                //{
                                //    Hediff_BasePregnancy h = (Hediff_BasePregnancy)hediff;
                                //    if (h.babies.Count > 1) h.babies.RemoveRange(1, h.babies.Count - 1);
                                //}
                                pregnant = true;
                                deadeggs.Add(egg);
                            }
                        }
                        else if (Configurations.UseMultiplePregnancy && Configurations.EnableHeteroOvularTwins)
                        {
                            Hediff hediff = PregnancyHelper.GetPregnancy(parent.pawn);
                            if (hediff is Hediff_MultiplePregnancy)
                            {
                                Hediff_MultiplePregnancy h = (Hediff_MultiplePregnancy)hediff;
                                h.AddNewBaby(parent.pawn, egg.fertilizer);
                            }
                            pregnant = true;
                            deadeggs.Add(egg);
                        }
                        else
                        {
                            pregnant = true;
                            break;
                        }
                    }
                    else deadeggs.Add(egg);
                }

                if (pregnant && (!Configurations.UseMultiplePregnancy || !Configurations.EnableHeteroOvularTwins))
                {
                    eggs.Clear();
                    deadeggs.Clear();
                    return true;
                }
                else if (!deadeggs.NullOrEmpty())
                {
                    foreach (Egg egg in deadeggs)
                    {
                        eggs.Remove(egg);
                    }
                    deadeggs.Clear();
                }
                if (pregnant) return true;
            }
            return false;
        }

        protected void BleedOut()
        {
            //FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, ThingDefOf.Filth_Blood,parent.pawn.Label);
            CumIn(parent.pawn, Rand.Range(0.02f * Configurations.BleedingAmount, 0.04f * Configurations.BleedingAmount), Translations.Menstrual_Blood, -5.0f, parent.pawn.def.race?.BloodDef ?? ThingDefOf.Filth_Blood);
            GetNotCum(Translations.Menstrual_Blood).color = BloodColor;
        }

        /// <summary>
        /// Make filth ignoring absorber
        /// </summary>
        /// <param name="cum"></param>
        /// <param name="amount"></param>
        protected void MakeCumFilth(Cum cum, float amount)
        {
            if (amount >= minmakefilthvalue) FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, cum.FilthDef, cum.pawn?.LabelShort ?? "Unknown");
        }

        /// <summary>
        /// Absorb cum and return leaked amount
        /// </summary>
        /// <param name="cum"></param>
        /// <param name="amount"></param>
        /// <param name="absorber"></param>
        /// <returns></returns>
        protected float AbsorbCum(Cum cum, float amount, Absorber absorber)
        {

            if (absorber != null)
            {
                float absorbable = absorber.GetStatValue(VariousDefOf.MaxAbsorbable);
                absorber.SetColor(Colors.CMYKLerp(GetCumMixtureColor, absorber.DrawColor, 1f - amount / absorbable));
                if (!absorber.dirty)
                {
                    absorber.absorbedfluids += amount;
                    if (absorber.absorbedfluids > absorbable)
                    {
                        absorber.def = absorber.DirtyDef;
                        //absorber.fluidColor = GetCumMixtureColor;
                        absorber.dirty = true;
                    }
                }
                else
                {

                    //if (absorber.LeakAfterDirty) FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, cum.FilthDef, cum.pawn.LabelShort);
                    return amount;
                }
            }
            else
            {
                //if (amount >= minmakefilthvalue) FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, cum.FilthDef, cum.pawn.LabelShort);
                return amount;
            }
            return 0;
        }

        protected float MakeCumFilthMixture(float amount, List<string> cumlabels)
        {

            if (amount >= minmakefilthvalue)
            {
                FilthMaker_Colored.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, VariousDefOf.FilthMixture, cumlabels, GetCumMixtureColor, false);
            }
            return amount;
        }

        protected void EggDecay()
        {
            List<Egg> deadeggs = new List<Egg>();
            foreach (Egg egg in eggs)
            {
                egg.lifespanhrs -= Configurations.CycleAcceleration;
                egg.position += Configurations.CycleAcceleration;
                if (egg.lifespanhrs < 0) deadeggs.Add(egg);
                if (egg.fertilized) egg.fertstage += Configurations.CycleAcceleration;
            }
            if (!deadeggs.NullOrEmpty())
            {
                foreach (Egg egg in deadeggs)
                {
                    eggs.Remove(egg);
                }
                deadeggs.Clear();
            }
        }

        protected void AddCrampPain()
        {
            Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_MenstrualCramp, parent.pawn);
            hediff.Severity = crampPain * Rand.Range(0.9f, 1.1f);
            HediffCompProperties_SeverityPerDay Prop = (HediffCompProperties_SeverityPerDay)hediff.TryGetComp<HediffComp_SeverityPerDay>().props;
            Prop.severityPerDay = -hediff.Severity / (bleedingIntervalhours / 24) * Configurations.CycleAcceleration;
            parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
        }

        protected virtual void FollicularAction()
        {
            if (!IsBreedingSeason())
            {
                GoNextStage(Stage.Anestrus);
                return;
            }
            if (curStageHrs >= FollicularIntervalHours)
            {
                GoNextStage(Stage.Ovulatory);
            }
            else
            {
                curStageHrs += Configurations.CycleAcceleration;
                if (!estrusflag && curStageHrs > FollicularIntervalHours - Props.estrusDaysBeforeOvulation * 24)
                {
                    estrusflag = true;
                    SetEstrus(Props.eggLifespanDays + Props.estrusDaysBeforeOvulation);
                }
                StayCurrentStage();
            }
        }

        protected virtual void OvulatoryAction()
        {
            estrusflag = false;
            int i = 0;
            float eggnum;
            try
            {
                eggnum = Rand.ByCurve(parent.pawn.RaceProps.litterSizeCurve) + eggstack;
            }
            catch(NullReferenceException)
            {
                eggnum = 1 + eggstack;
            }

            do
            {
                ovarypower--;
                eggs.Add(new Egg((int)(Props.eggLifespanDays * 24 / CycleFactor)));
                i++;
            } while (i < (int)eggnum);
            eggstack = 0;
            if (Configurations.EnableMenopause && ovarypower < 1)
            {
                eggs.Clear();
                Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Climacteric);
                if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
                hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Menopause, parent.pawn);
                hediff.Severity = 0.2f;
                parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
                ovarypower = 0;
                GoNextStage(Stage.Young);
            }
            else if (Configurations.EnableMenopause && ovarypower < ovarypowerthreshold)
            {
                Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Climacteric, parent.pawn);
                hediff.Severity = 0.008f * i;
                parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
                lutealIntervalhours = PeriodRandomizer(lutealIntervalhours, Props.deviationFactor * 6);
                GoNextStage(Stage.ClimactericLuteal);
            }
            else
            {
                lutealIntervalhours = PeriodRandomizer(lutealIntervalhours, Props.deviationFactor);
                GoNextStage(Stage.Luteal);
            }
        }

        protected virtual void LutealAction()
        {
            if (!eggs.NullOrEmpty())
            {
                FertilizationCheck();
                EggDecay();
                if (Implant())
                {
                    if (Breast != null)
                    {
                        Breast.PregnancyTransition();
                    }
                    GoNextStage(Stage.Pregnant);
                }
                else
                {
                    curStageHrs += Configurations.CycleAcceleration;
                    StayCurrentStage();
                }
            }
            else if (curStageHrs <= lutealIntervalhours)
            {
                curStageHrs += Configurations.CycleAcceleration;
                StayCurrentStage();
            }
            else
            {
                if (Props.bleedingIntervalDays == 0)
                {
                    follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
                    GoNextStage(Stage.Follicular);
                }
                else
                {
                    bleedingIntervalhours = PeriodRandomizer(bleedingIntervalhours, Props.deviationFactor);
                    if (crampPain >= 0.05f)
                    {
                        AddCrampPain();
                    }
                    GoNextStage(Stage.Bleeding);
                }
            }
        }

        protected virtual void BleedingAction()
        {
            if (curStageHrs >= bleedingIntervalhours)
            {
                follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
                Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_MenstrualCramp);
                if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
                GoNextStage(Stage.Follicular);
            }
            else
            {
                if (curStageHrs < bleedingIntervalhours / 4) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut();
                curStageHrs += Configurations.CycleAcceleration;
                StayCurrentStage();
            }
        }

        protected virtual void PregnantAction()
        {
            if (!eggs.NullOrEmpty())
            {
                FertilizationCheck();
                EggDecay();
                Implant();
            }
            if (parent.pawn.IsPregnant()) StayCurrentStageConst(Stage.Pregnant);
            else
            {
                if (Breast != null)
                {
                    Breast.BirthTransition();
                }
                GoNextStage(Stage.Recover);
            }
        }

        protected virtual void YoungAction()
        {
            if (!Configurations.EnableMenopause && ovarypower < 0 && ovarypower > -10000)
            {
                RemoveClimactericEffect();
            }
            if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0)
            {
                StayCurrentStageConst(Stage.Young);
            }
            else GoNextStage(Stage.Follicular);
        }

        protected virtual void AnestrusAction()
        {
            if (IsBreedingSeason())
            {
                GoFollicularOrBleeding();
            }
            else
            {
                StayCurrentStage();
            }
        }

        protected virtual void ThoughtCumInside(Pawn cummer)
        {
            if (xxx.is_human(parent.pawn) && xxx.is_human(cummer))
            {
                if (parent.pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0 || cummer.Has(Quirk.ImpregnationFetish) || cummer.Has(Quirk.Breeder))
                {
                    if (cummer.relations.OpinionOf(parent.pawn) <= -25)
                    {
                        cummer.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideM, parent.pawn);
                    }
                    else
                    {
                        cummer.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideM, parent.pawn);
                    }
                }

                if (IsDangerDay)
                {
                    if (parent.pawn.Has(Quirk.Breeder) || parent.pawn.Has(Quirk.ImpregnationFetish))
                    {
                        parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetish, cummer);
                    }
                    else if (!parent.pawn.relations.DirectRelationExists(PawnRelationDefOf.Spouse, cummer) && !parent.pawn.relations.DirectRelationExists(PawnRelationDefOf.Fiance, cummer))
                    {
                        if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) < 0.50f) parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFLowFert, cummer);
                        else parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideF, cummer);
                    }
                    else if (parent.pawn.relations.OpinionOf(cummer) <= -5)
                    {
                        parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideF, cummer);
                    }
                }
                else
                {
                    if (parent.pawn.Has(Quirk.Breeder) || parent.pawn.Has(Quirk.ImpregnationFetish))
                    {
                        parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetishSafe, cummer);
                    }
                    else if (parent.pawn.relations.OpinionOf(cummer) <= -5)
                    {
                        parent.pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideFSafe, cummer);
                    }
                }
            }
        }

        private Action PeriodSimulator(Stage targetstage)
        {
            Action action = null;
            switch (targetstage)
            {
                case Stage.Follicular:
                    action = FollicularAction;
                    break;
                case Stage.Ovulatory:
                    action = OvulatoryAction;
                    break;
                case Stage.Luteal:
                    action = LutealAction;
                    break;
                case Stage.Bleeding:
                    action = BleedingAction;
                    break;
                case Stage.Fertilized:  //Obsoleted stage. merged in luteal stage
                    action = delegate
                    {
                        ModLog.Message("Obsoleted stage. skipping...");
                        GoNextStage(Stage.Luteal);
                    };
                    break;
                case Stage.Pregnant:
                    action = PregnantAction;
                    break;
                case Stage.Recover:
                    action = delegate
                    {
                        if (curStageHrs >= recoveryIntervalhours)
                        {
                            if (Configurations.EnableMenopause && ovarypower < ovarypowerthreshold)
                            {
                                GoNextStage(Stage.ClimactericFollicular);
                            }
                            else if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) == 0)
                            {
                                GoNextStage(Stage.Young);
                            }
                            else
                            {
                                follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
                                GoNextStage(Stage.Follicular);
                            }
                        }
                        else
                        {
                            curStageHrs += Configurations.CycleAcceleration;
                            StayCurrentStage();
                        }
                    };
                    break;
                case Stage.None:
                    action = delegate
                    {
                        StayCurrentStageConst(Stage.None);
                    };
                    break;
                case Stage.Young:
                    action = YoungAction;
                    break;
                case Stage.ClimactericFollicular:
                    action = delegate
                    {
                        if (!Configurations.EnableMenopause)
                        {
                            RemoveClimactericEffect();
                            StayCurrentStage();
                        }
                        else if (curStageHrs >= (follicularIntervalhours - bleedingIntervalhours) * CycleFactor)
                        {
                            GoNextStage(Stage.Ovulatory);
                        }
                        else if (ovarypower < ovarypowerthreshold / 3 && Rand.Range(0.0f, 1.0f) < 0.2f) //skips ovulatory
                        {
                            follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
                            GoNextStage(Stage.ClimactericFollicular);
                        }
                        else
                        {
                            curStageHrs += Configurations.CycleAcceleration;
                            StayCurrentStage();
                        }
                    };
                    break;
                case Stage.ClimactericLuteal:
                    action = delegate
                    {
                        if (!Configurations.EnableMenopause)
                        {
                            RemoveClimactericEffect();
                            StayCurrentStage();
                        }
                        else if (!eggs.NullOrEmpty())
                        {
                            FertilizationCheck();
                            EggDecay();
                            if (Implant()) GoNextStage(Stage.Pregnant);
                            else
                            {
                                curStageHrs += Configurations.CycleAcceleration;
                                StayCurrentStage();
                            }
                        }
                        else if (curStageHrs <= lutealIntervalhours)
                        {
                            curStageHrs += Configurations.CycleAcceleration;
                            StayCurrentStage();
                        }
                        else
                        {
                            if (Props.bleedingIntervalDays == 0)
                            {
                                follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
                                GoNextStage(Stage.ClimactericFollicular);
                            }
                            else if (ovarypower < ovarypowerthreshold / 4 || (ovarypower < ovarypowerthreshold / 3 && Rand.Range(0.0f, 1.0f) < 0.3f)) //skips bleeding
                            {
                                follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
                                GoNextStage(Stage.ClimactericFollicular);
                            }
                            else
                            {
                                bleedingIntervalhours = PeriodRandomizer(bleedingIntervalhours, Props.deviationFactor);
                                if (crampPain >= 0.05f)
                                {
                                    AddCrampPain();
                                }
                                GoNextStage(Stage.ClimactericBleeding);
                            }
                        }

                    };
                    break;
                case Stage.ClimactericBleeding:
                    action = delegate
                    {
                        if (!Configurations.EnableMenopause)
                        {
                            RemoveClimactericEffect();
                            StayCurrentStage();
                        }
                        else if (curStageHrs >= bleedingIntervalhours)
                        {
                            follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
                            GoNextStage(Stage.ClimactericFollicular);
                        }
                        else
                        {
                            if (curStageHrs < bleedingIntervalhours / 6) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut();
                            curStageHrs += Configurations.CycleAcceleration;
                            StayCurrentStage();
                        }
                    };
                    break;
                case Stage.Anestrus:
                    action = AnestrusAction;
                    break;
                default:
                    curStage = Stage.Follicular;
                    curStageHrs = 0;
                    if (follicularIntervalhours < 0) follicularIntervalhours = PeriodRandomizer(Props.folicularIntervalDays * 24, Props.deviationFactor);
                    HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(Stage.Follicular), tickInterval, parent.pawn, false);
                    break;
            }
            action += delegate
            {
                if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0) curStage = Stage.Young;
                CumOut();
                AfterSimulator();
            };

            actionref = action;
            return actionref;

        }

        protected void GoNextStage(Stage nextstage, float factor = 1.0f)
        {
            curStageHrs = 0;
            curStage = nextstage;
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(nextstage), (int)(tickInterval * factor), parent.pawn, false);
        }

        protected void GoNextStageSetHour(Stage nextstage, int hour, float factor = 1.0f)
        {
            curStageHrs = hour;
            curStage = nextstage;
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(nextstage), (int)(tickInterval * factor), parent.pawn, false);
        }

        //stage can be interrupted in other reasons
        protected void StayCurrentStage(float factor = 1.0f)
        {
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), (int)(tickInterval * factor), parent.pawn, false);
        }

        //stage never changes
        protected void StayCurrentStageConst(Stage curstage, float factor = 1.0f)
        {
            HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curstage), (int)(tickInterval * factor), parent.pawn, false);
        }

        protected void GoFollicularOrBleeding()
        {
            if (Props.bleedingIntervalDays == 0)
            {
                follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
                GoNextStage(Stage.Follicular);
            }
            else
            {
                bleedingIntervalhours = PeriodRandomizer(bleedingIntervalhours, Props.deviationFactor);
                GoNextStage(Stage.Bleeding);
            }
        }

        protected void RemoveClimactericEffect()
        {
            Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Climacteric);
            if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
            hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Menopause);
            if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
            if (curStage == Stage.ClimactericBleeding) curStage = Stage.Bleeding;
            else if (curStage == Stage.ClimactericFollicular) curStage = Stage.Follicular;
            else if (curStage == Stage.ClimactericLuteal) curStage = Stage.Luteal;
        }

        protected int PeriodRandomizer(int intervalhours, float deviation)
        {
            return intervalhours + (int)(intervalhours * Rand.Range(-deviation, deviation));
        }

        protected float InterspeciesImplantFactor(Pawn fertilizer)
        {
            if (fertilizer.def.defName == parent.pawn.def.defName) return 1.0f;
            else
            {
                if (RJWPregnancySettings.complex_interspecies) return SexUtility.BodySimilarity(parent.pawn, fertilizer);
                else return RJWPregnancySettings.interspecies_impregnation_modifier;
            }
        }

        protected float PainRandomizer()
        {
            float rand = Rand.Range(0.0f, 1.0f);
            if (rand < 0.01f) return Rand.Range(0.0f, 0.2f);
            else if (rand < 0.2f) return Rand.Range(0.1f, 0.2f);
            else if (rand < 0.8f) return Rand.Range(0.2f, 0.4f);
            else if (rand < 0.95f) return Rand.Range(0.4f, 0.6f);
            else return Rand.Range(0.6f, 1.0f);
        }

        protected Stage RandomStage()
        {
            int rand = Rand.Range(0, 2);

            switch (rand)
            {
                case 0:
                    curStageHrs = Rand.Range(0, (Props.folicularIntervalDays - Props.bleedingIntervalDays) * 24);
                    return Stage.Follicular;
                case 1:
                    curStageHrs = Rand.Range(0, Props.eggLifespanDays * 24);
                    return Stage.Luteal;
                case 2:
                    curStageHrs = Rand.Range(0, Props.bleedingIntervalDays * 24);
                    return Stage.Bleeding;
                default: return Stage.Follicular;
            }

        }

        public class Egg : IExposable
        {
            public bool fertilized;
            public int lifespanhrs;
            public Pawn fertilizer;
            public int position;
            public int fertstage = 0;

            public Egg()
            {
                fertilized = false;
                lifespanhrs = 96;
                fertilizer = null;
                position = 0;
            }

            public Egg(int lifespanhrs)
            {
                fertilized = false;
                this.lifespanhrs = lifespanhrs;
                fertilizer = null;
                position = 0;
            }

            public void ExposeData()
            {
                Scribe_References.Look(ref fertilizer, "fertilizer", true);
                Scribe_Values.Look(ref fertilized, "fertilized", fertilized, true);
                Scribe_Values.Look(ref lifespanhrs, "lifespanhrs", lifespanhrs, true);
                Scribe_Values.Look(ref position, "position", position, true);
                Scribe_Values.Look(ref fertstage, "fertstage", fertstage, true);
            }
        }

    }

    public class HediffComp_Anus : HediffComp
    {
        public override void CompPostTick(ref float severityAdjustment)
        {
        }
    }

}


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

namespace RJW_Menstruation
{
    public static class MenstruationUtility
    {
        public static HediffComp_Menstruation GetMenstruationComp(this Pawn pawn)
        {
            var hedifflist = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn))?.FindAll((Hediff h) => h.def.defName.ToLower().Contains("vagina"));
            HediffComp_Menstruation result;
            if (hedifflist.NullOrEmpty()) return null;
            else
            {
                foreach (Hediff h in hedifflist)
                {
                    result = h.TryGetComp<HediffComp_Menstruation>();
                    if (result != null) return result;
                }
            }
            return null;
        }

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

        public static float GetFertilityChance(this HediffComp_Menstruation comp)
        {
            return comp.TotalFertCum * Configurations.FertilizeChance;
        }

        public static HediffComp_Menstruation.Stage GetCurStage(this Pawn pawn)
        {
            return GetMenstruationComp(pawn)?.curStage ?? HediffComp_Menstruation.Stage.Bleeding;
        }

        public static Texture2D GetPregnancyIcon(this HediffComp_Menstruation comp, Hediff hediff)
        {
            string icon = "";
            Texture2D result = null;
            int babycount = 1;
            if (hediff is Hediff_MechanoidPregnancy)
            {
                return ContentFinder<Texture2D>.Get(("Womb/Mechanoid_Fluid"), true);
            }
            else if (hediff is Hediff_BasePregnancy)
            {
                Hediff_BasePregnancy h = (Hediff_BasePregnancy)hediff;
                babycount = h.babies.Count;
                string fetustex = h.babies?.FirstOrDefault()?.def.GetModExtension<PawnDNAModExtension>()?.fetusTexPath ?? "Fetus/Fetus_Default";
                if (h.GestationProgress < 0.2f) icon = comp.wombTex + "_Implanted";
                else if (h.GestationProgress < 0.3f)
                {
                    if (h.babies?.First()?.def?.race?.FleshType == FleshTypeDefOf.Insectoid) icon += "Fetus/Insects/Insect_Early00";
                    else icon += "Fetus/Fetus_Early00";
                }
                else if (h.GestationProgress < 0.4f) icon += fetustex + "00";
                else if (h.GestationProgress < 0.5f) icon += fetustex + "01";
                else if (h.GestationProgress < 0.6f) icon += fetustex + "02";
                else if (h.GestationProgress < 0.7f) icon += fetustex + "03";
                else if (h.GestationProgress < 0.8f) icon += fetustex + "04";
                else icon += fetustex + "05";
            }
            else icon = "Fetus/Slime_Abomi02";

            result = TryGetTwinsIcon(icon, babycount);

            if (result == null) result = ContentFinder<Texture2D>.Get((icon), true);
            return result;
        }

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

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

        public static Texture2D GetWombIcon(this HediffComp_Menstruation comp)
        {
            if (comp.Pawn.health.hediffSet.GetHediffs<Hediff_InsectEgg>().FirstOrDefault() != null) return ContentFinder<Texture2D>.Get(("Womb/Womb_Egged"), true);
            string icon = comp.wombTex;
            HediffComp_Menstruation.Stage stage = comp.curStage;
            if (stage == HediffComp_Menstruation.Stage.Bleeding) icon += "_Bleeding";

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

            return wombtex;
        }
        public static Texture2D GetEggIcon(this HediffComp_Menstruation comp)
        {
            if (comp.parent.pawn.IsPregnant())
            {
                if (comp.parent.pawn.GetPregnancyProgress() < 0.2f) return ContentFinder<Texture2D>.Get("Eggs/Egg_Implanted00", true);
                else return ContentFinder<Texture2D>.Get("Womb/Empty", true);
            }
            else if (!comp.IsEggExist) return ContentFinder<Texture2D>.Get("Womb/Empty", true);
            else
            {
                int fertstage = comp.IsFertilized;
                if (fertstage >= 0)
                {
                    if (fertstage < 1) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized00", true);
                    else if (fertstage < 24) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized01", true);
                    else return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilized02", true);
                }
                else if (comp.IsEggFertilizing) return ContentFinder<Texture2D>.Get("Eggs/Egg_Fertilizing01", true);
                else return ContentFinder<Texture2D>.Get("Eggs/Egg", true);
            }
        }

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

    }
}