mirror of
https://gitgud.io/lutepickle/rjw_menstruation.git
synced 2024-08-14 22:46:52 +00:00
1180 lines
47 KiB
C#
1180 lines
47 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Verse;
|
|
using RimWorld;
|
|
using HugsLib;
|
|
using rjw;
|
|
using UnityEngine;
|
|
|
|
namespace RJW_Menstruation
|
|
{
|
|
public class CompProperties_Menstruation : HediffCompProperties
|
|
{
|
|
public float maxCumCapacity; // ml
|
|
public float baseImplantationChanceFactor;
|
|
public float basefertilizationChanceFactor;
|
|
public float deviationFactor;
|
|
public int folicularIntervalDays = 14; //before ovulation including beginning of bleeding
|
|
public int lutealIntervalDays = 14; //after ovulation until bleeding
|
|
public int bleedingIntervalDays = 6; //must be less than folicularIntervalDays
|
|
public int recoveryIntervalDays = 10; //additional infertile days after gave birth
|
|
public int eggLifespanDays = 2; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
|
|
public string wombTex = "Womb/Womb"; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
|
|
public string vagTex = "Genitals/Vagina"; //fertiledays = ovaluationday - spermlifespan ~ ovaluationday + egglifespanday
|
|
public bool infertile = false;
|
|
public int ovaryPower = 600000000; // default: almost unlimited ovulation
|
|
public CompProperties_Menstruation()
|
|
{
|
|
|
|
compClass = typeof(HediffComp_Menstruation);
|
|
}
|
|
}
|
|
|
|
|
|
public class CompProperties_Anus : HediffCompProperties
|
|
{
|
|
public string analTex = "Genitals/Anal";
|
|
|
|
public CompProperties_Anus()
|
|
{
|
|
compClass = typeof(HediffComp_Anus);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class HediffComp_Menstruation : HediffComp
|
|
{
|
|
const float minmakefilthvalue = 1.0f;
|
|
const int ovarypowerthreshold = 72;
|
|
|
|
public static readonly int tickInterval = 2500; // an hour
|
|
public CompProperties_Menstruation Props;
|
|
public Stage curStage = Stage.Follicular;
|
|
public int curStageHrs = 0;
|
|
public Action actionref;
|
|
public bool loaded = false;
|
|
public int ovarypower = -100000;
|
|
|
|
|
|
public enum Stage
|
|
{
|
|
Follicular,
|
|
Ovulatory,
|
|
Luteal,
|
|
Bleeding,
|
|
Fertilized, //Obsoleted
|
|
Pregnant,
|
|
Recover,
|
|
None,
|
|
Young,
|
|
ClimactericFollicular,
|
|
ClimactericLuteal,
|
|
ClimactericBleeding,
|
|
}
|
|
|
|
private List<Cum> cums;
|
|
private List<Egg> eggs;
|
|
private int follicularIntervalhours = -1;
|
|
private int lutealIntervalhours = -1;
|
|
private int bleedingIntervalhours = -1;
|
|
private int recoveryIntervalhours = -1;
|
|
private float crampPain= -1;
|
|
|
|
public float TotalCum
|
|
{
|
|
get
|
|
{
|
|
float res = 0;
|
|
if (cums.NullOrEmpty()) return 0;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
res += cum.volume;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
public float TotalFertCum
|
|
{
|
|
get
|
|
{
|
|
float res = 0;
|
|
if (cums.NullOrEmpty()) return 0;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
if (!cum.notcum) res += cum.fertvolume;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
public float TotalCumPercent
|
|
{
|
|
get
|
|
{
|
|
float res = 0;
|
|
if (cums.NullOrEmpty()) return 0;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
res += cum.volume;
|
|
}
|
|
return res/ Props.maxCumCapacity;
|
|
}
|
|
}
|
|
public float CumCapacity
|
|
{
|
|
get
|
|
{
|
|
float res = Props.maxCumCapacity * parent.pawn.BodySize;
|
|
if (curStage == Stage.Pregnant) res *= 0.2f;
|
|
return res;
|
|
}
|
|
}
|
|
public float CumInFactor
|
|
{
|
|
get
|
|
{
|
|
float res = 1.0f;
|
|
if (parent.pawn.health.hediffSet.HasHediff(VariousDefOf.RJW_IUD)) res = 0.001f;
|
|
return res;
|
|
}
|
|
}
|
|
//make follicular interval into half and double egg lifespan
|
|
public float CycleFactor
|
|
{
|
|
get
|
|
{
|
|
if (xxx.has_quirk(parent.pawn, "Breeder")) return 0.5f;
|
|
|
|
return 1.0f;
|
|
}
|
|
}
|
|
//effect on implant chance
|
|
public float ImplantFactor
|
|
{
|
|
get
|
|
{
|
|
float factor = 1.0f;
|
|
if (parent.pawn.Has(Quirk.Breeder)) factor = 10.0f;
|
|
if (xxx.is_animal(parent.pawn)) factor *= RJWPregnancySettings.animal_impregnation_chance/100f;
|
|
else factor *= RJWPregnancySettings.humanlike_impregnation_chance/100f;
|
|
return parent.pawn.health.capacities.GetLevel(xxx.reproduction) * factor;
|
|
}
|
|
}
|
|
public IEnumerable<string> GetCumsInfo
|
|
{
|
|
get
|
|
{
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (!cum.notcum) yield return String.Format(cum.pawn?.Label + ": {0:0.##}ml", cum.volume);
|
|
else yield return String.Format(cum.notcumLabel + ": {0:0.##}ml", cum.volume);
|
|
}
|
|
else yield return Translations.Info_noCum;
|
|
}
|
|
}
|
|
public Color GetCumMixtureColor
|
|
{
|
|
get
|
|
{
|
|
Color mixedcolor = Color.white;
|
|
|
|
if (!cums.NullOrEmpty())
|
|
{
|
|
float mixedsofar = 0;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
mixedcolor = Color.LerpUnclamped(mixedcolor, cum.color, cum.volume/(mixedsofar + cum.volume));
|
|
mixedsofar += cum.volume;
|
|
}
|
|
}
|
|
return mixedcolor;
|
|
}
|
|
}
|
|
public string GetCurStageLabel
|
|
{
|
|
get
|
|
{
|
|
switch (curStage)
|
|
{
|
|
case Stage.Follicular:
|
|
return Translations.Stage_Follicular;
|
|
case Stage.Ovulatory:
|
|
return Translations.Stage_Ovulatory;
|
|
case Stage.Luteal:
|
|
return Translations.Stage_Luteal;
|
|
case Stage.Bleeding:
|
|
return Translations.Stage_Bleeding;
|
|
case Stage.Fertilized:
|
|
return Translations.Stage_Fertilized;
|
|
case Stage.Pregnant:
|
|
if (Configurations.InfoDetail == Configurations.DetailLevel.All || (PregnancyHelper.GetPregnancy(parent.pawn)?.Visible ?? false)) return Translations.Stage_Pregnant;
|
|
else return Translations.Stage_Luteal;
|
|
case Stage.Recover:
|
|
return Translations.Stage_Recover;
|
|
case Stage.None:
|
|
case Stage.Young:
|
|
return Translations.Stage_None;
|
|
case Stage.ClimactericFollicular:
|
|
return Translations.Stage_Follicular + " - " + Translations.Stage_Climacteric;
|
|
case Stage.ClimactericLuteal:
|
|
return Translations.Stage_Luteal + " - " + Translations.Stage_Climacteric;
|
|
case Stage.ClimactericBleeding:
|
|
return Translations.Stage_Bleeding + " - " + Translations.Stage_Climacteric;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public string GetFertilizingInfo
|
|
{
|
|
get
|
|
{
|
|
string res = "";
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
if (cums.NullOrEmpty()) return eggs.Count + " " + Translations.Dialog_WombInfo07;
|
|
else
|
|
{
|
|
int fertilized = 0;
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
if (egg.fertilized) fertilized++;
|
|
}
|
|
if (fertilized != 0) res += fertilized + " " + Translations.Dialog_WombInfo05;
|
|
if (fertilized != 0 && eggs.Count - fertilized != 0) res += ", ";
|
|
if (eggs.Count - fertilized != 0) res += eggs.Count - fertilized + " " + Translations.Dialog_WombInfo06;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
public bool GetEggFertilizing
|
|
{
|
|
get
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
if (!cums.NullOrEmpty()) foreach(Cum cum in cums)
|
|
{
|
|
if (cum.fertvolume > 0) return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
else return false;
|
|
}
|
|
}
|
|
public bool GetFertilization
|
|
{
|
|
get
|
|
{
|
|
if (!eggs.NullOrEmpty()) foreach(Egg egg in eggs)
|
|
{
|
|
if (egg.fertilized) return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
public bool GetEgg
|
|
{
|
|
get
|
|
{
|
|
return !eggs.NullOrEmpty();
|
|
}
|
|
}
|
|
|
|
public int GetNumofEggs
|
|
{
|
|
get
|
|
{
|
|
if (eggs.NullOrEmpty()) return 0;
|
|
else return eggs.Count;
|
|
}
|
|
}
|
|
|
|
public override void CompExposeData()
|
|
{
|
|
base.CompExposeData();
|
|
Scribe_Collections.Look(ref cums, saveDestroyedThings: true, label: "cums", lookMode: LookMode.Deep, ctorArgs: new object[0]);
|
|
Scribe_Collections.Look(ref eggs, saveDestroyedThings: true, label: "eggs", lookMode: LookMode.Deep, ctorArgs: new object[0]);
|
|
Scribe_Values.Look(ref curStage, "curStage", curStage, true);
|
|
Scribe_Values.Look(ref curStageHrs, "curStageHrs", curStageHrs, true);
|
|
Scribe_Values.Look(ref follicularIntervalhours, "follicularIntervalhours", follicularIntervalhours, true);
|
|
Scribe_Values.Look(ref lutealIntervalhours, "lutealIntervalhours", lutealIntervalhours, true);
|
|
Scribe_Values.Look(ref bleedingIntervalhours, "bleedingIntervalhours", bleedingIntervalhours, true);
|
|
Scribe_Values.Look(ref recoveryIntervalhours, "recoveryIntervalhours", recoveryIntervalhours, true);
|
|
Scribe_Values.Look(ref crampPain, "crampPain", crampPain, true);
|
|
Scribe_Values.Look(ref ovarypower, "ovarypower", ovarypower, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
public override void CompPostPostAdd(DamageInfo? dinfo)
|
|
{
|
|
if (!loaded) Initialize();
|
|
}
|
|
|
|
public override void CompPostTick(ref float severityAdjustment)
|
|
{
|
|
//initializer moved to SpawnSetup
|
|
//if (!loaded)
|
|
//{
|
|
// Initialize();
|
|
//}
|
|
}
|
|
|
|
public override void CompPostPostRemoved()
|
|
{
|
|
|
|
HugsLibController.Instance.TickDelayScheduler.TryUnscheduleCallback(actionref);
|
|
ModLog.Message(parent.pawn.Label + "tick scheduler removed");
|
|
base.CompPostPostRemoved();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Get fluid in womb that not a cum
|
|
/// </summary>
|
|
/// <param name="notcumlabel"></param>
|
|
/// <returns></returns>
|
|
public Cum GetNotCum(string notcumlabel)
|
|
{
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.notcum && cum.notcumLabel.Equals(notcumlabel)) return cum;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get pawn's cum in womb
|
|
/// </summary>
|
|
/// <param name="pawn"></param>
|
|
/// <returns></returns>
|
|
public Cum GetCum(Pawn pawn)
|
|
{
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (!cum.notcum && cum.pawn.Equals(pawn)) return cum;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inject pawn's cum into womb
|
|
/// </summary>
|
|
/// <param name="pawn"></param>
|
|
/// <param name="injectedvolume"></param>
|
|
/// <param name="fertility"></param>
|
|
/// <param name="filthdef"></param>
|
|
public void CumIn(Pawn pawn, float injectedvolume, float fertility = 1.0f, ThingDef filthdef = null)
|
|
{
|
|
float volume = injectedvolume * CumInFactor;
|
|
float tmp = TotalCum + volume;
|
|
if (tmp > Props.maxCumCapacity)
|
|
{
|
|
float cumoutrate = 1 - (Props.maxCumCapacity / tmp);
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.pawn.Equals(pawn))
|
|
{
|
|
cum.volume += volume;
|
|
cum.fertvolume += volume;
|
|
cum.FilthDef = filthdef;
|
|
merged = true;
|
|
}
|
|
cum.volume *= 1 - cumoutrate;
|
|
cum.fertvolume *= 1 - cumoutrate;
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate),fertility, filthdef));
|
|
}
|
|
else
|
|
{
|
|
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.pawn.Equals(pawn))
|
|
{
|
|
cum.volume += volume;
|
|
cum.fertvolume += volume;
|
|
cum.FilthDef = filthdef;
|
|
merged = true;
|
|
}
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume, fertility, filthdef));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inject pawn's fluid into womb
|
|
/// </summary>
|
|
/// <param name="pawn"></param>
|
|
/// <param name="volume"></param>
|
|
/// <param name="notcumlabel"></param>
|
|
/// <param name="decayresist"></param>
|
|
/// <param name="filthdef"></param>
|
|
public void CumIn(Pawn pawn, float volume, string notcumlabel, float decayresist = 0, ThingDef filthdef = null)
|
|
{
|
|
float tmp = TotalCum + volume;
|
|
if (tmp > Props.maxCumCapacity)
|
|
{
|
|
float cumoutrate = 1 - (Props.maxCumCapacity / tmp);
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.notcum && cum.pawn.Equals(pawn) && cum.notcumLabel.Equals(notcumlabel))
|
|
{
|
|
cum.volume += volume;
|
|
cum.decayresist = decayresist;
|
|
cum.fertvolume = 0;
|
|
cum.FilthDef = filthdef;
|
|
merged = true;
|
|
}
|
|
cum.volume *= 1 - cumoutrate;
|
|
cum.fertvolume *= 1 - cumoutrate;
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume * (1 - cumoutrate), notcumlabel,decayresist, filthdef));
|
|
}
|
|
else
|
|
{
|
|
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.notcum && cum.pawn.Equals(pawn) && cum.notcumLabel.Equals(notcumlabel))
|
|
{
|
|
cum.volume += volume;
|
|
cum.decayresist = decayresist;
|
|
cum.fertvolume = 0;
|
|
cum.FilthDef = filthdef;
|
|
merged = true;
|
|
}
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume, notcumlabel,decayresist, filthdef));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excrete cums in womb naturally
|
|
/// </summary>
|
|
public void CumOut()
|
|
{
|
|
if (cums.NullOrEmpty()) return;
|
|
List<Cum> removecums = new List<Cum>();
|
|
foreach(Cum cum in cums)
|
|
{
|
|
float vd = cum.volume;
|
|
cum.volume *= Math.Max(0,(1 - (Configurations.CumDecayRatio * (1 - cum.decayresist))));
|
|
cum.fertvolume *= Math.Max(0, (1 - (Configurations.CumDecayRatio * (1 - cum.decayresist))) * (1 - (Configurations.CumFertilityDecayRatio * (1 - cum.decayresist))));
|
|
if (vd - cum.volume > minmakefilthvalue) MakeCumFilth(cum);
|
|
if (cum.fertvolume < 0.01f) cum.fertvolume = 0;
|
|
if (cum.volume < 0.01f) removecums.Add(cum);
|
|
}
|
|
foreach(Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
removecums.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excrete cums in womb and get excreted amount of specific cum
|
|
/// </summary>
|
|
/// <param name="targetcum"></param>
|
|
/// <param name="portion"></param>
|
|
/// <returns></returns>
|
|
public float CumOut(Cum targetcum, float portion = 0.1f)
|
|
{
|
|
if (cums.NullOrEmpty()) return 0;
|
|
float outcum = 0;
|
|
List<Cum> removecums = new List<Cum>();
|
|
foreach (Cum cum in cums)
|
|
{
|
|
float vd = cum.volume;
|
|
if (cum.Equals(targetcum)) outcum = cum.volume * (portion * (1 - cum.decayresist));
|
|
cum.volume *= Math.Max(0, 1 - (portion * (1 - cum.decayresist)));
|
|
cum.fertvolume *= Math.Max(0, (1 - (portion * (1 - cum.decayresist))) * (1 - (Configurations.CumFertilityDecayRatio * (1 - cum.decayresist))));
|
|
if (vd-cum.volume > minmakefilthvalue) MakeCumFilth(cum);
|
|
if (cum.fertvolume < 0.01f) cum.fertvolume = 0;
|
|
if (cum.volume < 0.01f) removecums.Add(cum);
|
|
}
|
|
foreach (Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
removecums.Clear();
|
|
return outcum;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ignores cum's decayratio and get excreted amount of specific cum
|
|
/// </summary>
|
|
/// <param name="targetcum"></param>
|
|
/// <param name="portion"></param>
|
|
/// <returns></returns>
|
|
public float CumOutForce(Cum targetcum, float portion = 0.1f)
|
|
{
|
|
if (cums.NullOrEmpty()) return 0;
|
|
float outcum = 0;
|
|
List<Cum> removecums = new List<Cum>();
|
|
foreach (Cum cum in cums)
|
|
{
|
|
float vd = cum.volume;
|
|
if (cum.Equals(targetcum)) outcum = cum.volume * (portion);
|
|
cum.volume *= 1 - (portion);
|
|
cum.fertvolume *= (1 - (portion)) * (1 - (Configurations.CumFertilityDecayRatio));
|
|
if (vd - cum.volume > minmakefilthvalue) MakeCumFilth(cum);
|
|
if (cum.fertvolume < 0.01f) cum.fertvolume = 0;
|
|
if (cum.volume < 0.1f) removecums.Add(cum);
|
|
}
|
|
foreach (Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
removecums.Clear();
|
|
return outcum;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fertilize eggs and return the result
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool FertilizationCheck()
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
bool onefertilized = false;
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
if (!egg.fertilized) egg.fertilizer = Fertilize();
|
|
if (egg.fertilizer != null) {
|
|
egg.fertilized = true;
|
|
onefertilized = true;
|
|
}
|
|
}
|
|
return onefertilized;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
Props = (CompProperties_Menstruation)props;
|
|
|
|
if (!Props.infertile)
|
|
{
|
|
if (follicularIntervalhours < 0)
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(Props.folicularIntervalDays * 24, Props.deviationFactor);
|
|
curStage = RandomStage();
|
|
}
|
|
|
|
if (lutealIntervalhours < 0) lutealIntervalhours = PeriodRandomizer(Props.lutealIntervalDays * 24, Props.deviationFactor);
|
|
if (bleedingIntervalhours < 0) bleedingIntervalhours = PeriodRandomizer(Props.bleedingIntervalDays * 24, Props.deviationFactor);
|
|
if (recoveryIntervalhours < 0) recoveryIntervalhours = PeriodRandomizer(Props.recoveryIntervalDays * 24, Props.deviationFactor);
|
|
if (crampPain < 0) crampPain = PainRandomizer();
|
|
if (cums == null) cums = new List<Cum>();
|
|
if (eggs == null) eggs = new List<Egg>();
|
|
|
|
|
|
if (!Configurations.EnableMenopause)
|
|
{
|
|
Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Climacteric);
|
|
if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
|
|
hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Menopause);
|
|
if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
|
|
}
|
|
else if (ovarypower < -50000)
|
|
{
|
|
ovarypower = (int)(Props.ovaryPower * Rand.Range(0.7f, 1.3f)) - (Math.Max(0, parent.pawn.ageTracker.AgeBiologicalYears - 15)) * (60 / (Props.folicularIntervalDays + Props.lutealIntervalDays) * Configurations.CycleAcceleration);
|
|
if (ovarypower < 1)
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Menopause, parent.pawn);
|
|
hediff.Severity = 0.2f;
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
curStage = Stage.Young;
|
|
}
|
|
else if (ovarypower < ovarypowerthreshold)
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Climacteric, parent.pawn);
|
|
hediff.Severity = 0.008f * (ovarypowerthreshold - ovarypower);
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
}
|
|
}
|
|
|
|
|
|
if (parent.pawn.IsPregnant()) curStage = Stage.Pregnant;
|
|
if (parent.pawn.IsAnimal())
|
|
{
|
|
if (Configurations.EnableAnimalCycle)
|
|
{
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!parent.pawn.IsPregnant() && parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0) HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(Stage.Young), tickInterval, parent.pawn, false);
|
|
else HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cums == null) cums = new List<Cum>();
|
|
curStage = Stage.None;
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), tickInterval, parent.pawn, false);
|
|
}
|
|
//Log.Message(parent.pawn.Label + " - Initialized menstruation comp");
|
|
loaded = true;
|
|
}
|
|
|
|
|
|
private Pawn Fertilize()
|
|
{
|
|
if (cums.NullOrEmpty()) return null;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
float rand = Rand.Range(0.0f, 1.0f);
|
|
if (!cum.notcum && rand < cum.fertvolume * cum.fertFactor * Configurations.FertilizeChance * Props.basefertilizationChanceFactor)
|
|
{
|
|
if (!RJWPregnancySettings.bestial_pregnancy_enabled && (xxx.is_animal(parent.pawn) ^ xxx.is_animal(cum.pawn))) continue;
|
|
return cum.pawn;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
//for now, only one egg can be implanted
|
|
private bool Implant()
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
List<Egg> deadeggs = new List<Egg>();
|
|
bool pregnant = false;
|
|
foreach(Egg egg in eggs)
|
|
{
|
|
if (egg.position < 24 || !egg.fertilized) continue;
|
|
else if (Rand.Range(0.0f, 1.0f) <= Configurations.ImplantationChance * Props.baseImplantationChanceFactor * ImplantFactor * InterspeciesImplantFactor(egg.fertilizer))
|
|
{
|
|
if (!parent.pawn.IsPregnant())
|
|
{
|
|
if (!Configurations.UseMultiplePregnancy)
|
|
{
|
|
PregnancyHelper.PregnancyDecider(parent.pawn, egg.fertilizer);
|
|
pregnant = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Hediff_BasePregnancy.Create<Hediff_MultiplePregnancy>(parent.pawn, egg.fertilizer);
|
|
Hediff hediff = PregnancyHelper.GetPregnancy(parent.pawn);
|
|
if (hediff is Hediff_BasePregnancy)
|
|
{
|
|
Hediff_BasePregnancy h = (Hediff_BasePregnancy)hediff;
|
|
if (h.babies.Count > 1) h.babies.RemoveRange(1, h.babies.Count - 1);
|
|
}
|
|
pregnant = true;
|
|
deadeggs.Add(egg);
|
|
}
|
|
}
|
|
else if (Configurations.UseMultiplePregnancy && Configurations.EnableHeteroOvularTwins)
|
|
{
|
|
Hediff hediff = PregnancyHelper.GetPregnancy(parent.pawn);
|
|
if (hediff is Hediff_MultiplePregnancy)
|
|
{
|
|
Hediff_MultiplePregnancy h = (Hediff_MultiplePregnancy)hediff;
|
|
h.AddNewBaby(parent.pawn, egg.fertilizer);
|
|
}
|
|
pregnant = true;
|
|
deadeggs.Add(egg);
|
|
}
|
|
else
|
|
{
|
|
pregnant = true;
|
|
break;
|
|
}
|
|
}
|
|
else deadeggs.Add(egg);
|
|
}
|
|
|
|
if (pregnant && (!Configurations.UseMultiplePregnancy || !Configurations.EnableHeteroOvularTwins))
|
|
{
|
|
eggs.Clear();
|
|
deadeggs.Clear();
|
|
return true;
|
|
}
|
|
else if (!deadeggs.NullOrEmpty())
|
|
{
|
|
foreach (Egg egg in deadeggs)
|
|
{
|
|
eggs.Remove(egg);
|
|
}
|
|
deadeggs.Clear();
|
|
}
|
|
if (pregnant) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void BleedOut()
|
|
{
|
|
//FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, ThingDefOf.Filth_Blood,parent.pawn.Label);
|
|
CumIn(parent.pawn, Rand.Range(0f, 5f), Translations.Menstrual_Blood,-5.0f,ThingDefOf.Filth_Blood);
|
|
GetNotCum(Translations.Menstrual_Blood).color = Colors.blood;
|
|
}
|
|
|
|
private void MakeCumFilth(Cum cum)
|
|
{
|
|
FilthMaker.TryMakeFilth(parent.pawn.Position, parent.pawn.Map, cum.FilthDef, cum.pawn.LabelShort);
|
|
}
|
|
|
|
|
|
|
|
private void EggDecay()
|
|
{
|
|
List<Egg> deadeggs = new List<Egg>();
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
egg.lifespanhrs -= Configurations.CycleAcceleration;
|
|
egg.position += Configurations.CycleAcceleration;
|
|
if (egg.lifespanhrs < 0) deadeggs.Add(egg);
|
|
}
|
|
|
|
if (!deadeggs.NullOrEmpty())
|
|
{
|
|
foreach (Egg egg in deadeggs)
|
|
{
|
|
eggs.Remove(egg);
|
|
}
|
|
deadeggs.Clear();
|
|
}
|
|
}
|
|
|
|
|
|
private Action PeriodSimulator(Enum targetstage)
|
|
{
|
|
Action action = null;
|
|
switch (targetstage)
|
|
{
|
|
case Stage.Follicular:
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= (follicularIntervalhours - bleedingIntervalhours) * CycleFactor)
|
|
{
|
|
GoNextStage(Stage.Ovulatory);
|
|
}
|
|
else
|
|
{
|
|
curStageHrs+=Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
case Stage.Ovulatory:
|
|
action = delegate
|
|
{
|
|
int i = 0;
|
|
do
|
|
{
|
|
ovarypower--;
|
|
eggs.Add(new Egg((int)(Props.eggLifespanDays * 24 / CycleFactor)));
|
|
i++;
|
|
} while (i < Rand.ByCurve(parent.pawn.RaceProps.litterSizeCurve));
|
|
|
|
if (Configurations.EnableMenopause && ovarypower < 1)
|
|
{
|
|
eggs.Clear();
|
|
Hediff hediff = parent.pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_Climacteric);
|
|
if (hediff != null) parent.pawn.health.RemoveHediff(hediff);
|
|
hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Menopause, parent.pawn);
|
|
hediff.Severity = 0.2f;
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
GoNextStage(Stage.Young);
|
|
}
|
|
else if (Configurations.EnableMenopause && ovarypower < ovarypowerthreshold)
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_Climacteric, parent.pawn);
|
|
hediff.Severity = 0.008f * i;
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
lutealIntervalhours = PeriodRandomizer(lutealIntervalhours, Props.deviationFactor * 6);
|
|
GoNextStage(Stage.ClimactericLuteal);
|
|
}
|
|
else
|
|
{
|
|
lutealIntervalhours = PeriodRandomizer(lutealIntervalhours, Props.deviationFactor);
|
|
GoNextStage(Stage.Luteal);
|
|
}
|
|
};
|
|
break;
|
|
case Stage.Luteal:
|
|
action = delegate
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
EggDecay();
|
|
FertilizationCheck();
|
|
if (Implant()) GoNextStage(Stage.Pregnant);
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
else if (curStageHrs <= lutealIntervalhours)
|
|
{
|
|
curStageHrs+=Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
else
|
|
{
|
|
if (Props.bleedingIntervalDays == 0)
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
else
|
|
{
|
|
bleedingIntervalhours = PeriodRandomizer(bleedingIntervalhours, Props.deviationFactor);
|
|
if (crampPain >= 0.05f)
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_MenstrualCramp, parent.pawn);
|
|
hediff.Severity = crampPain * Rand.Range(0.9f, 1.1f);
|
|
HediffCompProperties_SeverityPerDay Prop = (HediffCompProperties_SeverityPerDay)hediff.TryGetComp<HediffComp_SeverityPerDay>().props;
|
|
Prop.severityPerDay = - hediff.Severity / (bleedingIntervalhours/24) * Configurations.CycleAcceleration;
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
}
|
|
GoNextStage(Stage.Bleeding);
|
|
}
|
|
}
|
|
};
|
|
break;
|
|
case Stage.Bleeding:
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= bleedingIntervalhours)
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
else
|
|
{
|
|
if (curStageHrs < bleedingIntervalhours / 4) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut();
|
|
curStageHrs+=Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
case Stage.Fertilized: //Obsoleted stage. merged in luteal stage
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= 24)
|
|
{
|
|
if (Implant())
|
|
{
|
|
GoNextStage(Stage.Pregnant);
|
|
}
|
|
else
|
|
{
|
|
GoNextStageSetHour(Stage.Luteal, 96);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curStageHrs+=Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
case Stage.Pregnant:
|
|
action = delegate
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
EggDecay();
|
|
FertilizationCheck();
|
|
Implant();
|
|
}
|
|
if (parent.pawn.IsPregnant()) StayCurrentStageConst(Stage.Pregnant);
|
|
else GoNextStage(Stage.Recover);
|
|
};
|
|
break;
|
|
case Stage.Recover:
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= recoveryIntervalhours)
|
|
{
|
|
if (ovarypower < ovarypowerthreshold)
|
|
{
|
|
GoNextStage(Stage.ClimactericFollicular);
|
|
}
|
|
else if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) == 0)
|
|
{
|
|
GoNextStage(Stage.Young);
|
|
}
|
|
else
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor);
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curStageHrs+=Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
case Stage.None:
|
|
action = delegate
|
|
{
|
|
StayCurrentStageConst(Stage.None);
|
|
};
|
|
break;
|
|
case Stage.Young:
|
|
action = delegate
|
|
{
|
|
if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0) StayCurrentStageConst(Stage.Young);
|
|
else GoNextStage(Stage.Follicular);
|
|
};
|
|
break;
|
|
case Stage.ClimactericFollicular:
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= (follicularIntervalhours - bleedingIntervalhours) * CycleFactor)
|
|
{
|
|
GoNextStage(Stage.Ovulatory);
|
|
}
|
|
else if (ovarypower < ovarypowerthreshold / 3 && Rand.Range(0.0f, 1.0f) < 0.2f) //skips ovulatory
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
|
|
GoNextStage(Stage.ClimactericFollicular);
|
|
}
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
case Stage.ClimactericLuteal:
|
|
action = delegate
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
EggDecay();
|
|
FertilizationCheck();
|
|
if (Implant()) GoNextStage(Stage.Pregnant);
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
else if (curStageHrs <= lutealIntervalhours)
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
else
|
|
{
|
|
if (Props.bleedingIntervalDays == 0)
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
|
|
GoNextStage(Stage.ClimactericFollicular);
|
|
}
|
|
else if (ovarypower < ovarypowerthreshold / 4 || (ovarypower < ovarypowerthreshold / 3 && Rand.Range(0.0f,1.0f) < 0.3f)) //skips bleeding
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
|
|
GoNextStage(Stage.ClimactericFollicular);
|
|
}
|
|
else
|
|
{
|
|
bleedingIntervalhours = PeriodRandomizer(bleedingIntervalhours, Props.deviationFactor);
|
|
if (crampPain >= 0.05f)
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_MenstrualCramp, parent.pawn);
|
|
hediff.Severity = crampPain * Rand.Range(0.9f, 1.1f);
|
|
HediffCompProperties_SeverityPerDay Prop = (HediffCompProperties_SeverityPerDay)hediff.TryGetComp<HediffComp_SeverityPerDay>().props;
|
|
Prop.severityPerDay = -hediff.Severity / (bleedingIntervalhours / 24) * Configurations.CycleAcceleration;
|
|
parent.pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(parent.pawn));
|
|
}
|
|
GoNextStage(Stage.ClimactericBleeding);
|
|
}
|
|
}
|
|
|
|
};
|
|
break;
|
|
case Stage.ClimactericBleeding:
|
|
action = delegate
|
|
{
|
|
if (curStageHrs >= bleedingIntervalhours)
|
|
{
|
|
follicularIntervalhours = PeriodRandomizer(follicularIntervalhours, Props.deviationFactor * 6);
|
|
GoNextStage(Stage.ClimactericFollicular);
|
|
}
|
|
else
|
|
{
|
|
if (curStageHrs < bleedingIntervalhours / 6) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut();
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
};
|
|
break;
|
|
|
|
default:
|
|
curStage = Stage.Follicular;
|
|
curStageHrs = 0;
|
|
if (follicularIntervalhours < 0) follicularIntervalhours = PeriodRandomizer(Props.folicularIntervalDays*24, Props.deviationFactor);
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(Stage.Follicular), tickInterval, parent.pawn, false);
|
|
break;
|
|
}
|
|
action += () =>
|
|
{
|
|
if (parent.pawn.health.capacities.GetLevel(xxx.reproduction) <= 0) curStage = Stage.Young;
|
|
CumOut();
|
|
};
|
|
|
|
actionref = action;
|
|
return actionref;
|
|
|
|
void GoNextStage(Stage nextstage, float factor = 1.0f)
|
|
{
|
|
curStageHrs = 0;
|
|
curStage = nextstage;
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(nextstage), (int)(tickInterval * factor), parent.pawn, false);
|
|
}
|
|
|
|
void GoNextStageSetHour(Stage nextstage, int hour, float factor = 1.0f)
|
|
{
|
|
curStageHrs = hour;
|
|
curStage = nextstage;
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(nextstage), (int)(tickInterval * factor), parent.pawn, false);
|
|
}
|
|
|
|
//stage can be interrupted in other reasons
|
|
void StayCurrentStage(float factor = 1.0f)
|
|
{
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curStage), (int)(tickInterval * factor), parent.pawn, false);
|
|
}
|
|
|
|
//stage never changes
|
|
void StayCurrentStageConst(Stage curstage, float factor = 1.0f)
|
|
{
|
|
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(PeriodSimulator(curstage), (int)(tickInterval * factor), parent.pawn, false);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private int PeriodRandomizer(int intervalhours, float deviation)
|
|
{
|
|
return intervalhours + (int)(intervalhours*Rand.Range(-deviation,deviation));
|
|
}
|
|
|
|
private float InterspeciesImplantFactor(Pawn fertilizer)
|
|
{
|
|
if (RJWPregnancySettings.complex_interspecies) return SexUtility.BodySimilarity(parent.pawn, fertilizer);
|
|
else return RJWPregnancySettings.interspecies_impregnation_modifier;
|
|
}
|
|
|
|
private float PainRandomizer()
|
|
{
|
|
float rand = Rand.Range(0.0f, 1.0f);
|
|
if (rand < 0.01f) return Rand.Range(0.0f,0.2f);
|
|
else if (rand < 0.2f) return Rand.Range(0.1f, 0.2f);
|
|
else if (rand < 0.8f) return Rand.Range(0.2f, 0.4f);
|
|
else if (rand < 0.95f) return Rand.Range(0.4f, 0.6f);
|
|
else return Rand.Range(0.6f, 1.0f);
|
|
}
|
|
|
|
private Stage RandomStage()
|
|
{
|
|
int rand = Rand.Range(0,2);
|
|
|
|
switch (rand)
|
|
{
|
|
case 0:
|
|
curStageHrs = Rand.Range(0, (Props.folicularIntervalDays - Props.bleedingIntervalDays) * 24);
|
|
return Stage.Follicular;
|
|
case 1:
|
|
curStageHrs = Rand.Range(0, Props.eggLifespanDays * 24);
|
|
return Stage.Luteal;
|
|
case 2:
|
|
curStageHrs = Rand.Range(0, Props.bleedingIntervalDays * 24);
|
|
return Stage.Bleeding;
|
|
default: return Stage.Follicular;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class Egg : IExposable
|
|
{
|
|
public bool fertilized;
|
|
public int lifespanhrs;
|
|
public Pawn fertilizer;
|
|
public int position;
|
|
|
|
public Egg()
|
|
{
|
|
fertilized = false;
|
|
lifespanhrs = 96;
|
|
fertilizer = null;
|
|
position = 0;
|
|
}
|
|
|
|
public Egg(int lifespanhrs)
|
|
{
|
|
fertilized = false;
|
|
this.lifespanhrs = lifespanhrs;
|
|
fertilizer = null;
|
|
position = 0;
|
|
}
|
|
|
|
public void ExposeData()
|
|
{
|
|
Scribe_References.Look(ref fertilizer, "fertilizer", true);
|
|
Scribe_Values.Look(ref fertilized, "fertilized", fertilized, true);
|
|
Scribe_Values.Look(ref lifespanhrs, "lifespanhrs", lifespanhrs, true);
|
|
Scribe_Values.Look(ref position, "position", position, true);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public class HediffComp_Anus : HediffComp
|
|
{
|
|
public override void CompPostTick(ref float severityAdjustment)
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|