Compare commits

..

11 commits

Author SHA1 Message Date
Akiyami Solo
9afb3c0df0 Merge branch 'dev' into 'dev'
Breast fullness is not displayed and the "milk yourself" action is not invoked on a pawn when the rjw-mc-biotech mod is present

See merge request lutepickle/rjw_menstruation!6
2024-02-28 19:48:38 +00:00
lutepickle
32800a3499 Null check for apparel tracker in DirtyEffect 2024-02-22 22:09:12 -08:00
lutepickle
ff95b8da39 It's safe and a bit quicker to unconditionally remove forced in CheckDirty 2024-02-17 15:37:14 -08:00
lutepickle
8306439576 Refactor allraces and allkinds 2024-02-16 22:03:03 -08:00
lutepickle
ebb486179b Reformat RandomOvulationChance 2024-02-16 20:47:59 -08:00
lutepickle
f79050483d Remove the originsize system. Nobody used it 2024-02-16 20:24:10 -08:00
lutepickle
d483e22ac2 Little cleanup 2024-02-16 16:55:53 -08:00
lutepickle
7e5b2000da Mark the Conit fork of Birds & Bees as incompatible 2024-02-16 13:52:46 -08:00
lutepickle
836d3d69c8 Let CalculatedImpactChance return early to avoid unnecessarily hitting OvulationChance 2024-02-12 22:19:10 -08:00
lutepickle
50310988ca Be sure the periodic can go into anestrus if it initializes into ovulatory 2024-02-11 10:47:01 -08:00
lutepickle
82dea0f425 Always attempt to ovulate at least one egg 2024-02-11 07:30:25 -08:00
10 changed files with 57 additions and 122 deletions

Binary file not shown.

View file

@ -144,11 +144,5 @@
</li>
</comps>
</value>
</Operation>
</Operation>
</Patch>

View file

@ -110,9 +110,6 @@ namespace RJW_Menstruation
else return base.ShouldBeInEstrus();
}
protected override float RandomOvulationChance()
{
return 0;
}
protected override float RandomOvulationChance => 0;
}
}

View file

@ -127,7 +127,6 @@ namespace RJW_Menstruation
protected float implantationChanceCache = -1.0f;
protected int opcache = -1;
protected float antisperm = 0.0f;
protected float? originvagsize = null;
// RJW pregnancy, or Biotech pregnancy/labor/laborpushing
protected Hediff pregnancy = null;
@ -179,7 +178,8 @@ namespace RJW_Menstruation
public float HoursBetweenSimulations => (float)TickInterval / GenDate.TicksPerHour;
public Hediff Pregnancy {
public Hediff Pregnancy
{
get
{
if (pregnancy == null) return null;
@ -255,7 +255,7 @@ namespace RJW_Menstruation
public float TotalCum
{
get => cums?.Sum(cum => cum.Volume) ?? 0;
get => cums?.Sum(cum => cum.Volume) ?? 0;
}
public float TotalFertCum
{
@ -322,6 +322,7 @@ namespace RJW_Menstruation
// Implant factor will be based solely on pawn age, plus any rollover from ovulation chance
float factor = 1.0f;
StatDefOf.Fertility.GetStatPart<StatPart_FertilityByGenderAge>()?.TransformValue(StatRequest.For(Pawn), ref factor);
if (factor <= 0.0f) return 0.0f;
if (OvulationChance > 1.0f) factor *= OvulationChance;
return Props.baseImplantationChanceFactor * FertilityModifier * factor;
}
@ -356,7 +357,7 @@ namespace RJW_Menstruation
{
if (cums.NullOrEmpty()) yield return Translations.Info_noCum;
else foreach (Cum cum in cums)
yield return string.Format("{0}: {1:0.##}ml", cum.notcum ? cum.notcumLabel : cum.pawn?.Label, cum.Volume);
yield return string.Format("{0}: {1:0.##}ml", cum.notcum ? cum.notcumLabel : cum.pawn?.Label, cum.Volume);
}
}
public Color GetCumMixtureColor
@ -563,19 +564,6 @@ namespace RJW_Menstruation
}
}
public float OriginVagSize
{
get
{
if (originvagsize == null)
{
originvagsize = parent.Severity;
}
return originvagsize ?? 0.1f;
}
set => originvagsize = value;
}
public int CurStageIntervalTicks
{
get => currentIntervalTicks;
@ -608,7 +596,7 @@ namespace RJW_Menstruation
else if (Pawn.story?.bodyType == BodyTypeDefOf.Female) discoveryTime = 0.35f;
// Estimated; there's no way to get the exact value after the fact without writing it into the save
float lutealProgressWhenImplanted = Math.Min(0.5f, maxImplantDelayHours / (Props.lutealIntervalDays * GenDate.HoursPerDay));
return GenMath.LerpDouble(0, discoveryTime, lutealProgressWhenImplanted, 1.0f, pregnancy.Severity);
}
}
@ -656,7 +644,6 @@ namespace RJW_Menstruation
Scribe_Values.Look(ref ovarypower, "ovarypower", ovarypower, true);
Scribe_Values.Look(ref eggstack, "eggstack", 0);
Scribe_Values.Look(ref estrusflag, "estrusflag", false);
Scribe_Values.Look(ref originvagsize, "originvagsize", originvagsize, true);
Scribe_Values.Look(ref DoCleanWomb, "DoCleanWomb", false);
Scribe_References.Look(ref pregnancy, "pregnancy");
if (Scribe.mode == LoadSaveMode.PostLoadInit)
@ -679,12 +666,10 @@ namespace RJW_Menstruation
ovulationFactor = 1f;
noBleeding = false;
opcache = -1;
if (Pawn.genes == null || !ModsConfig.BiotechActive) return;
foreach (MenstruationModExtension extension in Pawn.genes.GenesListForReading.Select(gene => gene.def.GetModExtension<MenstruationModExtension>()))
{
if (extension == null) continue;
if (Pawn.genes == null || !ModsConfig.BiotechActive) return;
foreach (MenstruationModExtension extension in Pawn.genes.GenesListForReading.Select(gene => gene.def.GetModExtension<MenstruationModExtension>()).Where(ext => ext != null))
{
eggLifeSpanTicks = (int)(eggLifeSpanTicks * extension.eggLifeTimeFactor);
if (extension.alwaysEstrus) estrusLevel = EstrusLevel.Visible;
else if (extension.neverEstrus) estrusLevel = EstrusLevel.None;
@ -721,7 +706,7 @@ namespace RJW_Menstruation
{
if (Pawn.IsHashIntervalTick(recalculateTickInterval)) TickInterval = -1; // Every so often, force TickInterval to be recalculated in case the pawn's status changed.
if (!Pawn.IsHashIntervalTick(TickInterval)) return;
if (!ShouldSimulate()) return;
// Initialize immediately if needed, but if there's an error, then don't spam it every tick
@ -730,7 +715,7 @@ namespace RJW_Menstruation
Log.Warning($"{Pawn}'s womb is ticking, but was not initialized first");
Initialize();
}
if (initError) Log.Warning($"Attempting to process {Pawn}'s womb uninitialized");
if (Pregnancy != null && curStage != Stage.Pregnant)
@ -740,7 +725,7 @@ namespace RJW_Menstruation
}
BeforeSimulator();
if (ShouldBeInfertile()) GoNextStage(Stage.Infertile);
switch (curStage)
{
@ -817,7 +802,7 @@ namespace RJW_Menstruation
tip.Append(": ");
tip.Append(GetCurStageLabel);
string fertInfo = GetFertilizingInfo;
if(CurrentVisibleStage == Stage.Luteal && fertInfo.Length > 0)
if (CurrentVisibleStage == Stage.Luteal && fertInfo.Length > 0)
{
tip.AppendLine();
tip.Append(fertInfo);
@ -828,7 +813,7 @@ namespace RJW_Menstruation
protected virtual int TicksToNextStage()
{
return Math.Max(0,(currentIntervalTicks - curStageTicks) / Configurations.CycleAcceleration);
return Math.Max(0, (currentIntervalTicks - curStageTicks) / Configurations.CycleAcceleration);
}
public override string CompDebugString()
@ -1173,7 +1158,7 @@ namespace RJW_Menstruation
if (cycleSpeed < 0f) cycleSpeed = Utility.RandGaussianLike(0.8f, 1.2f);
if (cycleVariability < 0f) cycleVariability = MenstruationUtility.RandomVariabilityPercent();
InitOvary();
if (currentIntervalTicks < 0)
@ -1361,7 +1346,7 @@ namespace RJW_Menstruation
//float fertFailChancePerHour = Mathf.Pow(1.0f - Configurations.FertilizeChance, totalFertPower * Props.basefertilizationChanceFactor);
//float fertFailChancePerInterval = Mathf.Pow(fertFailChancePerHour, (float)TickInterval / GenDate.TicksPerHour);
float fertFailChancePerInterval = Mathf.Pow(1.0f - Configurations.FertilizeChance, totalFertPower * Props.basefertilizationChanceFactor * HoursBetweenSimulations);
if (Rand.Chance(fertFailChancePerInterval)) return null;
Pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1);
@ -1491,7 +1476,7 @@ namespace RJW_Menstruation
float interspeciesFactor = InterspeciesImplantFactor(egg.fertilizer);
float implantChance = Configurations.ImplantationChance * ImplantChance * interspeciesFactor;
Log.Message($"Fertilized egg of {Pawn} failed to implant (chance {implantChance.ToStringPercent()}, " +
(interspeciesFactor < 1.0f ? $"interspecies factor {interspeciesFactor.ToStringPercent()}, " : "" ) +
(interspeciesFactor < 1.0f ? $"interspecies factor {interspeciesFactor.ToStringPercent()}, " : "") +
$"father {egg.fertilizer})");
}
deadeggs.Add(egg);
@ -1577,9 +1562,6 @@ namespace RJW_Menstruation
return amount;
}
protected void EggDecay()
{
HashSet<Egg> deadeggs = new HashSet<Egg>();
@ -1656,7 +1638,7 @@ namespace RJW_Menstruation
eggnum = 1f;
}
eggnum *= ovulationFactor;
int toOvulate = (int)eggnum + eggstack;
int toOvulate = Math.Max(1, (int)eggnum + eggstack);
int ovulated = 0;
for (int i = 0; i < toOvulate; i++)
@ -1667,7 +1649,7 @@ namespace RJW_Menstruation
}
ovarypower -= ovulated;
eggstack = 0;
if (Configurations.Debug && ovulated != toOvulate)
if (Configurations.Debug && ovulated < toOvulate)
Log.Message($"{Pawn} ovulated {ovulated}/{toOvulate} eggs ({OvulationChance.ToStringPercent()} chance)");
GoNextStage(Stage.Luteal);
@ -1814,10 +1796,10 @@ namespace RJW_Menstruation
}
else pawnMemories.TryGainMemory(VariousDefOf.CameInsideFFetish, cummer);
}
else if (Pawn.relations.OpinionOf(cummer) <= -5)
pawnMemories.TryGainMemory(VariousDefOf.HaterCameInsideF, cummer);
else if (Pawn.IsInEstrus() && Pawn.relations.OpinionOf(cummer) < RJWHookupSettings.MinimumRelationshipToHookup)
pawnMemories.TryGainMemory(VariousDefOf.HaterCameInsideFEstrus, cummer);
else if (Pawn.relations.OpinionOf(cummer) <= -5)
pawnMemories.TryGainMemory(VariousDefOf.HaterCameInsideF, cummer);
else if (Pawn.IsInEstrus() && Pawn.relations.OpinionOf(cummer) < RJWHookupSettings.MinimumRelationshipToHookup)
pawnMemories.TryGainMemory(VariousDefOf.HaterCameInsideFEstrus, cummer);
else if (!Pawn.relations.DirectRelationExists(PawnRelationDefOf.Spouse, cummer) && !Pawn.relations.DirectRelationExists(PawnRelationDefOf.Fiance, cummer))
{
if (Pawn.health.capacities.GetLevel(xxx.reproduction) < 0.50f) pawnMemories.TryGainMemory(VariousDefOf.CameInsideFLowFert, cummer);
@ -1914,16 +1896,13 @@ namespace RJW_Menstruation
else return Rand.Range(0.6f, 1.0f);
}
protected virtual float RandomOvulationChance()
{
return (float)Props.ovulationIntervalHours / GenDate.HoursPerDay;
}
protected virtual float RandomOvulationChance => (float)Props.ovulationIntervalHours / GenDate.HoursPerDay;
protected Stage RandomStage()
{
Stage stage = Rand.ElementByWeight(
Stage.Follicular, Props.follicularIntervalDays - Props.bleedingIntervalDays,
Stage.Ovulatory, RandomOvulationChance(),
Stage.Ovulatory, RandomOvulationChance,
Stage.Luteal, Props.lutealIntervalDays,
Stage.Bleeding, Props.bleedingIntervalDays);
@ -2035,28 +2014,6 @@ namespace RJW_Menstruation
public class HediffComp_Anus : HediffComp
{
protected float? originanussize;
public float OriginAnusSize
{
get
{
if (originanussize == null)
{
originanussize = parent.Severity;
}
return originanussize ?? 0.1f;
}
}
public override void CompExposeData()
{
base.CompExposeData();
Scribe_Values.Look(ref originanussize, "originanussize", originanussize, true);
}
public override void CompPostTick(ref float severityAdjustment)
{
}
public CompProperties_Anus Props => (CompProperties_Anus)props;
}
}

View file

@ -29,14 +29,14 @@ namespace RJW_Menstruation
protected override void InitializeExtraValues()
{
base.InitializeExtraValues();
base.InitializeExtraValues();
if (averageCycleIntervalTicks < 0)
{
averageCycleIntervalTicks = (int)(Props.cycleIntervalDays.RandomInRange * GenDate.TicksPerDay / cycleSpeed);
if (ticksToNextCycle < -50000)
ticksToNextCycle = Rand.Range(0, averageCycleIntervalTicks);
// Make the cutoff halfway into cycle, just to be sure there isn't a double-cycle the first time
if ((curStage == Stage.Follicular || curStage == Stage.Luteal || curStage == Stage.Bleeding)
if ((curStage == Stage.Follicular || curStage == Stage.Ovulatory || curStage == Stage.Luteal || curStage == Stage.Bleeding)
&& (averageCycleIntervalTicks - ticksToNextCycle) / 2 >= GenDate.TicksPerDay * (Props.follicularIntervalDays + Props.lutealIntervalDays) / cycleSpeed)
GoNextStage(Stage.Anestrus);
}

View file

@ -141,7 +141,7 @@ namespace RJW_Menstruation
else if (gestationProgress < 0.8f) icon = fetustex + "04";
else icon = fetustex + "05";
return TryGetTwinsIcon(icon, babycount) ?? ContentFinder<Texture2D>.Get((icon), true);
return TryGetTwinsIcon(icon, babycount) ?? ContentFinder<Texture2D>.Get(icon, true);
}
public static Texture2D TryGetTwinsIcon(string path, int babycount)
@ -160,7 +160,7 @@ namespace RJW_Menstruation
List<Hediff_InsectEgg> insectEggs = new List<Hediff_InsectEgg>();
comp.Pawn.health.hediffSet.GetHediffs(ref insectEggs);
if (!insectEggs.NullOrEmpty() && insectEggs.Sum(hediff => hediff.eggssize) > 1.0f) return null; // same logic as "Stuffed" in GetInsectEggedIcon
if (insectEggs?.Sum(hediff => hediff.eggssize) > 1.0f) return null; // same logic as "Stuffed" in GetInsectEggedIcon
string icon = comp.WombTex;
float cumpercent = comp.TotalCumPercent;
@ -183,7 +183,7 @@ namespace RJW_Menstruation
else if (cumpercent < 0.89f) icon += "_Cum_15";
else if (cumpercent < 0.95f) icon += "_Cum_16";
else icon += "_Cum_17";
Texture2D cumtex = ContentFinder<Texture2D>.Get((icon), true);
Texture2D cumtex = ContentFinder<Texture2D>.Get(icon, true);
return cumtex;
}
public static Texture2D GetInsectEggedIcon(this HediffComp_Menstruation comp)
@ -306,15 +306,13 @@ namespace RJW_Menstruation
}
public static Texture2D GetGenitalIcon(this Pawn pawn, HediffComp_Menstruation comp, bool drawOrigin = false)
public static Texture2D GetGenitalIcon(this Pawn pawn, HediffComp_Menstruation comp)
{
Hediff hediff = comp?.parent;
if (hediff == null) return ContentFinder<Texture2D>.Get("Genitals/Vagina00", true);
//HediffComp_Menstruation comp = hediff.GetMenstruationComp();
string icon;
float severity;
if (drawOrigin) severity = comp.OriginVagSize;
else severity = hediff.Severity;
float severity = hediff.Severity;
if (comp != null) icon = comp.VagTex;
else icon = "Genitals/Vagina";
@ -331,30 +329,18 @@ namespace RJW_Menstruation
else if (severity < 1.01f) icon += "10"; //cavernous
else icon += "11"; //abyssal
return ContentFinder<Texture2D>.Get((icon), true);
return ContentFinder<Texture2D>.Get(icon, true);
}
public static Texture2D GetAnalIcon(this Pawn pawn, bool drawOrigin = false)
public static Texture2D GetAnalIcon(this Pawn pawn)
{
Hediff hediff = pawn.health.hediffSet.hediffs.FirstOrDefault(h => VariousDefOf.AllAnuses.Contains(h.def)) ??
pawn.health.hediffSet.hediffs.FirstOrDefault(h => h.def.defName.ToLower().Contains("anus"));
if (hediff == null) return ContentFinder<Texture2D>.Get(("Genitals/Anal00"), true);
if (hediff == null) return ContentFinder<Texture2D>.Get("Genitals/Anal00", true);
string icon = ((CompProperties_Anus)hediff.GetAnusComp()?.props)?.analTex ?? "Genitals/Anal";
float severity = hediff.Severity;
string icon;
float severity;
HediffComp_Anus comp = hediff.GetAnusComp();
if (comp != null)
{
CompProperties_Anus Props = (CompProperties_Anus)comp.props;
icon = Props.analTex ?? "Genitals/Anal";
if (drawOrigin) severity = comp.OriginAnusSize;
else severity = hediff.Severity;
}
else
{
icon = "Genitals/Anal";
severity = hediff.Severity;
}
if (severity < 0.20f) icon += "00"; //micro
else if (severity < 0.40f) icon += "01"; //tight
else if (severity < 0.60f) icon += "02"; //average
@ -362,7 +348,7 @@ namespace RJW_Menstruation
else if (severity < 1.01f) icon += "04"; //cavernous
else icon += "05"; //abyssal
return ContentFinder<Texture2D>.Get((icon), true);
return ContentFinder<Texture2D>.Get(icon, true);
}
public static float GestationHours(this Hediff hediff)
@ -453,7 +439,7 @@ namespace RJW_Menstruation
if (precept != null) return true;
else return pawn.IsBreeder() ||
pawn.HasImpregnationFetish();
pawn.HasImpregnationFetish();
}
public static float DamagePants(this Pawn pawn, float fluidAmount)

View file

@ -225,9 +225,7 @@ namespace RJW_Menstruation
bool newHasStats = !DirtyDef.equippedStatOffsets.NullOrEmpty();
def = DirtyDef;
dirty = true;
OutfitForcedHandler forcedHandler = Wearer.outfits?.forcedHandler;
if (forcedHandler?.IsForced(this) ?? false)
forcedHandler.SetForced(this, false);
Wearer.outfits?.forcedHandler?.SetForced(this, false);
if (oldHasStats || newHasStats)
Wearer.health.capacities.Notify_CapacityLevelsDirty();
Wearer.apparel.Notify_ApparelChanged();
@ -274,7 +272,7 @@ namespace RJW_Menstruation
public override void DirtyEffect(int tickInterval)
{
if (wearTicks > MinHrstoDirtyEffect * GenDate.TicksPerHour && Rand.MTBEventOccurs(100.0f, GenDate.TicksPerHour, tickInterval) && !Wearer.apparel.IsLocked(this))
if (wearTicks > MinHrstoDirtyEffect * GenDate.TicksPerHour && Rand.MTBEventOccurs(100.0f, GenDate.TicksPerHour, tickInterval) && !(Wearer.apparel?.IsLocked(this) ?? false))
{
Wearer.health.AddHediff(HediffDefOf.WoundInfection, Genital_Helper.get_genitalsBPR(Wearer));
}

View file

@ -388,10 +388,9 @@ namespace RJW_Menstruation
Rect genitalIconRect = new Rect(rect.x, rect.y + fontheight, genitalRectWidth, genitalRectHeight);
Rect genitalVaginaLabelRect = new Rect(rect.x, rect.y + 10f, genitalRectWidth, fontheight);
Rect genitalAnusLabelRect = new Rect(rect.x, rect.y + fontheight + genitalRectHeight, genitalRectWidth, fontheight);
bool showOrigin = Mouse.IsOver(genitalIconRect) && Input.GetMouseButton(0);
vagina = pawn.GetGenitalIcon(comp, showOrigin);
anal = pawn.GetAnalIcon(showOrigin);
vagina = pawn.GetGenitalIcon(comp);
anal = pawn.GetAnalIcon();
GUI.color = new Color(1.00f, 0.47f, 0.47f, 1);
GUI.Box(rect, "", boxstyle);
GUI.color = Utility.SafeSkinColor(pawn);

View file

@ -58,9 +58,8 @@ namespace RJW_Menstruation
get
{
if (allraces != null) return allraces;
allraces = DefDatabase<ThingDef>.AllDefsListForReading.Where(thingdef => thingdef.race?.IsFlesh ?? false).ToList();
List<ThingDef> allThings = DefDatabase<ThingDef>.AllDefsListForReading;
allraces = allThings.FindAll(x => x.race != null && x.race.IsFlesh);
return allraces;
}
}
@ -69,9 +68,8 @@ namespace RJW_Menstruation
get
{
if (allkinds != null) return allkinds;
allkinds = DefDatabase<PawnKindDef>.AllDefsListForReading.Where(pawnkinddef => pawnkinddef.race != null).ToList();
List<PawnKindDef> allKinds = DefDatabase<PawnKindDef>.AllDefsListForReading;
allkinds = allKinds.FindAll(x => x.race != null);
return allkinds;
}
}

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
<packageId>rjw.menstruation</packageId>
<name>RJW Menstruation Cycle</name>
<author>lutepickle</author>
<url>https://gitgud.io/lutepickle/rjw_menstruation/</url>
<supportedVersions>
<li>1.2</li>
<li>1.3</li>
@ -28,13 +30,17 @@
<li>Abraxas.RJW.RaceSupport</li>
<li>rjw.milk.humanoid</li>
</loadAfter>
<packageId>rjw.menstruation</packageId>
<incompatibleWithByVersion>
<v1.4>
<li>conit.thebirdsandthebees</li> <!--Breaks fertility calculations-->
</v1.4>
</incompatibleWithByVersion>
<description>Adds menstruation mechanics to vaginas:
Wombs cycle between follicular, luteal, and bleeding phases
Tracks eggs ovulated and cum in wombs to determine pregnancy
Womb icon and status window
Estrus effects
Estrus and pheromone effects
Race-specific fetus images for many vanilla animals
Pregnancies from multiple eggs and different fathers
Identical siblings