diff --git a/1.4/Assemblies/RJW_Menstruation.dll b/1.4/Assemblies/RJW_Menstruation.dll index 32d579b..d4d7bb3 100644 Binary files a/1.4/Assemblies/RJW_Menstruation.dll and b/1.4/Assemblies/RJW_Menstruation.dll differ diff --git a/1.4/Languages/English/Keyed/RJW_Menstruation.xml b/1.4/Languages/English/Keyed/RJW_Menstruation.xml index 3df2d06..a1c65b3 100644 --- a/1.4/Languages/English/Keyed/RJW_Menstruation.xml +++ b/1.4/Languages/English/Keyed/RJW_Menstruation.xml @@ -120,6 +120,8 @@ Hookup minimum opinion in estrus Estimated sperm lifespan Estimated egg lifespan + Ovulation {0} + Chance of each egg being released during ovulation. Implantation chance of fertilized eggs. Chance of fertilization this hour: {0}% Use basic RJW pregnancy Use menstruation multiple pregnancy diff --git a/1.4/MilkModule/Assemblies/MilkModule.dll b/1.4/MilkModule/Assemblies/MilkModule.dll index 5c953b2..1881466 100644 Binary files a/1.4/MilkModule/Assemblies/MilkModule.dll and b/1.4/MilkModule/Assemblies/MilkModule.dll differ diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/DebugActions.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/DebugActions.cs index 3a8fdb5..fe98f5a 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/DebugActions.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/DebugActions.cs @@ -10,7 +10,7 @@ namespace RJW_Menstruation private static void SetFollicular(Pawn p) { foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) - comp.curStage = HediffComp_Menstruation.Stage.Follicular; + comp.GoNextStage(HediffComp_Menstruation.Stage.Follicular); Messages.Message($"{p} is now follicular", p, MessageTypeDefOf.NeutralEvent, false); } @@ -18,7 +18,7 @@ namespace RJW_Menstruation private static void SetOvulatory(Pawn p) { foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) - comp.curStage = HediffComp_Menstruation.Stage.Ovulatory; + comp.GoNextStage(HediffComp_Menstruation.Stage.Ovulatory); Messages.Message($"{p} is now ovulatory", p, MessageTypeDefOf.NeutralEvent, false); } @@ -26,7 +26,7 @@ namespace RJW_Menstruation private static void SetLuteal(Pawn p) { foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) - comp.curStage = HediffComp_Menstruation.Stage.Luteal; + comp.GoNextStage(HediffComp_Menstruation.Stage.Luteal); Messages.Message($"{p} is now luteal", p, MessageTypeDefOf.NeutralEvent, false); } @@ -34,7 +34,7 @@ namespace RJW_Menstruation private static void SetBleeding(Pawn p) { foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) - comp.curStage = HediffComp_Menstruation.Stage.Bleeding; + comp.GoNextStage(HediffComp_Menstruation.Stage.Bleeding); Messages.Message($"{p} is now bleeding", p, MessageTypeDefOf.NeutralEvent, false); } /* diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/DrugOutcomeDoers.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/DrugOutcomeDoers.cs index 6e8c375..2b51556 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/DrugOutcomeDoers.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/DrugOutcomeDoers.cs @@ -16,7 +16,7 @@ namespace RJW_Menstruation ) { comp.SetEstrus(); - comp.curStage = HediffComp_Menstruation.Stage.Ovulatory; + comp.GoNextStage(HediffComp_Menstruation.Stage.Ovulatory); comp.ovarypower--; } } @@ -32,7 +32,7 @@ namespace RJW_Menstruation ) { comp.SetEstrus(); - comp.curStage = HediffComp_Menstruation.Stage.Ovulatory; + comp.GoNextStage(HediffComp_Menstruation.Stage.Ovulatory); comp.eggstack += ingested.stackCount - 1; } } @@ -89,7 +89,7 @@ namespace RJW_Menstruation else m.moodPowerFactor = 0.3f; } - if (pawn.Has(Quirk.Breeder)) pawn.needs.mood.thoughts.memories.TryGainMemoryFast(VariousDefOf.HateTookContraceptivePill); + if (pawn.HasQuirk(QuirkUtility.Quirks.Breeder)) pawn.needs.mood.thoughts.memories.TryGainMemoryFast(VariousDefOf.HateTookContraceptivePill); else pawn.needs.mood.thoughts.memories.TryGainMemoryFast(VariousDefOf.TookContraceptivePill); } } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs index ef575f0..e86422d 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Menstruation.cs @@ -203,7 +203,7 @@ namespace RJW_Menstruation get { if (!Configurations.EnableMenopause || Props.infertile) return Mathf.Max(1.0f, ovarypower / OvaryPowerThreshold); - else return ovarypower / OvaryPowerThreshold; + else return (float)ovarypower / OvaryPowerThreshold; } } @@ -280,16 +280,65 @@ namespace RJW_Menstruation return 1.0f; } } - //effect on implant chance - public float ImplantFactor + + // I hate doing this, but it's the least bad option + public bool calculatingOvulationChance = false; + + public float OvulationChance + { + get + { + float ovulationChance = 1.0f; + if (EggHealth <= 0.0f) return 0.0f; + if (EggHealth < 1.0f / 3.0f) ovulationChance = 0.8f; + if (ModsConfig.BiotechActive && xxx.is_human(Pawn)) + { + if (Pawn.SterileGenes()) return 0.0f; + // Replicate how rjw.PawnCapacityWorker_Fertility.CalculateCapacityLevel does it, but without the age factor + if (!Pawn.RaceHasFertility()) return 0.0f; + if (AndroidsCompatibility.IsAndroid(Pawn) && parent.def != Genital_Helper.archotech_vagina) return 0.0f; + foreach (var part in StatDefOf.Fertility.parts) + { + if(part is StatPart_FertilityByGenderAge fertilityByAge) + { + float factor = 1.0f; + fertilityByAge.TransformValue(StatRequest.For(Pawn), ref factor); + if (factor <= 0.0f) return 0.0f; // Too young or too old + } + else part.TransformValue(StatRequest.For(Pawn), ref ovulationChance); + } + if (Pawn.HasQuirk(QuirkUtility.Quirks.Breeder)) ovulationChance *= 10.0f; + try + { + calculatingOvulationChance = true; + ovulationChance *= PawnCapacityUtility.CalculateCapacityLevel(Pawn.health.hediffSet, xxx.reproduction); + } + finally { calculatingOvulationChance = false; } + } + return ovulationChance; + } + } + public float ImplantChance { get { float factor = 1.0f; - if (Pawn.Has(Quirk.Breeder)) factor = 10.0f; - return Pawn.health.capacities.GetLevel(xxx.reproduction) * Props.baseImplantationChanceFactor * FertilityModifier * factor; + if (ModsConfig.BiotechActive && xxx.is_human(Pawn)) + { + // Implant factor will be based solely on pawn age, plus any rollover from ovulation chance + StatPart_FertilityByGenderAge fertilityStatPart = StatDefOf.Fertility.GetStatPart(); + fertilityStatPart?.TransformValue(StatRequest.For(Pawn), ref factor); + float ovulationOverflow = OvulationChance; + if (ovulationOverflow > 1.0f) factor *= ovulationOverflow; + return Props.baseImplantationChanceFactor * FertilityModifier * factor; + } + else + { + return Pawn.health.capacities.GetLevel(xxx.reproduction) * Props.baseImplantationChanceFactor * FertilityModifier * factor; + } } } + public IEnumerable GetCumsInfo { get @@ -345,13 +394,13 @@ namespace RJW_Menstruation switch (CurrentVisibleStage) { case Stage.Follicular: - return Translations.Stage_Follicular + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); + return Translations.Stage_Follicular + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : ""); case Stage.Ovulatory: - return Translations.Stage_Ovulatory + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); + return Translations.Stage_Ovulatory + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : ""); case Stage.Luteal: - return Translations.Stage_Luteal + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); + return Translations.Stage_Luteal + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : ""); case Stage.Bleeding: - return Translations.Stage_Bleeding + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); + return Translations.Stage_Bleeding + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : ""); case Stage.Pregnant: return Translations.Stage_Pregnant; case Stage.Recover: @@ -375,13 +424,13 @@ namespace RJW_Menstruation switch (CurrentVisibleStage) { case Stage.Follicular: - return Translations.Stage_Follicular_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : ""); + return Translations.Stage_Follicular_Desc + (EggHealth < 1f ? " " + Translations.Stage_Climacteric_Desc : ""); case Stage.Ovulatory: - return Translations.Stage_Ovulatory_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : ""); + return Translations.Stage_Ovulatory_Desc + (EggHealth < 1f ? " " + Translations.Stage_Climacteric_Desc : ""); case Stage.Luteal: - return Translations.Stage_Luteal_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : ""); + return Translations.Stage_Luteal_Desc + (EggHealth < 1f ? " " + Translations.Stage_Climacteric_Desc : ""); case Stage.Bleeding: - return Translations.Stage_Bleeding_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : ""); + return Translations.Stage_Bleeding_Desc + (EggHealth < 1f ? " " + Translations.Stage_Climacteric_Desc : ""); case Stage.Pregnant: return Translations.Stage_Pregnant_Desc; case Stage.Recover: @@ -651,6 +700,16 @@ namespace RJW_Menstruation return false; } + public bool ShouldBeInfertile() + { + if (pregnancy != null) return false; + if (ImplantChance <= 0.0f) return true; + // Give the last egg ovulated a chance to implant + if (curStage == Stage.Luteal || curStage == Stage.Bleeding || curStage == Stage.Recover) return false; + if (EggHealth <= 0.0f) return true; + return false; + } + public override void CompPostTick(ref float severityAdjustment) { base.CompPostTick(ref severityAdjustment); @@ -678,7 +737,7 @@ namespace RJW_Menstruation BeforeSimulator(); - if (pregnancy == null && (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())) GoNextStage(Stage.Infertile); + if (ShouldBeInfertile()) GoNextStage(Stage.Infertile); switch (curStage) { case Stage.Follicular: @@ -813,8 +872,8 @@ namespace RJW_Menstruation if (!precum && fertility > 0 && IsDangerDay && pawn.relations.GetPregnancyApproachForPartner(Pawn) == PregnancyApproach.AvoidPregnancy) { float successChance = pulloutSuccessRate; - if (pawn.Has(Quirk.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; - if (Pawn.Has(Quirk.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; + if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; + if (Pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; if (Rand.Chance(successChance)) return; } if (Pawn.HasIUD()) fertility /= 100f; @@ -1111,9 +1170,12 @@ namespace RJW_Menstruation if (cycleSpeed < 0f) cycleSpeed = Utility.RandGaussianLike(0.8f, 1.2f); if (cycleVariability < 0f) cycleVariability = MenstruationUtility.RandomVariabilityPercent(); + + InitOvary(); + if (currentIntervalHours < 0) { - if (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || Pawn.SterileGenes()) curStage = Stage.Infertile; + if (ShouldBeInfertile()) curStage = Stage.Infertile; else if (!IsBreedingSeason()) curStage = Stage.Anestrus; else curStage = RandomStage(); if (curStage == Stage.Follicular) @@ -1128,8 +1190,6 @@ namespace RJW_Menstruation if (cums == null) cums = new List(); if (eggs == null) eggs = new List(); - - InitOvary(); TakeLoosePregnancy(); //Log.Message(Pawn.Label + " - Initialized menstruation comp"); @@ -1283,7 +1343,7 @@ namespace RJW_Menstruation float totalFertPower = eligibleCum.Sum(cum => cum.FertVolume); - if (Rand.Range(0.0f, 1.0f) > 1.0f - Mathf.Pow(1.0f - Configurations.FertilizeChance, totalFertPower * Props.basefertilizationChanceFactor)) + if (Rand.Chance(Mathf.Pow(1.0f - Configurations.FertilizeChance, totalFertPower * Props.basefertilizationChanceFactor))) return null; Pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1); @@ -1324,7 +1384,7 @@ namespace RJW_Menstruation deadeggs.Add(egg); continue; } - else if (Rand.Range(0.0f, 1.0f) <= Configurations.ImplantationChance * ImplantFactor * InterspeciesImplantFactor(egg.fertilizer)) + else if (Rand.Chance(Configurations.ImplantationChance * ImplantChance * InterspeciesImplantFactor(egg.fertilizer))) { if (Configurations.Debug) Log.Message($"Implanting fertilized egg of {Pawn} into {parent}, father {egg.fertilizer}"); if (pregnancy != null) @@ -1545,7 +1605,6 @@ namespace RJW_Menstruation { estrusflag = false; float eggnum; - int ovulated; try { eggnum = Math.Max(Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1f); @@ -1560,23 +1619,24 @@ namespace RJW_Menstruation eggnum = 1f; } eggnum *= ovulationFactor; - ovulated = (int)eggnum + eggstack; + int toOvulate = (int)eggnum + eggstack; + + float ovulationChance = OvulationChance; + int ovulated = 0; + for (int i = 0; i < toOvulate; i++) + if (i < eggstack || Rand.Chance(ovulationChance)) // eggstack comes from drugs and are guaranteed ovulated + { + eggs.Add(new Egg((int)(EggLifespanHours / CycleFactor))); + ++ovulated; + } + if(ovulated < ovarypower) ovulated = Math.Max(ovarypower, eggstack); - for (int i = 0; i < ovulated; i++) - eggs.Add(new Egg((int)(EggLifespanHours / CycleFactor))); ovarypower -= ovulated; - eggstack = 0; - if (EggHealth <= 0) - { - eggs.Clear(); - ovarypower = 0; - GoNextStage(Stage.Infertile); - } - else - { - GoNextStage(Stage.Luteal); - } + if (Configurations.Debug && ovulated != toOvulate) + Log.Message($"{Pawn} ovulated {ovulated}/{toOvulate} eggs ({ovulationChance.ToStringPercent()} chance)"); + + GoNextStage(Stage.Luteal); } protected virtual void LutealAction() @@ -1584,7 +1644,7 @@ namespace RJW_Menstruation if (curStageHrs >= currentIntervalHours) { eggs.Clear(); - if (EggHealth < 1f / 4f || (EggHealth < 1f / 3f && Rand.Range(0.0f, 1.0f) < 0.3f)) //skips bleeding + if (EggHealth < 1f / 4f || (EggHealth < 1f / 3f && Rand.Chance(0.3f))) //skips bleeding { GoNextStage(Stage.Follicular); } @@ -1663,7 +1723,7 @@ namespace RJW_Menstruation { if (curStageHrs >= currentIntervalHours) { - if (Pawn.health.capacities.GetLevel(xxx.reproduction) == 0 || EggHealth <= 0 || Pawn.SterileGenes()) + if (ShouldBeInfertile()) { GoNextStage(Stage.Infertile); } @@ -1685,7 +1745,7 @@ namespace RJW_Menstruation protected virtual void InfertileAction() { - if (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes()) + if (ShouldBeInfertile()) { StayCurrentStageConst(Stage.Infertile); } @@ -1712,9 +1772,9 @@ namespace RJW_Menstruation { if (!xxx.is_human(Pawn) || !xxx.is_human(cummer)) return; - if ((cummer.Has(Quirk.Teratophile) != (Pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0)) || - cummer.Has(Quirk.ImpregnationFetish) || - cummer.Has(Quirk.Breeder)) + if ((cummer.HasQuirk(QuirkUtility.Quirks.Teratophile) != (Pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0)) || + cummer.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || + cummer.HasQuirk(QuirkUtility.Quirks.Breeder)) { if (cummer.relations.OpinionOf(Pawn) <= -25) { @@ -1728,7 +1788,7 @@ namespace RJW_Menstruation if (IsDangerDay) { - if (Pawn.Has(Quirk.Breeder) || Pawn.Has(Quirk.ImpregnationFetish)) + if (Pawn.HasQuirk(QuirkUtility.Quirks.Breeder) || Pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) { Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetish, cummer); } @@ -1751,7 +1811,7 @@ namespace RJW_Menstruation } else { - if (Pawn.Has(Quirk.Breeder) || Pawn.Has(Quirk.ImpregnationFetish)) + if (Pawn.HasQuirk(QuirkUtility.Quirks.Breeder) || Pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) { Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetishSafe, cummer); } @@ -1772,7 +1832,7 @@ namespace RJW_Menstruation TaleRecorder.RecordTale(VariousDefOf.TaleCameInside, new object[] { cummer, Pawn }); } - protected void GoNextStage(Stage nextstage, bool calculateHours = true) + public void GoNextStage(Stage nextstage, bool calculateHours = true) { curStageHrs = 0; if (calculateHours) currentIntervalHours = PeriodRandomizer(nextstage); @@ -1781,12 +1841,7 @@ namespace RJW_Menstruation protected virtual void GoOvulatoryStage() { - if (EggHealth < 1.0f / 3.0f && Rand.Range(0.0f, 1.0f) < 0.2f) // Skip ovulation if deep into climacteric - { - estrusflag = false; - GoNextStage(Stage.Luteal); - } - else GoNextStage(Stage.Ovulatory); + GoNextStage(Stage.Ovulatory); } //stage can be interrupted in other reasons diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs index 979077a..8d7ab50 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PregeneratedBabies.cs @@ -214,6 +214,10 @@ namespace RJW_Menstruation PregnancyUtility.ApplyBirthOutcome(thisOutcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments); // No more babies if mom dies halfway through. Unrealistic maybe, but saves a lot of headache in ApplyBirthOutcome if (mother.health.Dead) break; + if (xxx.is_human(baby)) + mother.records.Increment(xxx.CountOfBirthHuman); + else if (xxx.is_animal(baby)) + mother.records.Increment(xxx.CountOfBirthAnimal); thisOutcome = ((RitualOutcomeEffectWorker_ChildBirth)precept_Ritual.outcomeEffect).GetOutcome(birthQuality, null); } while (comp.HasBaby); @@ -263,6 +267,10 @@ namespace RJW_Menstruation PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments); if (mother.health.Dead) break; + if (xxx.is_human(baby)) + mother.records.Increment(xxx.CountOfBirthHuman); + else if (xxx.is_animal(baby)) + mother.records.Increment(xxx.CountOfBirthAnimal); } while (comp.HasBaby); // The ritual version doesn't use the return value, either diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs index a7e2918..d67fbb4 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/MenstruationUtility.cs @@ -1,4 +1,5 @@ using RimWorld; +using RimWorld.Planet; using rjw; using System; using System.Collections.Generic; @@ -218,6 +219,8 @@ namespace RJW_Menstruation } public static Texture2D GetEggIcon(this HediffComp_Menstruation comp, bool includeOvary) { + const float ovaryChanceToShow_01 = 0.4f; + const float ovaryChanceToShow_02 = 1.0f; switch (comp.CurrentVisibleStage) { case HediffComp_Menstruation.Stage.Follicular: @@ -228,15 +231,20 @@ namespace RJW_Menstruation job.Sexprops != null && !job.Sexprops.usedCondom && (job.Sexprops.sexType == xxx.rjwSextype.Vaginal || job.Sexprops.sexType == xxx.rjwSextype.DoublePenetration)) - return ContentFinder.Get("Ovaries/Ovary_01", true); + return ContentFinder.Get((comp.OvulationChance >= ovaryChanceToShow_01) ? "Ovaries/Ovary_01" : "Ovaries_Ovary_00", true); else break; } if (comp.curStageHrs > comp.CurStageIntervalHours - 30) // Approximate time for ovulation to occur - return ContentFinder.Get("Ovaries/Ovary_01", true); + return ContentFinder.Get((comp.OvulationChance >= ovaryChanceToShow_01) ? "Ovaries/Ovary_01" : "Ovaries_Ovary_00", true); else break; case HediffComp_Menstruation.Stage.Ovulatory: if (!includeOvary) break; - return ContentFinder.Get("Ovaries/Ovary_02", true); + if (comp.OvulationChance >= ovaryChanceToShow_02) + return ContentFinder.Get("Ovaries/Ovary_02", true); + else if (comp.OvulationChance >= ovaryChanceToShow_01) + return ContentFinder.Get("Ovaries/Ovary_01", true); + else + return ContentFinder.Get("Ovaries/Ovary_00", true); case HediffComp_Menstruation.Stage.Luteal: if (!comp.IsEggExist) break; int fertstage = comp.IsFertilized; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs index cab288a..b47d614 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Hediff_MultiplePregnancy.cs @@ -23,11 +23,11 @@ namespace RJW_Menstruation { if (is_discovered || !xxx.is_human(pawn) || - pawn.Has(Quirk.Breeder) || + pawn.HasQuirk(QuirkUtility.Quirks.Breeder) || (pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) || x.def.Equals(PawnRelationDefOf.Fiance))) != null) return; - if (pawn.Has(Quirk.ImpregnationFetish) || pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Lover)) != null) + if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Lover)) != null) { pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancyMild); } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs index 2ffe8b3..df6584b 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Biotech_Patch.cs @@ -26,33 +26,28 @@ namespace RJW_Menstruation public static void Postfix(Hediff_Pregnant __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); - if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor starting, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLabor); - if (Configurations.Debug) Log.Message($"New pregnancy Hediff is {comp.Pregnancy}"); } } [HarmonyPatch(typeof(Hediff_Labor), nameof(Hediff_Labor.PreRemoved))] public class Labor_PreRemoved_Patch { - public static void PostFix(Hediff_Labor __instance) + public static void Postfix(Hediff_Labor __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); - if (Configurations.Debug) Log.Message($"{comp.Pawn}'s initial labor ending, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLaborPushing); - if (Configurations.Debug) Log.Message($"New pregnancy Hediff is {comp.Pregnancy}"); } } [HarmonyPatch(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PreRemoved))] public class LaborPushing_PreRemoved_Patch { - public static void PostFix(Hediff_LaborPushing __instance) + public static void Postfix(Hediff_LaborPushing __instance) { HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); - if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor pushing ending, menstruation comp is {comp}"); if (comp == null) return; comp.Pregnancy = null; } @@ -62,7 +57,7 @@ namespace RJW_Menstruation [HarmonyPatch(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.GestationProgress), MethodType.Getter)] public class Hediff_Pregnant_GestationProgess_Patch { - public static void PostFix(Hediff_Pregnant __instance, ref float __result) + public static void Postfix(Hediff_Pregnant __instance, ref float __result) { if (__result < 1f) return; Pawn pawn = __instance.pawn; @@ -74,7 +69,7 @@ namespace RJW_Menstruation [HarmonyPatch(typeof(Recipe_ExtractOvum), nameof(Recipe_ExtractOvum.AvailableReport))] public class ExtractOvum_AvailableReport_Patch { - public static void PostFix(Thing thing, ref AcceptanceReport __result) + public static void Postfix(Thing thing, ref AcceptanceReport __result) { if (!__result.Accepted) return; Pawn pawn = (Pawn)thing; @@ -97,7 +92,7 @@ namespace RJW_Menstruation [HarmonyPatch(typeof(Recipe_ExtractOvum), "OnSurgerySuccess")] public class ExtractOvum_OnSurgerySuccess_Patch { - public static void PostFix(Pawn pawn) + public static void Postfix(Pawn pawn) { List comps = pawn.GetMenstruationComps().ToList(); if (!comps.Any()) return; @@ -110,7 +105,7 @@ namespace RJW_Menstruation [HarmonyPatch(typeof(Recipe_ImplantEmbryo), nameof(Recipe_ImplantEmbryo.ApplyOnPawn))] public class ImplantEmbryo_ApplyOnPawn_Patch { - public static void PostFix(Pawn pawn) + public static void Postfix(Pawn pawn) { foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps()) comp.TakeLoosePregnancy(); @@ -120,7 +115,7 @@ namespace RJW_Menstruation [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))] public class ApplyBirthOutcome_Breast_Patch { - public static void PostFix(Thing birtherThing) + public static void Postfix(Thing birtherThing) { if (birtherThing is Pawn pawn && !pawn.health.Dead) pawn.GetBreastComp()?.GaveBirth(); diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/RJW_Patch.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/RJW_Patch.cs index cc1ff7c..57a6b9d 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/RJW_Patch.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/RJW_Patch.cs @@ -31,7 +31,7 @@ namespace RJW_Menstruation List pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn)); HediffComp_Menstruation comp; - if (pawn.Has(Quirk.ImpregnationFetish) || partner.Has(Quirk.ImpregnationFetish) || partner.IsInEstrus()) + if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.IsInEstrus()) comp = partner.GetFertileMenstruationComp(); else comp = partner.GetRandomMenstruationComp(); if (comp == null) return true; @@ -102,7 +102,7 @@ namespace RJW_Menstruation { if (partner.IsAnimal() && !Configurations.EnableAnimalCycle) return true; HediffComp_Menstruation comp; - if (pawn.Has(Quirk.ImpregnationFetish) || partner.Has(Quirk.ImpregnationFetish) || partner.IsInEstrus()) + if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || partner.IsInEstrus()) comp = partner.GetFertileMenstruationComp(); else comp = partner.GetRandomMenstruationComp(); if (comp == null) @@ -129,11 +129,11 @@ namespace RJW_Menstruation else if (pawn.GetMenstruationComps().Any()) return false; else return pawn.IsPregnant(); } - private static readonly MethodInfo IsPregnant = AccessTools.Method(typeof(PawnExtensions), nameof(PawnExtensions.IsPregnant), new System.Type[] {typeof(Pawn), typeof(bool)}); + private static readonly MethodInfo IsPregnant = AccessTools.Method(typeof(PawnExtensions), nameof(PawnExtensions.IsPregnant), new System.Type[] { typeof(Pawn), typeof(bool) }); public static IEnumerable Transpiler(IEnumerable instructions) { if (IsPregnant == null || IsPregnant.ReturnType != typeof(bool)) throw new System.InvalidOperationException("IsPregnant not found"); - foreach(CodeInstruction instruction in instructions) + foreach (CodeInstruction instruction in instructions) { if (instruction.Calls(IsPregnant)) yield return CodeInstruction.Call(typeof(CanImpregnate_Patch), nameof(PregnancyBlocksImpregnation)); @@ -179,7 +179,7 @@ namespace RJW_Menstruation { // Awkward, but it'll have to do Pawn pawn = props.pawn; - if (__result == 0 || !pawn.Has(Quirk.ImpregnationFetish) || !props.hasPartner()) return; + if (__result == 0 || !pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) || !props.hasPartner()) return; // Check if the existing code would have added the count Pawn partner = props.partner; @@ -371,4 +371,27 @@ namespace RJW_Menstruation } } + + [HarmonyPatch(typeof(PawnCapacityWorker_Fertility), nameof(PawnCapacityWorker_Fertility.CalculateCapacityLevel))] + public static class PawnCapacityWorker_Fertility_Patch + { + private static float GetFertilityStatOrOne(Thing thing, StatDef stat, bool applyPostProcess, int cacheStaleAfterTicks) + { + Pawn pawn = (Pawn)thing; + if (pawn.GetMenstruationComps().Any(comp => comp.calculatingOvulationChance)) + return 1.0f; + else return thing.GetStatValue(stat, applyPostProcess, cacheStaleAfterTicks); + } + private static readonly MethodInfo GetStatValue = AccessTools.Method(typeof(StatExtension), "GetStatValue", new System.Type[] { typeof(Thing), typeof(StatDef), typeof(bool), typeof(int) }); + public static IEnumerable Transpiler(IEnumerable instructions) + { + if (GetStatValue == null || GetStatValue.ReturnType != typeof(float)) throw new System.InvalidOperationException("GetStatValue not found"); + foreach (CodeInstruction instruction in instructions) + { + if (instruction.Calls(GetStatValue)) + yield return CodeInstruction.Call(typeof(PawnCapacityWorker_Fertility_Patch), nameof(GetFertilityStatOrOne)); + else yield return instruction; + } + } + } } diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/QuirkUtility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/QuirkUtility.cs new file mode 100644 index 0000000..c8c2352 --- /dev/null +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/QuirkUtility.cs @@ -0,0 +1,33 @@ +using rjw; +using Verse; + +namespace RJW_Menstruation +{ + public static class QuirkUtility + { + // All quirks used in Menstruation + public enum Quirks + { + Breeder, + ImpregnationFetish, + Messy, + Teratophile, + } + public static bool HasQuirk(this Pawn pawn, Quirks quirk) + { + switch (quirk) + { + case Quirks.Breeder: + return pawn.Has(Quirk.Breeder); + case Quirks.ImpregnationFetish: + return pawn.Has(Quirk.ImpregnationFetish); + case Quirks.Messy: + return pawn.Has(Quirk.Messy); + case Quirks.Teratophile: + return pawn.Has(Quirk.Teratophile); + default: + return false; + } + } + } +} diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj b/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj index 0266ab8..f2f1834 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj @@ -77,6 +77,7 @@ + diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs index 7b0862d..1a67ebf 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Translations.cs @@ -134,6 +134,8 @@ namespace RJW_Menstruation public static readonly string EstimatedCumLifespan = "EstimatedCumLifespan".Translate(); public static readonly string EstimatedEggLifespan = "EstimatedEggLifespan".Translate(); + public static string OvulationChanceLabel(string value) => "OvulationChanceLabel".Translate(value); + public static readonly string OvulationChanceDesc = "OvulationChanceDesc".Translate(); public static string FertilityDesc(string value) => "FertilityDesc".Translate(value); public static readonly string Gizmo_GatherCum = "Gizmo_GatherCum".Translate(); diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs index 4d428a9..9b726b2 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/UI/Dialog_WombStatus.cs @@ -228,7 +228,7 @@ namespace RJW_Menstruation string fainfo = PregnancyCommon.GetFatherInfo(babiescomp?.babies, babiescomp.Pawn, true) + " "; // Keep all parents known, for now if (feinfo == "Null") feinfo = "1 " + p.Mother.def.label + " " + Translations.Dialog_WombInfo02; - if (fainfo == "Null") + if (fainfo == "Null ") { string father = p.Father?.LabelShort ?? Translations.Dialog_FatherUnknown; fainfo = Translations.Dialog_WombInfo03 + ": " + father + " "; @@ -450,11 +450,18 @@ namespace RJW_Menstruation statvalue = pawn.records.GetValue(xxx.CountOfBirthEgg); FillableBarLabeled(lineRect, " " + xxx.CountOfBirthEgg.LabelCap.CapitalizeFirst() + " " + statvalue, statvalue / 100, TextureCache.RecoverTexture, Texture2D.blackTexture, xxx.CountOfBirthEgg.description); - lineRect.y += height * 4; + lineRect.y += height * 3; - statvalue = Configurations.ImplantationChance * comp.ImplantFactor; + if (ModsConfig.BiotechActive && xxx.is_human(pawn)) + { + statvalue = comp.OvulationChance; + FillableBarLabeled(lineRect, " " + Translations.OvulationChanceLabel(statvalue.ToStringPercent()), statvalue, TextureCache.LutealTexture, Texture2D.blackTexture, Translations.OvulationChanceDesc); + } + lineRect.y += height; + + statvalue = Configurations.ImplantationChance * comp.ImplantChance; float fertchance = comp.GetFertilityChance(); - FillableBarLabeled(lineRect, " " + xxx.reproduction.LabelCap.CapitalizeFirst() + " " + statvalue.ToStringPercent(), statvalue, TextureCache.LutealTexture, Texture2D.blackTexture, Translations.FertilityDesc(String.Format("{0:0.##}", fertchance * 100))); + FillableBarLabeled(lineRect, " " + xxx.reproduction.LabelCap.CapitalizeFirst() + " " + statvalue.ToStringPercent(), statvalue, TextureCache.LutealTexture, Texture2D.blackTexture, Translations.FertilityDesc(string.Format("{0:0.##}", fertchance * 100))); Rect overayRect = new Rect(lineRect.x, lineRect.y, lineRect.width * Math.Min(1.0f, fertchance), lineRect.height); GUI.DrawTexture(overayRect, TextureCache.FertChanceTex); lineRect.y += height; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs index af02fef..13aae6e 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Utility.cs @@ -86,7 +86,7 @@ namespace RJW_Menstruation { res = 0.0f; } - if (pawn.Has(Quirk.Messy)) res *= Rand.Range(4.0f, 8.0f); + if (pawn.HasQuirk(QuirkUtility.Quirks.Messy)) res *= Rand.Range(4.0f, 8.0f); return res; } @@ -463,6 +463,10 @@ namespace RJW_Menstruation { return Color.white; } + catch (InvalidOperationException) // And sometimes it can try to pull the value of a Nullable without checking, too + { + return Color.white; + } } } } diff --git a/About/Manifest.xml b/About/Manifest.xml index 5477ddb..24b2e46 100644 --- a/About/Manifest.xml +++ b/About/Manifest.xml @@ -1,7 +1,7 @@ RJW Menstruation - 1.0.8.7 + 1.0.8.8 diff --git a/changelogs.txt b/changelogs.txt index 46e6221..4647b8e 100644 --- a/changelogs.txt +++ b/changelogs.txt @@ -1,3 +1,14 @@ +Version 1.0.8.8 + - Fix pawns skipping straight to menopause instead of going through climacteric stages. + - Fix father appearing as "Null" in womb dialog for some Biotech pregnancies. + - Fix Biotech multiple pregnancy births not being tracked in the mother's statistics. + - Other bug fixes + - Rework ovulation mechanics. A pawn's implantation chance is now dependent only on their age and climacteric effects. + - All other fertility-altering effects instead change the odds of ovulation occuring, rolled per-egg. This chance appears in the womb dialog. + - If the chance of ovulation goes above 100%, the implantation chance is increased. + - Drugs that increase the number of eggs ovulated are still guaranteed to work. + - If Biotech is disabled or not installed, the old fertility system will apply instead. + Version 1.0.8.7 - Fix missing texture when using Milkable Colonists. - Fix estrus and egg lifespan lasting far longer than intended.