diff --git a/1.4/Assemblies/RJW_Menstruation.dll b/1.4/Assemblies/RJW_Menstruation.dll index bc84c90..4d7a51b 100644 Binary files a/1.4/Assemblies/RJW_Menstruation.dll and b/1.4/Assemblies/RJW_Menstruation.dll differ diff --git a/1.4/Patches/Biosculpter_Patch.xml b/1.4/Patches/Biosculpter_Patch.xml index cdee9e3..558f4b7 100644 --- a/1.4/Patches/Biosculpter_Patch.xml +++ b/1.4/Patches/Biosculpter_Patch.xml @@ -11,7 +11,7 @@ eggRestoration Restore one year worth of eggs in each womb. - Eggs/Egg + UI/Icon/EggRegeneration 4 diff --git a/1.4/Patches/Hediffs_PrivateParts_Animal.xml b/1.4/Patches/Hediffs_PrivateParts_Animal.xml index 723eafc..fc60760 100644 --- a/1.4/Patches/Hediffs_PrivateParts_Animal.xml +++ b/1.4/Patches/Hediffs_PrivateParts_Animal.xml @@ -30,6 +30,28 @@ + + /Defs/rjw.HediffDef_PartBase[defName="CatVagina"] diff --git a/1.4/Textures/UI/Icon/EggRegeneration.png b/1.4/Textures/UI/Icon/EggRegeneration.png new file mode 100644 index 0000000..3c16583 Binary files /dev/null and b/1.4/Textures/UI/Icon/EggRegeneration.png differ diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/CompBiosclupterPod_EggRestorationCycle.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/CompBiosculpterPod_EggRestorationCycle.cs similarity index 100% rename from 1.4/source/RJW_Menstruation/RJW_Menstruation/CompBiosclupterPod_EggRestorationCycle.cs rename to 1.4/source/RJW_Menstruation/RJW_Menstruation/CompBiosculpterPod_EggRestorationCycle.cs 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 78638ee..32219a6 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 @@ -68,6 +68,8 @@ namespace RJW_Menstruation const int tickInterval = GenDate.TicksPerHour; const int maxImplantDelayHours = 30 * 24; const int minImplantAgeHours = 3 * 24; + const float pulloutSuccessRate = 0.8f; + const float fetishPulloutSuccessModifier = 0.25f; public CompProperties_Menstruation Props; public Stage curStage = Stage.Follicular; @@ -637,7 +639,7 @@ namespace RJW_Menstruation curStage = Stage.Pregnant; } - CumOut(); + BeforeSimulator(); if (pregnancy == null && (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())) curStage = Stage.Infertile; switch (curStage) @@ -735,10 +737,17 @@ namespace RJW_Menstruation /// /// /// - /// - public void CumIn(Pawn pawn, float volume, float fertility = 1.0f, ThingDef filthdef = null) + /// + public void CumIn(Pawn pawn, float volume, float fertility = 1.0f, bool precum = false) { if (volume <= 0) return; + 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 (Rand.Chance(successChance)) return; + } if (Pawn.HasIUD()) fertility /= 100f; float cumd = TotalCumPercent; float tmp = TotalCum + volume; @@ -750,12 +759,12 @@ namespace RJW_Menstruation { if (cum.pawn.Equals(pawn)) { - cum.MergeWithCum(volume, fertility, filthdef); + cum.MergeWithCum(volume, fertility); merged = true; } cum.DismishForce(cumoutrate); } - if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate), fertility, filthdef)); + if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate), fertility)); } else { @@ -765,17 +774,20 @@ namespace RJW_Menstruation { if (cum.pawn.Equals(pawn)) { - cum.MergeWithCum(volume, fertility, filthdef); + cum.MergeWithCum(volume, fertility); merged = true; } } - if (!merged) cums.Add(new Cum(pawn, volume, fertility, filthdef)); + if (!merged) cums.Add(new Cum(pawn, volume, fertility)); } cumd = TotalCumPercent - cumd; - Pawn.records.AddTo(VariousDefOf.AmountofCreampied, volume); - AfterCumIn(pawn); - AfterFluidIn(cumd); + if (!precum) + { + Pawn.records.AddTo(VariousDefOf.AmountofCreampied, volume); + AfterCumIn(pawn); + AfterFluidIn(cumd); + } } /// @@ -1041,6 +1053,7 @@ namespace RJW_Menstruation else if (currentIntervalHours < curStageHrs) curStageHrs = currentIntervalHours; } if (crampPain < 0) crampPain = PainRandomizer(); + InitializeExtraValues(); if (cums == null) cums = new List(); if (eggs == null) eggs = new List(); @@ -1053,6 +1066,10 @@ namespace RJW_Menstruation initError = false; } + protected virtual void InitializeExtraValues() + { + } + protected virtual float RaceCyclesPerYear() { int breedingSeasons = 0; @@ -1118,8 +1135,12 @@ namespace RJW_Menstruation ovarypower = Math.Max(0, (int)(ovarypower * multiply)); } + protected virtual void BeforeSimulator() + { + CumOut(); + } - protected void AfterSimulator() + protected virtual void AfterSimulator() { if (EggHealth < 1f) { @@ -1468,7 +1489,7 @@ namespace RJW_Menstruation protected virtual void LutealAction() { - if (curStageHrs > currentIntervalHours) + if (curStageHrs >= currentIntervalHours) { eggs.Clear(); if (EggHealth < 1f / 4f || (EggHealth < 1f / 3f && Rand.Range(0.0f, 1.0f) < 0.3f)) //skips bleeding @@ -1784,7 +1805,7 @@ namespace RJW_Menstruation GoNextStage(Stage.Pregnant); } - public void CopyCycleProperties(HediffComp_Menstruation original) + public virtual void CopyCycleProperties(HediffComp_Menstruation original) { cycleSpeed = original.cycleSpeed; cycleVariability = original.cycleVariability; diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs new file mode 100644 index 0000000..0026e89 --- /dev/null +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_PeriodicOvulator.cs @@ -0,0 +1,103 @@ +using RimWorld; +using Verse; + +namespace RJW_Menstruation +{ + public class CompProperties_PeriodicOvulator : CompProperties_Menstruation + { + public FloatRange cycleIntervalDays; // From the start of one cycle to the start of the next + + public CompProperties_PeriodicOvulator() + { + compClass = typeof(HediffComp_PeriodicOvulator); + } + } + + public class HediffComp_PeriodicOvulator : HediffComp_Menstruation + { + public int hoursToNextCycle = -100000; + public int averageCycleIntervalHours = -1; + + public new CompProperties_PeriodicOvulator Props; + + protected override void InitializeExtraValues() + { + base.InitializeExtraValues(); + Props = (CompProperties_PeriodicOvulator)props; + if (averageCycleIntervalHours < 0) + { + averageCycleIntervalHours = (int)(24f * Props.cycleIntervalDays.RandomInRange / cycleSpeed); + if (hoursToNextCycle < -50000) + hoursToNextCycle = Rand.Range(0, averageCycleIntervalHours); + // 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) + && (averageCycleIntervalHours - hoursToNextCycle) / 2 >= 24 * (Props.follicularIntervalDays + Props.lutealIntervalDays) / cycleSpeed) + curStage = Stage.Anestrus; + } + } + + protected override float RaceCyclesPerYear() + { + // Don't bother trying to work seasonal breeding into the math + // Due to the enormous variation in possible cycle gaps, cheat and base it off the individual + return averageCycleIntervalHours * cycleSpeed / (24 * 360); // cancel out their cycleSpeed from initialization to get their "normal" speed + } + + protected override void BeforeSimulator() + { + base.BeforeSimulator(); + if (hoursToNextCycle > 0) hoursToNextCycle -= Configurations.CycleAcceleration; + } + + public override void CompExposeData() + { + base.CompExposeData(); + Scribe_Values.Look(ref hoursToNextCycle, "hoursToNextCycle", hoursToNextCycle, true); + Scribe_Values.Look(ref averageCycleIntervalHours, "averageCycleIntervalHours", averageCycleIntervalHours, true); + } + + protected override void BleedingAction() + { + if (curStageHrs >= currentIntervalHours) + { + Hediff hediff = Pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_MenstrualCramp); + if (hediff != null) Pawn.health.RemoveHediff(hediff); + estrusflag = false; + GoNextStage(Stage.Anestrus); + return; + } + else + { + if (curStageHrs < currentIntervalHours / 4) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut(); + curStageHrs += Configurations.CycleAcceleration; + StayCurrentStage(); + } + } + + protected override void PregnantAction() + { + base.PregnantAction(); + if (curStage != Stage.Pregnant) + // Go halfway into the cycle + hoursToNextCycle = (int)(averageCycleIntervalHours * Rand.Range(-cycleVariability, cycleVariability)) / 2; + } + + protected override void AnestrusAction() + { + if (hoursToNextCycle <= 0) + { + hoursToNextCycle = (int)(averageCycleIntervalHours * Rand.Range(-cycleVariability, cycleVariability)); + if (IsBreedingSeason()) GoNextStage(Stage.Follicular); + return; + } + StayCurrentStage(); + } + + public override void CopyCycleProperties(HediffComp_Menstruation original) + { + base.CopyCycleProperties(original); + if (original is HediffComp_PeriodicOvulator comp) + averageCycleIntervalHours = comp.averageCycleIntervalHours; + } + } +} 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 708596c..4de0dfa 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 @@ -1,5 +1,4 @@ using HarmonyLib; -using System; using System.Linq; using RimWorld; using Verse; @@ -178,15 +177,4 @@ namespace RJW_Menstruation } } - [HarmonyPatch(typeof(CompBiosculpterPod), nameof(CompBiosculpterPod.CannotUseNowPawnCycleReason), new Type[] { typeof(Pawn), typeof(Pawn), typeof(CompBiosculpterPod_Cycle), typeof(bool) } )] - public class CannotUseNowPawnCycleReason_Patch - { - private const string eggRestorationKey = "eggRestoration"; - public static void Postfix(ref string __result, Pawn biosculptee, CompBiosculpterPod_Cycle cycle) - { - if (__result != null) return; - if(cycle.Props.key == eggRestorationKey && !biosculptee.GetMenstruationComps().Any()) - __result = Translations.CannotNoWomb; - } - } } \ No newline at end of file diff --git a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Pawn_Patch.cs b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Pawn_Patch.cs index 97cb9c4..616f591 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Pawn_Patch.cs +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/Patch/Pawn_Patch.cs @@ -1,6 +1,8 @@ using HarmonyLib; using RimWorld; +using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; using Verse; @@ -18,11 +20,7 @@ namespace RJW_Menstruation comp.Initialize(); } - HediffComp_Breast bcomp = __instance.GetBreastComp(); - if (bcomp != null) - { - bcomp.Initialize(); - } + __instance.GetBreastComp()?.Initialize(); } } @@ -111,56 +109,15 @@ namespace RJW_Menstruation } - - //Merged to RJW - //[HarmonyPatch(typeof(PawnColumnWorker_Pregnant), "GetIconFor")] - //public class PawnColumnWorker_Patch_Icon - //{ - // public static void Postfix(Pawn pawn, ref Texture2D __result) - // { - // if (pawn.IsVisiblyPregnant()) __result = ContentFinder.Get("UI/Icons/Animal/Pregnant", true); - // } - // - //} - // - //[HarmonyPatch(typeof(PawnColumnWorker_Pregnant), "GetTooltipText")] - //public class PawnColumnWorker_Patch_Tooltip - //{ - // public static bool Prefix(Pawn pawn, ref string __result) - // { - // float gestationProgress = PregnancyHelper.GetPregnancy(pawn).Severity; - // int num = (int)(pawn.RaceProps.gestationPeriodDays * 60000f); - // int numTicks = (int)(gestationProgress * (float)num); - // __result = "PregnantIconDesc".Translate(numTicks.ToStringTicksToDays("F0"), num.ToStringTicksToDays("F0")); - // return false; - // } - // - //} - // - //[HarmonyPatch(typeof(TransferableUIUtility), "DoExtraAnimalIcons")] - //public class TransferableUIUtility_Patch_Icon - //{ - // //private static readonly Texture2D PregnantIcon = ContentFinder.Get("UI/Icons/Animal/Pregnant", true); - // - // - // - // public static void Postfix(Transferable trad, Rect rect, ref float curX, Texture2D ___PregnantIcon) - // { - // Pawn pawn = trad.AnyThing as Pawn; - // if (pawn?.health?.hediffSet != null && pawn.IsVisiblyPregnant()) - // { - // Rect rect3 = new Rect(curX - 24f, (rect.height - 24f) / 2f, 24f, 24f); - // curX -= 24f; - // if (Mouse.IsOver(rect3)) - // { - // TooltipHandler.TipRegion(rect3, PawnColumnWorker_Pregnant.GetTooltipText(pawn)); - // } - // GUI.DrawTexture(rect3, ___PregnantIcon); - // } - // } - //} - - - - + [HarmonyPatch(typeof(CompBiosculpterPod), nameof(CompBiosculpterPod.CannotUseNowPawnCycleReason), new Type[] { typeof(Pawn), typeof(Pawn), typeof(CompBiosculpterPod_Cycle), typeof(bool) })] + public class CannotUseNowPawnCycleReason_Patch + { + private const string eggRestorationKey = "eggRestoration"; + public static void Postfix(ref string __result, Pawn biosculptee, CompBiosculpterPod_Cycle cycle) + { + if (__result != null) return; + if (cycle.Props.key == eggRestorationKey && !biosculptee.GetMenstruationComps().Any()) + __result = Translations.CannotNoWomb; + } + } } 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 9032766..a32877c 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 @@ -43,7 +43,7 @@ namespace RJW_Menstruation } else if (Genital_Helper.has_ovipositorM(pawn, pawnparts)) { - comp.CumIn(pawn, Rand.Range(0.75f, 4.5f) * pawn.BodySize, partner.SterileGenes() ? 0.0f : 1.0f); + comp.CumIn(pawn, Rand.Range(0.75f, 4.5f) * pawn.BodySize, pawn.SterileGenes() ? 0.0f : 1.0f); } else comp.CumIn(pawn, pawn.GetCumVolume(pawnparts), 0); @@ -70,7 +70,7 @@ namespace RJW_Menstruation /// /// /// Interaction can result in pregnancy - private static bool InteractionCanCausePregnancy(SexProps props) + public static bool InteractionCanCausePregnancy(SexProps props) { InteractionWithExtension interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(props.dictionaryKey); @@ -331,6 +331,34 @@ namespace RJW_Menstruation } } + [HarmonyPatch(typeof(JobDriver_Sex), nameof(JobDriver_Sex.SexTick))] + public static class SexTick_Patch + { + private const float fertilePrecummersPercentage = 0.33f; + private const float precumRatio = 0.1f; // Relative to ejaculation volume + private const float precumFertility = 0.5f; + private const float expectedDurationTicks = 2000f * (0.9f - 0.5f) / 2; + + public static void Postfix(JobDriver_Sex __instance, Pawn pawn, Thing target) + { + if (!pawn.IsHashIntervalTick(__instance.ticks_between_thrusts)) return; + xxx.rjwSextype sextype = __instance.Sexprops.sexType; + if (!(target is Pawn partner) || pawn == partner) return; + if (sextype != xxx.rjwSextype.Vaginal && sextype != xxx.rjwSextype.DoublePenetration) return; + if (__instance.Sexprops.usedCondom) return; + if (!Impregnate_Patch.InteractionCanCausePregnancy(__instance.Sexprops)) return; + + // Archotech penises have more control. Or something. + CompHediffBodyPart penisComp = pawn.GetGenitalsList()?.Find(genital => (genital as Hediff_PartBaseNatural)?.def.defName.ToLower().Contains("penis") ?? false)?.TryGetComp(); + if (penisComp == null || Rand.ChanceSeeded(1.0f - fertilePrecummersPercentage, Gen.HashOffset(penisComp.parent.loadID))) return; + HediffComp_Menstruation vaginaComp = partner.GetRandomMenstruationComp(); + if (vaginaComp == null) return; + + float precumAmount = pawn.GetCumVolume(penisComp) * precumRatio * __instance.ticks_between_thrusts / expectedDurationTicks; + vaginaComp.CumIn(pawn, precumAmount, pawn.SterileGenes() ? 0.0f : precumFertility * pawn.health.capacities.GetLevel(xxx.reproduction), true); + } + } + [HarmonyPatch(typeof(CompHediffBodyPart), nameof(CompHediffBodyPart.updatesize))] public static class Updatesize_Patch { 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 1a8b5e3..5dd5cdc 100644 --- a/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj +++ b/1.4/source/RJW_Menstruation/RJW_Menstruation/RJW_Menstruation.csproj @@ -68,6 +68,7 @@ + diff --git a/changelogs.txt b/changelogs.txt index e67ba4d..146583c 100644 --- a/changelogs.txt +++ b/changelogs.txt @@ -1,6 +1,10 @@ Version 1.0.8.5 - - Added biosculptor recipe to restore 1 year's worth of eggs. + - Added biosculpter recipe to restore 1 year's worth of eggs, with icon by DestinyPlayer. + - Vaginal sex with the "avoid pregnancy" relation will (usually) pull out prevent cum from entering the womb if there's a risk of pregnancy. + - Impregnation fetishists are significantly less likely to pull out. + - Some males will release small amounts of semen into a womb during vaginal sex before their actual ejaculation. - Babies born from multiple pregnancy will properly produce the prompt to name them. + - Experimental "periodic ovulator" cycle type, currently not used. See Patches/Hediffs_Private_Parts_Animal.xml. Version 1.0.8.4 - Fix Biotech xenotype inheritance for single-child pregnancies.