using RimWorld; using rjw; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Verse; namespace RJW_Menstruation { public static class MenstruationUtility { [Obsolete("This method is obsolete. Use GetMenstruationComps or a related function instead", true)] public static HediffComp_Menstruation GetMenstruationComp(this Pawn pawn) { return pawn.GetFirstMenstruationComp(); } public static IEnumerable GetMenstruationComps(this Pawn pawn) { List hedifflist = pawn.health.hediffSet.hediffs.FindAll(h => VariousDefOf.AllVaginas.Contains(h.def)); if (hedifflist == null) yield break; foreach (Hediff hediff in hedifflist) { HediffComp_Menstruation result = hediff.TryGetComp(); if (result != null) yield return result; } } public static HediffComp_Menstruation GetFirstMenstruationComp(this Pawn pawn) { return pawn.GetMenstruationComps().FirstOrDefault(); } public static HediffComp_Menstruation GetRandomMenstruationComp(this Pawn pawn) { return pawn.GetMenstruationComps().RandomElementWithFallback(); } public static HediffComp_Menstruation GetFertileMenstruationComp(this Pawn pawn) { List comps = pawn.GetMenstruationComps().ToList(); return comps.Where(c => c.IsDangerDay).RandomElementWithFallback() ?? comps.RandomElementWithFallback(); } public static HediffComp_Menstruation GetMenstruationCompFromVagina(this Hediff vagina) { if (VariousDefOf.AllVaginas.Contains(vagina?.def)) { return vagina.TryGetComp(); } return null; } public static HediffComp_Menstruation GetMenstruationCompFromPregnancy(this Hediff pregnancy) { return pregnancy?.pawn.GetMenstruationComps().FirstOrDefault(comp => comp.Pregnancy == pregnancy); } public static HediffComp_Anus GetAnusComp(this Pawn pawn) { return pawn.health.hediffSet.hediffs.FirstOrDefault((Hediff h) => VariousDefOf.AllAnuses.Contains(h.def))?.TryGetComp(); } [Obsolete("This method is obsolete and can cause ambiguity. Use GetMenstruationCompFromVagina or GetMenstruationCompFromPregnancy instead.", true)] public static HediffComp_Menstruation GetMenstruationComp(Hediff hediff) { switch (hediff) { case Hediff_BasePregnancy rjwPreg: return rjwPreg.GetMenstruationCompFromPregnancy(); case Hediff_Pregnant vanillaPreg: return vanillaPreg.GetMenstruationCompFromPregnancy(); case Hediff_Labor vanillaLabor: return vanillaLabor.GetMenstruationCompFromPregnancy(); case Hediff_LaborPushing vanillaLaborPushing: return vanillaLaborPushing.GetMenstruationCompFromPregnancy(); case Hediff_PartBaseNatural rjwNatrual: return rjwNatrual.GetMenstruationCompFromVagina(); case Hediff_PartBaseArtifical rjwArtificial: return rjwArtificial.GetMenstruationCompFromVagina(); default: Log.Warning("Obsolete GetMenstruationComp called with unknown hediff. Ensure your submods are up to date."); return null; } } public static HediffComp_Anus GetAnusComp(this Hediff hediff) { if (hediff is Hediff_PartBaseNatural || hediff is Hediff_PartBaseArtifical) { return hediff.TryGetComp(); } return null; } public static float GetFertilityChance(this HediffComp_Menstruation comp) { return 1.0f - Mathf.Pow(1.0f - Configurations.FertilizeChance, comp.TotalFertCum * comp.Props.basefertilizationChanceFactor); } public static Texture2D GetPregnancyIcon(this HediffComp_Menstruation comp, Hediff hediff) { float gestationProgress = comp.StageProgress; ThingDef babydef; int babycount; HediffComp_PregeneratedBabies babiescomp = hediff?.TryGetComp(); if (hediff is Hediff_MechanoidPregnancy) { babydef = VariousDefOf.Scyther; babycount = 1; } else if (hediff is Hediff_BasePregnancy preg) { babydef = preg.babies?.FirstOrDefault()?.def ?? ThingDefOf.Human; babycount = preg.babies?.Count ?? 1; } else if (babiescomp?.HasBaby ?? false) { babydef = babiescomp.babies.First().def; babycount = babiescomp.babies.Count; } else { babydef = comp.Pawn.def; babycount = 1; } string fetustex = babydef.GetModExtension()?.fetusTexPath ?? "Fetus/Fetus_Default"; string icon; if (gestationProgress < 0.2f) icon = comp.WombTex + "_Implanted"; else if (gestationProgress < 0.4f) icon = fetustex + "00"; else if (gestationProgress < 0.5f) icon = fetustex + "01"; else if (gestationProgress < 0.6f) icon = fetustex + "02"; else if (gestationProgress < 0.7f) icon = fetustex + "03"; else if (gestationProgress < 0.8f) icon = fetustex + "04"; else icon = fetustex + "05"; return TryGetTwinsIcon(icon, babycount) ?? ContentFinder.Get(icon, true); } public static Texture2D TryGetTwinsIcon(string path, int babycount) { for (int i = babycount; i > 1; i--) { Texture2D result = ContentFinder.Get(path + "_Multiplet_" + i, false); if (result != null) return result; } return null; } public static Texture2D GetCumIcon(this HediffComp_Menstruation comp) { Pawn pawn = comp.Pawn; List insectEggs = new List(); comp.Pawn.health.hediffSet.GetHediffs(ref insectEggs); if (insectEggs?.Sum(hediff => hediff.eggssize) > 1.0f) return null; // same logic as "Stuffed" in GetInsectEggedIcon string icon = comp.WombTex; float cumpercent = comp.TotalCumPercent; if (cumpercent < 0.001f) return ContentFinder.Get("Womb/Empty", true); else if (cumpercent < 0.01f) icon += "_Cum_00"; else if (cumpercent < 0.05f) icon += "_Cum_01"; else if (cumpercent < 0.11f) icon += "_Cum_02"; else if (cumpercent < 0.17f) icon += "_Cum_03"; else if (cumpercent < 0.23f) icon += "_Cum_04"; else if (cumpercent < 0.29f) icon += "_Cum_05"; else if (cumpercent < 0.35f) icon += "_Cum_06"; else if (cumpercent < 0.41f) icon += "_Cum_07"; else if (cumpercent < 0.47f) icon += "_Cum_08"; else if (cumpercent < 0.53f) icon += "_Cum_09"; else if (cumpercent < 0.59f) icon += "_Cum_10"; else if (cumpercent < 0.65f) icon += "_Cum_11"; else if (cumpercent < 0.71f) icon += "_Cum_12"; else if (cumpercent < 0.77f) icon += "_Cum_13"; else if (cumpercent < 0.83f) icon += "_Cum_14"; else if (cumpercent < 0.89f) icon += "_Cum_15"; else if (cumpercent < 0.95f) icon += "_Cum_16"; else icon += "_Cum_17"; Texture2D cumtex = ContentFinder.Get(icon, true); return cumtex; } public static Texture2D GetInsectEggedIcon(this HediffComp_Menstruation comp) { List hediffs = new List(); comp.Pawn.health.hediffSet.GetHediffs(ref hediffs); if (hediffs.NullOrEmpty()) return null; string path = "Womb/Insect_Egged/Womb_Egged_"; float sumSize = hediffs.Sum(hediff => hediff.eggssize); if (sumSize > 1.0f) { path += "Stuffed_"; if (sumSize < 1.0f) path += "00"; else if (sumSize < 2.0f) path += "01"; else if (sumSize < 2.5f) path += "02"; else if (sumSize < 3.0f) path += "03"; else path += "04"; } else { if(hediffs.Max(hediff => hediff.eggssize) > 0.3f) // The threshold for "large egg" in the debug { path += "L"; if (hediffs.Count == 1) path += "00"; else if (hediffs.Count == 2) path += "01"; else path += "02"; } else { path += "S"; if (hediffs.Count == 1) path += "00"; else if (hediffs.Count == 2) path += "01"; else if (hediffs.Count == 3) path += "02"; else if (hediffs.Count == 4) path += "03"; else path += "04"; } } return ContentFinder.Get(path); } public static Texture2D GetWombIcon(this HediffComp_Menstruation comp) { Texture2D wombtex = comp.GetInsectEggedIcon(); if (wombtex != null) return wombtex; string icon = comp.WombTex; HediffComp_Menstruation.Stage stage = comp.curStage; if (stage == HediffComp_Menstruation.Stage.Bleeding) icon += "_Bleeding"; wombtex = ContentFinder.Get(icon, true); return wombtex; } public static Texture2D GetOvaryIcon(this HediffComp_Menstruation comp) { const float ovaryChanceToShow_01 = 0.2f; const float ovaryChanceToShow_02 = 0.8f; float ovulatoryProgress; bool isInduced = comp is HediffComp_InducedOvulator; if (comp.curStage == HediffComp_Menstruation.Stage.Follicular && isInduced && comp.Pawn.jobs.curDriver is JobDriver_Sex job && job.Sexprops != null && !UsingCondom(comp.Pawn, job.Partner) && (job.Sexprops.sexType == xxx.rjwSextype.Vaginal || job.Sexprops.sexType == xxx.rjwSextype.DoublePenetration)) ovulatoryProgress = 0.0f; else if (comp.curStage == HediffComp_Menstruation.Stage.Ovulatory) ovulatoryProgress = isInduced ? Mathf.Max(ovaryChanceToShow_01, comp.StageProgessNextUpdate) : comp.StageProgessNextUpdate; // else if (comp.curStage == HediffComp_Menstruation.Stage.Luteal && comp.IsEggExist) return ContentFinder.Get("Ovaries/Ovary_02", true); else return ContentFinder.Get("Womb/Empty", true); float combinedAppearance = ovulatoryProgress * comp.OvulationChance; if (combinedAppearance >= ovaryChanceToShow_02 && comp.OvulationChance >= 1.0f) return ContentFinder.Get("Ovaries/Ovary_02", true); else if (combinedAppearance >= ovaryChanceToShow_01) return ContentFinder.Get("Ovaries/Ovary_01", true); else return ContentFinder.Get("Ovaries/Ovary_00", true); } public static Texture2D GetEggIcon(this HediffComp_Menstruation comp, bool includeOvary) { switch (comp.CurrentVisibleStage) { case HediffComp_Menstruation.Stage.Follicular: case HediffComp_Menstruation.Stage.Ovulatory: if (!includeOvary) break; else return GetOvaryIcon(comp); case HediffComp_Menstruation.Stage.Luteal: if (!comp.IsEggExist) break; int fertTime = comp.EggFertilizedTime; if (fertTime >= 0) { if (fertTime <= GenDate.TicksPerHour * Configurations.CycleAcceleration) return ContentFinder.Get("Eggs/Egg_Fertilizing02", true); else if (fertTime <= 18 * GenDate.TicksPerHour) return ContentFinder.Get("Eggs/Egg_Fertilized00", true); else if (fertTime <= 54 * GenDate.TicksPerHour) return ContentFinder.Get("Eggs/Egg_Fertilized01", true); else return ContentFinder.Get("Eggs/Egg_Fertilized02", true); } else if (includeOvary && comp.curStageTicks <= comp.Props.ovulationIntervalHours * 0.4f * GenDate.TicksPerHour) // Total about as long as it spent in Ovary_01 { return ContentFinder.Get("Ovaries/Ovary_02", true); } else if (comp.IsEggFertilizing) { if (comp.GetFertilityChance() < 0.5f) return ContentFinder.Get("Eggs/Egg_Fertilizing00", true); else return ContentFinder.Get("Eggs/Egg_Fertilizing01", true); } else return ContentFinder.Get("Eggs/Egg", true); case HediffComp_Menstruation.Stage.Pregnant: if (comp.Pregnancy is Hediff_MechanoidPregnancy) break; else if (comp.GetPregnancyProgress() < 0.2f) return ContentFinder.Get("Eggs/Egg_Implanted00", true); else break; } return ContentFinder.Get("Womb/Empty", true); } public static void DrawEggOverlay(this HediffComp_Menstruation comp, Rect wombRect, bool includeOvary) { Rect rect = new Rect(wombRect.xMax - wombRect.width / 3, wombRect.y, wombRect.width / 3, wombRect.width / 3); GUI.color = Color.white; GUI.DrawTexture(rect, comp.GetEggIcon(includeOvary), ScaleMode.ScaleToFit); } public static Texture2D GetGenitalIcon(this Pawn pawn, HediffComp_Menstruation comp) { Hediff hediff = comp?.parent; if (hediff == null) return ContentFinder.Get("Genitals/Vagina00", true); //HediffComp_Menstruation comp = hediff.GetMenstruationComp(); string icon; float severity = hediff.Severity; if (comp != null) icon = comp.VagTex; else icon = "Genitals/Vagina"; if (severity < 0.20f) icon += "00"; //micro else if (severity < 0.30f) icon += "01"; //tight else if (severity < 0.40f) icon += "02"; //tight else if (severity < 0.47f) icon += "03"; //average else if (severity < 0.53f) icon += "04"; //average else if (severity < 0.60f) icon += "05"; //average else if (severity < 0.70f) icon += "06"; //accomodating else if (severity < 0.80f) icon += "07"; //accomodating else if (severity < 0.87f) icon += "08"; //cavernous else if (severity < 0.94f) icon += "09"; //cavernous else if (severity < 1.01f) icon += "10"; //cavernous else icon += "11"; //abyssal return ContentFinder.Get(icon, true); } 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.Get("Genitals/Anal00", true); string icon = ((CompProperties_Anus)hediff.GetAnusComp()?.props)?.analTex ?? "Genitals/Anal"; float 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 else if (severity < 0.80f) icon += "03"; //accomodating else if (severity < 1.01f) icon += "04"; //cavernous else icon += "05"; //abyssal return ContentFinder.Get(icon, true); } public static float GestationHours(this Hediff hediff) { if (hediff == null) { Log.Error("Tried to get gestation length without a pregnancy."); return 1f; } else if (hediff is Hediff_BasePregnancy rjw_preg) return (rjw_preg.p_end_tick - rjw_preg.p_start_tick) / GenDate.TicksPerHour; // TODO: Biotech pregnancy else return hediff.pawn.RaceProps.gestationPeriodDays * GenDate.HoursPerDay; } public static float RandomVariabilityPercent(int recursion = 0) { // Humans, in days const float mean = 1.635f; const float stddev = 0.9138f; const float lambda = 0.234f; if (recursion >= 10) return mean / (28 * 2); float variability = Rand.Gaussian(mean, stddev) - Mathf.Log(Rand.Value) / lambda; variability /= 28 * 2; // Convert to percentage if (variability < 0 || variability > 0.35f) return RandomVariabilityPercent(recursion + 1); // ~2% chance, about the limit on how far variability can go before things start to break else return variability; } public static bool ShouldCycle(this Pawn pawn) { if (!Configurations.EnableAnimalCycle && pawn.IsAnimal()) return false; if (pawn.GetComp() != null) return false; if (pawn.RaceHasOviPregnancy()) return false; if (ModsConfig.BiotechActive && pawn.genes != null && pawn.genes.GenesListForReading.Select(gene => gene.def).Intersect(VariousDefOf.EggLayerGenes).Any()) return false; return true; } public static bool IsInEstrus(this Pawn pawn, bool visible = true) { if (pawn.Dead) return false; return pawn.health?.hediffSet?.HasHediff(visible ? VariousDefOf.Hediff_Estrus : VariousDefOf.Hediff_Estrus_Concealed) ?? false; } public static HediffComp_Menstruation.EstrusLevel HighestEstrus(this Pawn pawn) { HediffComp_Menstruation.EstrusLevel res = HediffComp_Menstruation.EstrusLevel.None; foreach(HediffComp_Menstruation comp in pawn.GetMenstruationComps()) { switch (comp.GetEstrusLevel()) { case HediffComp_Menstruation.EstrusLevel.None: break; case HediffComp_Menstruation.EstrusLevel.Concealed: res = HediffComp_Menstruation.EstrusLevel.Concealed; break; case HediffComp_Menstruation.EstrusLevel.Visible: return HediffComp_Menstruation.EstrusLevel.Visible; } } return res; } public static bool HasIUD(this Pawn pawn) { if (pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) return true; if (ModsConfig.BiotechActive && pawn.health.hediffSet.HasHediff(HediffDefOf.ImplantedIUD)) return true; return false; } public static bool IsProPregnancy(this Pawn pawn, out Precept precept) { precept = null; Ideo ideo = pawn.Ideo; if (ideo != null) { precept = ideo.GetPrecept(VariousDefOf.Pregnancy_Required) ?? ideo.GetPrecept(VariousDefOf.Pregnancy_Holy) ?? ideo.GetPrecept(VariousDefOf.Pregnancy_Elevated); } if (precept != null) return true; else return pawn.IsBreeder() || pawn.HasImpregnationFetish(); } public static float DamagePants(this Pawn pawn, float fluidAmount) { if (pawn.apparel == null) return 0; Apparel pants = null; foreach(Apparel apparel in pawn.apparel.WornApparel.Where(app => app.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))) { if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin) { pants = apparel; break; } else if (pants == null || apparel.def.apparel.LastLayer == ApparelLayerDefOf.Middle) // Either grab whatever's available or reassign the pants from shell to a middle pants = apparel; // Pants are middle and this is a shell else continue; } if (pants == null) return 0; const float HPPerMl = 0.5f; DamageWorker.DamageResult damage = pants.TakeDamage(new DamageInfo(DamageDefOf.Deterioration, fluidAmount * HPPerMl, spawnFilth: false)); if (pants.Destroyed && PawnUtility.ShouldSendNotificationAbout(pawn) && !pawn.Dead) Messages.Message("MessageWornApparelDeterioratedAway".Translate(GenLabel.ThingLabel(pants.def, pants.Stuff), pawn).CapitalizeFirst(), pawn, MessageTypeDefOf.NegativeEvent); return damage.totalDamageDealt; } public static bool UsingCondom(Pawn pawn, Pawn partner) { return ((pawn?.jobs?.curDriver as JobDriver_Sex)?.Sexprops.usedCondom ?? false) || ((partner?.jobs?.curDriver as JobDriver_Sex)?.Sexprops.usedCondom ?? false); } } }