Compare commits

..

28 commits

Author SHA1 Message Date
MrDarkside
46a6f6fe16 Merge branch 'MrDarkside-main-patch-33001' into 'main'
Update 1.4/Languages/Russian/Keyed/RJW_Menstruation.xml

See merge request lutepickle/rjw_menstruation!4
2023-03-25 19:11:33 +00:00
lutepickle
e6d2446376 Clarify changelog 2023-03-20 16:19:06 -07:00
lutepickle
8de3864a8e Merge branch 'dev' 2023-03-20 16:17:34 -07:00
lutepickle
2ac4e1f8e5 Fix wrong comparison in OvulatoryAction 2023-03-20 16:17:16 -07:00
lutepickle
cb8a001cdf Change removecums to a HashSet 2023-03-19 22:52:39 -07:00
lutepickle
f7e3d6c1c4 Switch deadeggs to a HashSet 2023-03-19 22:48:04 -07:00
lutepickle
310c03a193 Remove the early exit from the implant loop for non-RJW multiple pregnancy. This lets Biotech fraternal twins implant in the same hour. 2023-03-19 22:39:55 -07:00
lutepickle
9ec87537f8 Merge branch 'dev' 2023-03-19 12:02:34 -07:00
lutepickle
e5aa310528 Move quirk testing into a dedicated function in anticipation of the RJW quirk submod 2023-03-17 20:07:00 -07:00
lutepickle
130ad4df29 Update changelog 2023-03-17 09:35:23 -07:00
lutepickle
5924c73b2f Track birth counts with the pregenerated babies 2023-03-17 09:34:45 -07:00
lutepickle
f7348ccee8 SafeSkinColor needs to catch InvalidOperationException, too, it seems 2023-03-17 07:01:55 -07:00
lutepickle
93855c5ee7 Rework infertile decision, let the last egg ovulated potentially implant. Guarantee full drug ovulation even if eggs aren't available. 2023-03-16 12:24:47 -07:00
lutepickle
b81a07c172 Mention climacteric in the changelog for implant chance 2023-03-12 19:11:18 -07:00
lutepickle
9aab479fe6 Correct PostFix to Postfix 2023-03-12 17:27:04 -07:00
lutepickle
699b3aa6d2 Fix bug introduced in ovulation rework which made induced ovulators not ovulate after follicular 2023-03-12 15:54:14 -07:00
lutepickle
de2da85558 Fix Null father in womb dialog for Biotech pregnancies 2023-03-11 14:07:55 -08:00
lutepickle
c05d8bad79 Only decrement ovary power by eggs actually ovulated, debug log under-ovulations 2023-03-11 10:21:25 -08:00
lutepickle
65b69eaf76 Shift breeder from implant chance to ovulation chane. It would overflow back down anyways. 2023-03-11 09:17:17 -08:00
lutepickle
2798d923b1 Don't show the ovulation chance on non-humans 2023-03-10 09:04:00 -08:00
lutepickle
3646f1135b Climacteric is 20% chance to skip, not 20% chance to ovulate 2023-03-06 17:52:11 -08:00
lutepickle
1aaaf50b64 Display ovaries at a lower state when ovulation chance is lower 2023-03-06 17:35:11 -08:00
lutepickle
2bb0871919 Properly set the conditions to not go/stay in the infertile stage for InfertileAction and RecoverAction 2023-03-06 17:20:49 -08:00
lutepickle
4504d02245 Switch some comp stages to GoNextStage 2023-03-06 17:05:01 -08:00
lutepickle
98cfa23b88 Replace some usage of Rand.Range with Rand.Chance 2023-03-06 16:58:23 -08:00
lutepickle
c8d32f883f New ovulation chance system 2023-03-06 16:53:22 -08:00
lutepickle
7825418da9 Properly add space before climacteric label 2023-03-06 14:40:29 -08:00
lutepickle
325b60b51c Make EggHealth properly return a float 2023-03-06 13:51:11 -08:00
18 changed files with 248 additions and 115 deletions

Binary file not shown.

View file

@ -120,6 +120,8 @@
<Option_EstrusRelationship_Label>Hookup minimum opinion in estrus</Option_EstrusRelationship_Label> <Option_EstrusRelationship_Label>Hookup minimum opinion in estrus</Option_EstrusRelationship_Label>
<EstimatedCumLifespan>Estimated sperm lifespan</EstimatedCumLifespan> <EstimatedCumLifespan>Estimated sperm lifespan</EstimatedCumLifespan>
<EstimatedEggLifespan>Estimated egg lifespan</EstimatedEggLifespan> <EstimatedEggLifespan>Estimated egg lifespan</EstimatedEggLifespan>
<OvulationChanceLabel>Ovulation {0}</OvulationChanceLabel>
<OvulationChanceDesc>Chance of each egg being released during ovulation.</OvulationChanceDesc>
<FertilityDesc>Implantation chance of fertilized eggs.&#10;Chance of fertilization this hour: {0}%</FertilityDesc> <FertilityDesc>Implantation chance of fertilized eggs.&#10;Chance of fertilization this hour: {0}%</FertilityDesc>
<Option_PregnancyFromBaseRJW_Label>Use basic RJW pregnancy</Option_PregnancyFromBaseRJW_Label> <Option_PregnancyFromBaseRJW_Label>Use basic RJW pregnancy</Option_PregnancyFromBaseRJW_Label>
<Option_PregnancyFromMultiplePregnancy_Label>Use menstruation multiple pregnancy</Option_PregnancyFromMultiplePregnancy_Label> <Option_PregnancyFromMultiplePregnancy_Label>Use menstruation multiple pregnancy</Option_PregnancyFromMultiplePregnancy_Label>

View file

@ -10,7 +10,7 @@ namespace RJW_Menstruation
private static void SetFollicular(Pawn p) private static void SetFollicular(Pawn p)
{ {
foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) 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); Messages.Message($"{p} is now follicular", p, MessageTypeDefOf.NeutralEvent, false);
} }
@ -18,7 +18,7 @@ namespace RJW_Menstruation
private static void SetOvulatory(Pawn p) private static void SetOvulatory(Pawn p)
{ {
foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) 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); Messages.Message($"{p} is now ovulatory", p, MessageTypeDefOf.NeutralEvent, false);
} }
@ -26,7 +26,7 @@ namespace RJW_Menstruation
private static void SetLuteal(Pawn p) private static void SetLuteal(Pawn p)
{ {
foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) 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); Messages.Message($"{p} is now luteal", p, MessageTypeDefOf.NeutralEvent, false);
} }
@ -34,7 +34,7 @@ namespace RJW_Menstruation
private static void SetBleeding(Pawn p) private static void SetBleeding(Pawn p)
{ {
foreach (HediffComp_Menstruation comp in p.GetMenstruationComps()) 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); Messages.Message($"{p} is now bleeding", p, MessageTypeDefOf.NeutralEvent, false);
} }
/* /*

View file

@ -16,7 +16,7 @@ namespace RJW_Menstruation
) )
{ {
comp.SetEstrus(); comp.SetEstrus();
comp.curStage = HediffComp_Menstruation.Stage.Ovulatory; comp.GoNextStage(HediffComp_Menstruation.Stage.Ovulatory);
comp.ovarypower--; comp.ovarypower--;
} }
} }
@ -32,7 +32,7 @@ namespace RJW_Menstruation
) )
{ {
comp.SetEstrus(); comp.SetEstrus();
comp.curStage = HediffComp_Menstruation.Stage.Ovulatory; comp.GoNextStage(HediffComp_Menstruation.Stage.Ovulatory);
comp.eggstack += ingested.stackCount - 1; comp.eggstack += ingested.stackCount - 1;
} }
} }
@ -89,7 +89,7 @@ namespace RJW_Menstruation
else m.moodPowerFactor = 0.3f; 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); else pawn.needs.mood.thoughts.memories.TryGainMemoryFast(VariousDefOf.TookContraceptivePill);
} }
} }

View file

@ -203,7 +203,7 @@ namespace RJW_Menstruation
get get
{ {
if (!Configurations.EnableMenopause || Props.infertile) return Mathf.Max(1.0f, ovarypower / OvaryPowerThreshold); 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; 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 get
{ {
float factor = 1.0f; float factor = 1.0f;
if (Pawn.Has(Quirk.Breeder)) factor = 10.0f; 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<StatPart_FertilityByGenderAge>();
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; return Pawn.health.capacities.GetLevel(xxx.reproduction) * Props.baseImplantationChanceFactor * FertilityModifier * factor;
} }
} }
}
public IEnumerable<string> GetCumsInfo public IEnumerable<string> GetCumsInfo
{ {
get get
@ -345,13 +394,13 @@ namespace RJW_Menstruation
switch (CurrentVisibleStage) switch (CurrentVisibleStage)
{ {
case Stage.Follicular: case Stage.Follicular:
return Translations.Stage_Follicular + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); return Translations.Stage_Follicular + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : "");
case Stage.Ovulatory: case Stage.Ovulatory:
return Translations.Stage_Ovulatory + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); return Translations.Stage_Ovulatory + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : "");
case Stage.Luteal: case Stage.Luteal:
return Translations.Stage_Luteal + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); return Translations.Stage_Luteal + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : "");
case Stage.Bleeding: case Stage.Bleeding:
return Translations.Stage_Bleeding + (EggHealth < 1f ? Translations.Stage_Climacteric : ""); return Translations.Stage_Bleeding + (EggHealth < 1f ? " " + Translations.Stage_Climacteric : "");
case Stage.Pregnant: case Stage.Pregnant:
return Translations.Stage_Pregnant; return Translations.Stage_Pregnant;
case Stage.Recover: case Stage.Recover:
@ -375,13 +424,13 @@ namespace RJW_Menstruation
switch (CurrentVisibleStage) switch (CurrentVisibleStage)
{ {
case Stage.Follicular: 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: 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: 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: 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: case Stage.Pregnant:
return Translations.Stage_Pregnant_Desc; return Translations.Stage_Pregnant_Desc;
case Stage.Recover: case Stage.Recover:
@ -651,6 +700,16 @@ namespace RJW_Menstruation
return false; 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) public override void CompPostTick(ref float severityAdjustment)
{ {
base.CompPostTick(ref severityAdjustment); base.CompPostTick(ref severityAdjustment);
@ -678,7 +737,7 @@ namespace RJW_Menstruation
BeforeSimulator(); 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) switch (curStage)
{ {
case Stage.Follicular: case Stage.Follicular:
@ -813,8 +872,8 @@ namespace RJW_Menstruation
if (!precum && fertility > 0 && IsDangerDay && pawn.relations.GetPregnancyApproachForPartner(Pawn) == PregnancyApproach.AvoidPregnancy) if (!precum && fertility > 0 && IsDangerDay && pawn.relations.GetPregnancyApproachForPartner(Pawn) == PregnancyApproach.AvoidPregnancy)
{ {
float successChance = pulloutSuccessRate; float successChance = pulloutSuccessRate;
if (pawn.Has(Quirk.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; if (pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier;
if (Pawn.Has(Quirk.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier; if (Pawn.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish)) successChance *= fetishPulloutSuccessModifier;
if (Rand.Chance(successChance)) return; if (Rand.Chance(successChance)) return;
} }
if (Pawn.HasIUD()) fertility /= 100f; if (Pawn.HasIUD()) fertility /= 100f;
@ -978,7 +1037,7 @@ namespace RJW_Menstruation
if (TotalCum > Props.maxCumCapacity * Pawn.BodySize) leakfactor = Math.Min(1 + (TotalCum - Props.maxCumCapacity * Pawn.BodySize) / 10, 2f); if (TotalCum > Props.maxCumCapacity * Pawn.BodySize) leakfactor = Math.Min(1 + (TotalCum - Props.maxCumCapacity * Pawn.BodySize) / 10, 2f);
if (absorber != null && absorber.dirty && !absorber.LeakAfterDirty) leakfactor = 0f; if (absorber != null && absorber.dirty && !absorber.LeakAfterDirty) leakfactor = 0f;
if (Pawn.CurJobDef == xxx.knotted) leakfactor = 0f; if (Pawn.CurJobDef == xxx.knotted) leakfactor = 0f;
List<Cum> removecums = new List<Cum>(); HashSet<Cum> removecums = new HashSet<Cum>();
foreach (Cum cum in cums) foreach (Cum cum in cums)
{ {
cum.CumEffects(Pawn); cum.CumEffects(Pawn);
@ -991,10 +1050,7 @@ namespace RJW_Menstruation
} }
if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels); if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak); else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
foreach (Cum cum in removecums) cums.RemoveAll(cum => removecums.Contains(cum));
{
cums.Remove(cum);
}
cumd = TotalCumPercent - cumd; cumd = TotalCumPercent - cumd;
if (totalleak >= 1.0f) AfterCumOut(); if (totalleak >= 1.0f) AfterCumOut();
AfterFluidOut(cumd); AfterFluidOut(cumd);
@ -1014,7 +1070,7 @@ namespace RJW_Menstruation
List<string> filthlabels = new List<string>(); List<string> filthlabels = new List<string>();
float outcum = 0; float outcum = 0;
float cumd = TotalCumPercent; float cumd = TotalCumPercent;
List<Cum> removecums = new List<Cum>(); HashSet<Cum> removecums = new HashSet<Cum>();
foreach (Cum cum in cums) foreach (Cum cum in cums)
{ {
float vd = cum.DismishForce(portion); float vd = cum.DismishForce(portion);
@ -1027,10 +1083,7 @@ namespace RJW_Menstruation
} }
if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels); if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak); else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
foreach (Cum cum in removecums) cums.RemoveAll(cum => removecums.Contains(cum));
{
cums.Remove(cum);
}
cumd = TotalCumPercent - cumd; cumd = TotalCumPercent - cumd;
AfterFluidOut(cumd); AfterFluidOut(cumd);
return outcum; return outcum;
@ -1048,7 +1101,7 @@ namespace RJW_Menstruation
Color color = GetCumMixtureColor; Color color = GetCumMixtureColor;
float totalleak = 0; float totalleak = 0;
List<string> cumlabels = new List<string>(); List<string> cumlabels = new List<string>();
List<Cum> removecums = new List<Cum>(); HashSet<Cum> removecums = new HashSet<Cum>();
bool pure = true; bool pure = true;
foreach (Cum cum in cums) foreach (Cum cum in cums)
{ {
@ -1059,10 +1112,7 @@ namespace RJW_Menstruation
if (cum.ShouldRemove()) removecums.Add(cum); if (cum.ShouldRemove()) removecums.Add(cum);
if (cum.notcum) pure = false; if (cum.notcum) pure = false;
} }
foreach (Cum cum in removecums) cums.RemoveAll(cum => removecums.Contains(cum));
{
cums.Remove(cum);
}
return new CumMixture(Pawn, totalleak, cumlabels, color, mixtureDef, pure); return new CumMixture(Pawn, totalleak, cumlabels, color, mixtureDef, pure);
} }
@ -1111,9 +1161,12 @@ namespace RJW_Menstruation
if (cycleSpeed < 0f) cycleSpeed = Utility.RandGaussianLike(0.8f, 1.2f); if (cycleSpeed < 0f) cycleSpeed = Utility.RandGaussianLike(0.8f, 1.2f);
if (cycleVariability < 0f) cycleVariability = MenstruationUtility.RandomVariabilityPercent(); if (cycleVariability < 0f) cycleVariability = MenstruationUtility.RandomVariabilityPercent();
InitOvary();
if (currentIntervalHours < 0) 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 if (!IsBreedingSeason()) curStage = Stage.Anestrus;
else curStage = RandomStage(); else curStage = RandomStage();
if (curStage == Stage.Follicular) if (curStage == Stage.Follicular)
@ -1128,8 +1181,6 @@ namespace RJW_Menstruation
if (cums == null) cums = new List<Cum>(); if (cums == null) cums = new List<Cum>();
if (eggs == null) eggs = new List<Egg>(); if (eggs == null) eggs = new List<Egg>();
InitOvary();
TakeLoosePregnancy(); TakeLoosePregnancy();
//Log.Message(Pawn.Label + " - Initialized menstruation comp"); //Log.Message(Pawn.Label + " - Initialized menstruation comp");
@ -1283,7 +1334,7 @@ namespace RJW_Menstruation
float totalFertPower = eligibleCum.Sum(cum => cum.FertVolume); 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; return null;
Pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1); Pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1);
@ -1304,7 +1355,7 @@ namespace RJW_Menstruation
{ {
if (eggs.NullOrEmpty()) return false; if (eggs.NullOrEmpty()) return false;
List<Egg> deadeggs = new List<Egg>(); HashSet<Egg> deadeggs = new HashSet<Egg>();
bool pregnant = false; bool pregnant = false;
foreach (Egg egg in eggs) foreach (Egg egg in eggs)
{ {
@ -1324,7 +1375,7 @@ namespace RJW_Menstruation
deadeggs.Add(egg); deadeggs.Add(egg);
continue; 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 (Configurations.Debug) Log.Message($"Implanting fertilized egg of {Pawn} into {parent}, father {egg.fertilizer}");
if (pregnancy != null) if (pregnancy != null)
@ -1397,9 +1448,7 @@ namespace RJW_Menstruation
rjw_preg.p_start_tick -= egg.fertstage / Configurations.CycleAcceleration * GenDate.TicksPerHour; rjw_preg.p_start_tick -= egg.fertstage / Configurations.CycleAcceleration * GenDate.TicksPerHour;
rjw_preg.p_end_tick -= egg.fertstage / Configurations.CycleAcceleration * GenDate.TicksPerHour; rjw_preg.p_end_tick -= egg.fertstage / Configurations.CycleAcceleration * GenDate.TicksPerHour;
} }
if (!(pregnancy is Hediff_MultiplePregnancy)) break;
} }
} }
else else
{ {
@ -1416,12 +1465,7 @@ namespace RJW_Menstruation
return true; return true;
} }
else else
{ eggs.RemoveAll(egg => deadeggs.Contains(egg));
foreach (Egg egg in deadeggs)
{
eggs.Remove(egg);
}
}
return pregnant; return pregnant;
} }
@ -1491,7 +1535,7 @@ namespace RJW_Menstruation
protected void EggDecay() protected void EggDecay()
{ {
List<Egg> deadeggs = new List<Egg>(); HashSet<Egg> deadeggs = new HashSet<Egg>();
foreach (Egg egg in eggs) foreach (Egg egg in eggs)
{ {
egg.position += Configurations.CycleAcceleration; egg.position += Configurations.CycleAcceleration;
@ -1502,10 +1546,7 @@ namespace RJW_Menstruation
if (egg.lifespanhrs < 0) deadeggs.Add(egg); if (egg.lifespanhrs < 0) deadeggs.Add(egg);
} }
} }
foreach (Egg egg in deadeggs) eggs.RemoveAll(egg => deadeggs.Contains(egg));
{
eggs.Remove(egg);
}
} }
protected void AddCrampPain() protected void AddCrampPain()
@ -1545,7 +1586,6 @@ namespace RJW_Menstruation
{ {
estrusflag = false; estrusflag = false;
float eggnum; float eggnum;
int ovulated;
try try
{ {
eggnum = Math.Max(Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1f); eggnum = Math.Max(Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1f);
@ -1560,31 +1600,32 @@ namespace RJW_Menstruation
eggnum = 1f; eggnum = 1f;
} }
eggnum *= ovulationFactor; eggnum *= ovulationFactor;
ovulated = (int)eggnum + eggstack; int toOvulate = (int)eggnum + eggstack;
for (int i = 0; i < ovulated; i++) 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))); eggs.Add(new Egg((int)(EggLifespanHours / CycleFactor)));
ovarypower -= ovulated; ++ovulated;
}
if(ovulated > ovarypower) ovulated = Math.Min(ovarypower, eggstack);
ovarypower -= ovulated;
eggstack = 0; eggstack = 0;
if (EggHealth <= 0) if (Configurations.Debug && ovulated != toOvulate)
{ Log.Message($"{Pawn} ovulated {ovulated}/{toOvulate} eggs ({ovulationChance.ToStringPercent()} chance)");
eggs.Clear();
ovarypower = 0;
GoNextStage(Stage.Infertile);
}
else
{
GoNextStage(Stage.Luteal); GoNextStage(Stage.Luteal);
} }
}
protected virtual void LutealAction() protected virtual void LutealAction()
{ {
if (curStageHrs >= currentIntervalHours) if (curStageHrs >= currentIntervalHours)
{ {
eggs.Clear(); 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); GoNextStage(Stage.Follicular);
} }
@ -1663,7 +1704,7 @@ namespace RJW_Menstruation
{ {
if (curStageHrs >= currentIntervalHours) if (curStageHrs >= currentIntervalHours)
{ {
if (Pawn.health.capacities.GetLevel(xxx.reproduction) == 0 || EggHealth <= 0 || Pawn.SterileGenes()) if (ShouldBeInfertile())
{ {
GoNextStage(Stage.Infertile); GoNextStage(Stage.Infertile);
} }
@ -1685,7 +1726,7 @@ namespace RJW_Menstruation
protected virtual void InfertileAction() protected virtual void InfertileAction()
{ {
if (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes()) if (ShouldBeInfertile())
{ {
StayCurrentStageConst(Stage.Infertile); StayCurrentStageConst(Stage.Infertile);
} }
@ -1712,9 +1753,9 @@ namespace RJW_Menstruation
{ {
if (!xxx.is_human(Pawn) || !xxx.is_human(cummer)) return; if (!xxx.is_human(Pawn) || !xxx.is_human(cummer)) return;
if ((cummer.Has(Quirk.Teratophile) != (Pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0)) || if ((cummer.HasQuirk(QuirkUtility.Quirks.Teratophile) != (Pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0)) ||
cummer.Has(Quirk.ImpregnationFetish) || cummer.HasQuirk(QuirkUtility.Quirks.ImpregnationFetish) ||
cummer.Has(Quirk.Breeder)) cummer.HasQuirk(QuirkUtility.Quirks.Breeder))
{ {
if (cummer.relations.OpinionOf(Pawn) <= -25) if (cummer.relations.OpinionOf(Pawn) <= -25)
{ {
@ -1728,7 +1769,7 @@ namespace RJW_Menstruation
if (IsDangerDay) 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); Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetish, cummer);
} }
@ -1751,7 +1792,7 @@ namespace RJW_Menstruation
} }
else 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); Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetishSafe, cummer);
} }
@ -1772,7 +1813,7 @@ namespace RJW_Menstruation
TaleRecorder.RecordTale(VariousDefOf.TaleCameInside, new object[] { cummer, Pawn }); 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; curStageHrs = 0;
if (calculateHours) currentIntervalHours = PeriodRandomizer(nextstage); if (calculateHours) currentIntervalHours = PeriodRandomizer(nextstage);
@ -1781,12 +1822,7 @@ namespace RJW_Menstruation
protected virtual void GoOvulatoryStage() protected virtual void GoOvulatoryStage()
{ {
if (EggHealth < 1.0f / 3.0f && Rand.Range(0.0f, 1.0f) < 0.2f) // Skip ovulation if deep into climacteric GoNextStage(Stage.Ovulatory);
{
estrusflag = false;
GoNextStage(Stage.Luteal);
}
else GoNextStage(Stage.Ovulatory);
} }
//stage can be interrupted in other reasons //stage can be interrupted in other reasons

View file

@ -214,6 +214,10 @@ namespace RJW_Menstruation
PregnancyUtility.ApplyBirthOutcome(thisOutcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments); 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 // No more babies if mom dies halfway through. Unrealistic maybe, but saves a lot of headache in ApplyBirthOutcome
if (mother.health.Dead) break; 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); thisOutcome = ((RitualOutcomeEffectWorker_ChildBirth)precept_Ritual.outcomeEffect).GetOutcome(birthQuality, null);
} while (comp.HasBaby); } while (comp.HasBaby);
@ -263,6 +267,10 @@ namespace RJW_Menstruation
PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments); PregnancyUtility.ApplyBirthOutcome(outcome, quality, ritual, genes, geneticMother, birtherThing, thisFather, doctor, lordJobRitual, assignments);
if (mother.health.Dead) break; 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); } while (comp.HasBaby);
// The ritual version doesn't use the return value, either // The ritual version doesn't use the return value, either

View file

@ -1,4 +1,5 @@
using RimWorld; using RimWorld;
using RimWorld.Planet;
using rjw; using rjw;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -218,6 +219,8 @@ namespace RJW_Menstruation
} }
public static Texture2D GetEggIcon(this HediffComp_Menstruation comp, bool includeOvary) 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) switch (comp.CurrentVisibleStage)
{ {
case HediffComp_Menstruation.Stage.Follicular: case HediffComp_Menstruation.Stage.Follicular:
@ -228,15 +231,20 @@ namespace RJW_Menstruation
job.Sexprops != null && job.Sexprops != null &&
!job.Sexprops.usedCondom && !job.Sexprops.usedCondom &&
(job.Sexprops.sexType == xxx.rjwSextype.Vaginal || job.Sexprops.sexType == xxx.rjwSextype.DoublePenetration)) (job.Sexprops.sexType == xxx.rjwSextype.Vaginal || job.Sexprops.sexType == xxx.rjwSextype.DoublePenetration))
return ContentFinder<Texture2D>.Get("Ovaries/Ovary_01", true); return ContentFinder<Texture2D>.Get((comp.OvulationChance >= ovaryChanceToShow_01) ? "Ovaries/Ovary_01" : "Ovaries_Ovary_00", true);
else break; else break;
} }
if (comp.curStageHrs > comp.CurStageIntervalHours - 30) // Approximate time for ovulation to occur if (comp.curStageHrs > comp.CurStageIntervalHours - 30) // Approximate time for ovulation to occur
return ContentFinder<Texture2D>.Get("Ovaries/Ovary_01", true); return ContentFinder<Texture2D>.Get((comp.OvulationChance >= ovaryChanceToShow_01) ? "Ovaries/Ovary_01" : "Ovaries_Ovary_00", true);
else break; else break;
case HediffComp_Menstruation.Stage.Ovulatory: case HediffComp_Menstruation.Stage.Ovulatory:
if (!includeOvary) break; if (!includeOvary) break;
if (comp.OvulationChance >= ovaryChanceToShow_02)
return ContentFinder<Texture2D>.Get("Ovaries/Ovary_02", true); return ContentFinder<Texture2D>.Get("Ovaries/Ovary_02", true);
else if (comp.OvulationChance >= ovaryChanceToShow_01)
return ContentFinder<Texture2D>.Get("Ovaries/Ovary_01", true);
else
return ContentFinder<Texture2D>.Get("Ovaries/Ovary_00", true);
case HediffComp_Menstruation.Stage.Luteal: case HediffComp_Menstruation.Stage.Luteal:
if (!comp.IsEggExist) break; if (!comp.IsEggExist) break;
int fertstage = comp.IsFertilized; int fertstage = comp.IsFertilized;

View file

@ -23,11 +23,11 @@ namespace RJW_Menstruation
{ {
if (is_discovered || if (is_discovered ||
!xxx.is_human(pawn) || !xxx.is_human(pawn) ||
pawn.Has(Quirk.Breeder) || pawn.HasQuirk(QuirkUtility.Quirks.Breeder) ||
(pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) || (pawn.relations?.DirectRelations?.Find(x => x.def.Equals(PawnRelationDefOf.Spouse) ||
x.def.Equals(PawnRelationDefOf.Fiance))) != null) x.def.Equals(PawnRelationDefOf.Fiance))) != null)
return; 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); pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.UnwantedPregnancyMild);
} }

View file

@ -26,33 +26,28 @@ namespace RJW_Menstruation
public static void Postfix(Hediff_Pregnant __instance) public static void Postfix(Hediff_Pregnant __instance)
{ {
HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor starting, menstruation comp is {comp}");
if (comp == null) return; if (comp == null) return;
comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLabor); 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))] [HarmonyPatch(typeof(Hediff_Labor), nameof(Hediff_Labor.PreRemoved))]
public class Labor_PreRemoved_Patch public class Labor_PreRemoved_Patch
{ {
public static void PostFix(Hediff_Labor __instance) public static void Postfix(Hediff_Labor __instance)
{ {
HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
if (Configurations.Debug) Log.Message($"{comp.Pawn}'s initial labor ending, menstruation comp is {comp}");
if (comp == null) return; if (comp == null) return;
comp.Pregnancy = __instance.pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.PregnancyLaborPushing); 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))] [HarmonyPatch(typeof(Hediff_LaborPushing), nameof(Hediff_LaborPushing.PreRemoved))]
public class LaborPushing_PreRemoved_Patch public class LaborPushing_PreRemoved_Patch
{ {
public static void PostFix(Hediff_LaborPushing __instance) public static void Postfix(Hediff_LaborPushing __instance)
{ {
HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy(); HediffComp_Menstruation comp = __instance.GetMenstruationCompFromPregnancy();
if (Configurations.Debug) Log.Message($"{comp.Pawn}'s labor pushing ending, menstruation comp is {comp}");
if (comp == null) return; if (comp == null) return;
comp.Pregnancy = null; comp.Pregnancy = null;
} }
@ -62,7 +57,7 @@ namespace RJW_Menstruation
[HarmonyPatch(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.GestationProgress), MethodType.Getter)] [HarmonyPatch(typeof(Hediff_Pregnant), nameof(Hediff_Pregnant.GestationProgress), MethodType.Getter)]
public class Hediff_Pregnant_GestationProgess_Patch 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; if (__result < 1f) return;
Pawn pawn = __instance.pawn; Pawn pawn = __instance.pawn;
@ -74,7 +69,7 @@ namespace RJW_Menstruation
[HarmonyPatch(typeof(Recipe_ExtractOvum), nameof(Recipe_ExtractOvum.AvailableReport))] [HarmonyPatch(typeof(Recipe_ExtractOvum), nameof(Recipe_ExtractOvum.AvailableReport))]
public class ExtractOvum_AvailableReport_Patch 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; if (!__result.Accepted) return;
Pawn pawn = (Pawn)thing; Pawn pawn = (Pawn)thing;
@ -97,7 +92,7 @@ namespace RJW_Menstruation
[HarmonyPatch(typeof(Recipe_ExtractOvum), "OnSurgerySuccess")] [HarmonyPatch(typeof(Recipe_ExtractOvum), "OnSurgerySuccess")]
public class ExtractOvum_OnSurgerySuccess_Patch public class ExtractOvum_OnSurgerySuccess_Patch
{ {
public static void PostFix(Pawn pawn) public static void Postfix(Pawn pawn)
{ {
List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList(); List<HediffComp_Menstruation> comps = pawn.GetMenstruationComps().ToList();
if (!comps.Any()) return; if (!comps.Any()) return;
@ -110,7 +105,7 @@ namespace RJW_Menstruation
[HarmonyPatch(typeof(Recipe_ImplantEmbryo), nameof(Recipe_ImplantEmbryo.ApplyOnPawn))] [HarmonyPatch(typeof(Recipe_ImplantEmbryo), nameof(Recipe_ImplantEmbryo.ApplyOnPawn))]
public class ImplantEmbryo_ApplyOnPawn_Patch public class ImplantEmbryo_ApplyOnPawn_Patch
{ {
public static void PostFix(Pawn pawn) public static void Postfix(Pawn pawn)
{ {
foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps()) foreach (HediffComp_Menstruation comp in pawn.GetMenstruationComps())
comp.TakeLoosePregnancy(); comp.TakeLoosePregnancy();
@ -120,7 +115,7 @@ namespace RJW_Menstruation
[HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))] [HarmonyPatch(typeof(PregnancyUtility), nameof(PregnancyUtility.ApplyBirthOutcome))]
public class ApplyBirthOutcome_Breast_Patch 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) if (birtherThing is Pawn pawn && !pawn.health.Dead)
pawn.GetBreastComp()?.GaveBirth(); pawn.GetBreastComp()?.GaveBirth();

View file

@ -31,7 +31,7 @@ namespace RJW_Menstruation
List<Hediff> pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn)); List<Hediff> pawnparts = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));
HediffComp_Menstruation comp; 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(); comp = partner.GetFertileMenstruationComp();
else comp = partner.GetRandomMenstruationComp(); else comp = partner.GetRandomMenstruationComp();
if (comp == null) return true; if (comp == null) return true;
@ -102,7 +102,7 @@ namespace RJW_Menstruation
{ {
if (partner.IsAnimal() && !Configurations.EnableAnimalCycle) return true; if (partner.IsAnimal() && !Configurations.EnableAnimalCycle) return true;
HediffComp_Menstruation comp; 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(); comp = partner.GetFertileMenstruationComp();
else comp = partner.GetRandomMenstruationComp(); else comp = partner.GetRandomMenstruationComp();
if (comp == null) if (comp == null)
@ -179,7 +179,7 @@ namespace RJW_Menstruation
{ {
// Awkward, but it'll have to do // Awkward, but it'll have to do
Pawn pawn = props.pawn; 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 // Check if the existing code would have added the count
Pawn partner = props.partner; 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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> 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;
}
}
}
} }

View file

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

View file

@ -77,6 +77,7 @@
<Compile Include="Patch\GC_Patch.cs" /> <Compile Include="Patch\GC_Patch.cs" />
<Compile Include="Patch\Gizmo_Patch.cs" /> <Compile Include="Patch\Gizmo_Patch.cs" />
<Compile Include="PregnancyCommon.cs" /> <Compile Include="PregnancyCommon.cs" />
<Compile Include="QuirkUtility.cs" />
<Compile Include="Recipe_Surgery.cs" /> <Compile Include="Recipe_Surgery.cs" />
<Compile Include="StatParts.cs" /> <Compile Include="StatParts.cs" />
<Compile Include="UI\Dialog_HybridCustom.cs" /> <Compile Include="UI\Dialog_HybridCustom.cs" />

View file

@ -134,6 +134,8 @@ namespace RJW_Menstruation
public static readonly string EstimatedCumLifespan = "EstimatedCumLifespan".Translate(); public static readonly string EstimatedCumLifespan = "EstimatedCumLifespan".Translate();
public static readonly string EstimatedEggLifespan = "EstimatedEggLifespan".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 string FertilityDesc(string value) => "FertilityDesc".Translate(value);
public static readonly string Gizmo_GatherCum = "Gizmo_GatherCum".Translate(); public static readonly string Gizmo_GatherCum = "Gizmo_GatherCum".Translate();

View file

@ -450,11 +450,18 @@ namespace RJW_Menstruation
statvalue = pawn.records.GetValue(xxx.CountOfBirthEgg); statvalue = pawn.records.GetValue(xxx.CountOfBirthEgg);
FillableBarLabeled(lineRect, " " + xxx.CountOfBirthEgg.LabelCap.CapitalizeFirst() + " " + statvalue, statvalue / 100, TextureCache.RecoverTexture, Texture2D.blackTexture, xxx.CountOfBirthEgg.description); 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(); 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); Rect overayRect = new Rect(lineRect.x, lineRect.y, lineRect.width * Math.Min(1.0f, fertchance), lineRect.height);
GUI.DrawTexture(overayRect, TextureCache.FertChanceTex); GUI.DrawTexture(overayRect, TextureCache.FertChanceTex);
lineRect.y += height; lineRect.y += height;

View file

@ -86,7 +86,7 @@ namespace RJW_Menstruation
{ {
res = 0.0f; 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; return res;
} }
@ -463,6 +463,10 @@ namespace RJW_Menstruation
{ {
return Color.white; return Color.white;
} }
catch (InvalidOperationException) // And sometimes it can try to pull the value of a Nullable without checking, too
{
return Color.white;
}
} }
} }
} }

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest> <Manifest>
<identifier>RJW Menstruation</identifier> <identifier>RJW Menstruation</identifier>
<version>1.0.8.7</version> <version>1.0.8.9</version>
<dependencies> <dependencies>
</dependencies> </dependencies>
<incompatibleWith /> <incompatibleWith />

View file

@ -1,3 +1,17 @@
Version 1.0.8.9
- Fix bug that sent pawns into menopause very early. Please use the recalculate ovary power dev action to restore lost eggs.
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 Version 1.0.8.7
- Fix missing texture when using Milkable Colonists. - Fix missing texture when using Milkable Colonists.
- Fix estrus and egg lifespan lasting far longer than intended. - Fix estrus and egg lifespan lasting far longer than intended.