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 cums; protected List 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 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; } } /// /// returns fertstage. if not fertilized returns -1 /// 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) { Props = (CompProperties_Menstruation)props; InitOvary((int)Utility.RandGaussianLike(18,40)); 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(); } /// /// Get fluid in womb that not a cum /// /// /// public Cum GetNotCum(string notcumlabel) { if (!cums.NullOrEmpty()) foreach (Cum cum in cums) { if (cum.notcum && cum.notcumLabel.Equals(notcumlabel)) return cum; } return null; } /// /// Get pawn's cum in womb /// /// /// public Cum GetCum(Pawn pawn) { if (!cums.NullOrEmpty()) foreach (Cum cum in cums) { if (!cum.notcum && cum.pawn.Equals(pawn)) return cum; } return null; } /// /// Inject pawn's cum into womb /// /// /// /// /// 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); } /// /// Inject pawn's fluid into womb /// /// /// /// /// /// 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() { } /// /// Action for both Cum and NotCum /// /// Fluid deviation 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(); } } /// /// For natural leaking /// protected virtual void AfterCumOut() { parent.pawn.needs?.mood?.thoughts?.memories?.TryGainMemory(VariousDefOf.LeakingFluids); } /// /// For all type of leaking /// /// protected virtual void AfterFluidOut(float fd) { } /// /// Excrete cums in womb naturally /// public void CumOut() { float leakfactor = 1.0f; float totalleak = 0f; float cumd = TotalCumPercent; List filthlabels = new List(); BeforeCumOut(out Absorber absorber); if (cums.NullOrEmpty()) return; else if (absorber != null && absorber.dirty && !absorber.LeakAfterDirty) leakfactor = 0f; List removecums = new List(); 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); } /// /// Force excrete cums in womb and get excreted amount of specific cum. /// /// /// /// public float CumOut(Cum targetcum, float portion = 0.1f) { if (cums.NullOrEmpty()) return 0; float totalleak = 0; List filthlabels = new List(); float outcum = 0; float cumd = TotalCumPercent; List removecums = new List(); 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; } /// /// Force excrete cums in womb and get mixture of cum. /// /// /// /// public Cum MixtureOut(ThingDef mixtureDef ,float portion = 0.1f) { if (cums.NullOrEmpty()) return null; Color color = GetCumMixtureColor; float totalleak = 0; List cumlabels = new List(); float cumd = TotalCumPercent; List removecums = new List(); 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); } /// /// Fertilize eggs and return the result /// /// 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(); if (eggs == null) eggs = new List(); 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(); 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; return cum.pawn; } } return null; } //for now, only one egg can be implanted protected bool Implant() { if (!eggs.NullOrEmpty()) { List deadeggs = new List(); 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(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; } /// /// Make filth ignoring absorber /// /// /// protected void MakeCumFilth(Cum cum, float amount) { if (amount >= minmakefilthvalue) FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, cum.FilthDef, cum.pawn?.LabelShort ?? "Unknown"); } /// /// Absorb cum and return leaked amount /// /// /// /// /// 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 cumlabels) { if (amount >= minmakefilthvalue) { FilthMaker_Colored.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, VariousDefOf.FilthMixture, cumlabels, GetCumMixtureColor, false); } return amount; } protected void EggDecay() { List deadeggs = new List(); 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().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) { } } }