mirror of
https://gitgud.io/lutepickle/rjw_menstruation.git
synced 2024-08-14 22:46:52 +00:00
1858 lines
68 KiB
C#
1858 lines
68 KiB
C#
using RimWorld;
|
|
using RimWorld.Planet;
|
|
using rjw;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
using Verse;
|
|
|
|
namespace RJW_Menstruation
|
|
{
|
|
[Flags]
|
|
public enum SeasonalBreed
|
|
{
|
|
Always = 0,
|
|
Spring = 1,
|
|
Summer = 2,
|
|
Fall = 4,
|
|
Winter = 8,
|
|
FirstHalf = Spring | Summer,
|
|
SecondHalf = Fall | Winter
|
|
}
|
|
|
|
|
|
public class CompProperties_Menstruation : HediffCompProperties
|
|
{
|
|
public float maxCumCapacity; // ml
|
|
public float baseImplantationChanceFactor;
|
|
public float basefertilizationChanceFactor;
|
|
public int follicularIntervalDays = 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 bool concealedEstrus = false;
|
|
public SeasonalBreed breedingSeason = SeasonalBreed.Always;
|
|
public int estrusDaysBeforeOvulation = 3;
|
|
public int eggMultiplier = 1;
|
|
|
|
|
|
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;
|
|
|
|
const int tickInterval = GenDate.TicksPerHour;
|
|
const int maxImplantDelayHours = 30 * 24;
|
|
const int minImplantAgeHours = 3 * 24;
|
|
|
|
public CompProperties_Menstruation Props;
|
|
public Stage curStage = Stage.Follicular;
|
|
public int curStageHrs = 0;
|
|
public bool loaded = false;
|
|
public bool initError = false;
|
|
public int ovarypower = -100000;
|
|
public int eggstack = 0;
|
|
public bool DoCleanWomb = false;
|
|
|
|
public enum Stage
|
|
{
|
|
Follicular,
|
|
Ovulatory,
|
|
Luteal,
|
|
Bleeding,
|
|
Pregnant,
|
|
Recover,
|
|
None,
|
|
Infertile,
|
|
Anestrus
|
|
}
|
|
|
|
public enum EstrusLevel
|
|
{
|
|
None,
|
|
Concealed,
|
|
Visible
|
|
}
|
|
|
|
public static readonly Dictionary<Stage, Texture2D> StageTexture = new Dictionary<Stage, Texture2D>()
|
|
{
|
|
{ Stage.Follicular, TextureCache.FollicularTexture },
|
|
{ Stage.Luteal, TextureCache.LutealTexture },
|
|
{ Stage.Bleeding, TextureCache.BleedingTexture },
|
|
{ Stage.Pregnant, TextureCache.PregnantTexture },
|
|
{ Stage.Recover, TextureCache.RecoverTexture }
|
|
};
|
|
|
|
|
|
protected List<Cum> cums;
|
|
protected List<Egg> eggs;
|
|
protected float cycleSpeed = -1;
|
|
protected float cycleVariability = -1;
|
|
protected int currentIntervalHours = -1;
|
|
protected float crampPain = -1;
|
|
protected Need sexNeed = null;
|
|
protected string customwombtex = null;
|
|
protected string customvagtex = null;
|
|
protected bool estrusflag = false;
|
|
protected int opcache = -1;
|
|
protected float antisperm = 0.0f;
|
|
protected float? originvagsize = null;
|
|
// RJW pregnancy, or Biotech pregnancy/labor/laborpushing
|
|
protected Hediff pregnancy = null;
|
|
|
|
private static readonly SimpleCurve SexFrequencyCurve = new SimpleCurve()
|
|
{
|
|
new CurvePoint(0.4f,0.05f),
|
|
new CurvePoint(0.6f,0.1f),
|
|
new CurvePoint(0.8f,0.25f),
|
|
new CurvePoint(1.0f,0.5f)
|
|
};
|
|
|
|
private static readonly SimpleCurve SexSatisfactionCurve = new SimpleCurve()
|
|
{
|
|
new CurvePoint(0.4f,0.5f),
|
|
new CurvePoint(0.6f,0.6f),
|
|
new CurvePoint(0.8f,0.7f),
|
|
new CurvePoint(1.0f,0.8f)
|
|
};
|
|
|
|
private static readonly SimpleCurve FertilityCurve = new SimpleCurve()
|
|
{
|
|
new CurvePoint(0.4f,0.01f),
|
|
new CurvePoint(0.6f,0.1f),
|
|
new CurvePoint(0.8f,0.25f),
|
|
new CurvePoint(1.0f,0.5f)
|
|
};
|
|
|
|
public Hediff Pregnancy {
|
|
get
|
|
{
|
|
if (pregnancy == null) return null;
|
|
else if (!Pawn.health.hediffSet.hediffs.Contains(pregnancy))
|
|
{
|
|
pregnancy = null;
|
|
return null;
|
|
}
|
|
else return pregnancy;
|
|
}
|
|
set => pregnancy = value;
|
|
}
|
|
|
|
public int OvaryPowerThreshold
|
|
{
|
|
get
|
|
{
|
|
if (opcache > 0) return opcache;
|
|
float avglittersize;
|
|
try
|
|
{
|
|
avglittersize = Mathf.Max(Rand.ByCurveAverage(Pawn.def.race.litterSizeCurve), 1.0f);
|
|
}
|
|
catch
|
|
{
|
|
// Any exceptions in that will have been reported elsewhere in the code by now
|
|
avglittersize = 1.0f;
|
|
};
|
|
const float yearsBeforeMenopause = 6.0f;
|
|
opcache = (int)(RaceCyclesPerYear() *
|
|
avglittersize *
|
|
yearsBeforeMenopause *
|
|
(Pawn.def.race.lifeExpectancy / ThingDefOf.Human.race.lifeExpectancy));
|
|
if (opcache == 0) opcache = 1;
|
|
return opcache;
|
|
}
|
|
}
|
|
|
|
// >= 1: Normal cycles
|
|
// 1 - 0: Climacteric
|
|
// <= 0: Menopause
|
|
public float EggHealth
|
|
{
|
|
get
|
|
{
|
|
if (!Configurations.EnableMenopause || Props.infertile) return Mathf.Max(1.0f, ovarypower / OvaryPowerThreshold);
|
|
else return ovarypower / OvaryPowerThreshold;
|
|
}
|
|
}
|
|
|
|
public float SexFrequencyModifier
|
|
{
|
|
get
|
|
{
|
|
float eggHealth = EggHealth;
|
|
if (eggHealth >= 1) return 1.0f;
|
|
else if (eggHealth <= 0) return 0.01f;
|
|
else return SexFrequencyCurve.Evaluate(eggHealth);
|
|
}
|
|
}
|
|
|
|
public float SexSatisfactionModifier
|
|
{
|
|
get
|
|
{
|
|
float eggHealth = EggHealth;
|
|
if (eggHealth >= 1) return 1.0f;
|
|
else if (eggHealth <= 0) return 0.5f;
|
|
else return SexSatisfactionCurve.Evaluate(eggHealth);
|
|
}
|
|
}
|
|
|
|
public float FertilityModifier
|
|
{
|
|
get
|
|
{
|
|
float eggHealth = EggHealth;
|
|
if (eggHealth >= 1) return 1.0f;
|
|
else if (eggHealth <= 0) return 0.0f;
|
|
else return FertilityCurve.Evaluate(eggHealth);
|
|
}
|
|
}
|
|
|
|
public float TotalCum
|
|
{
|
|
get
|
|
{
|
|
return cums?.Sum(cum => cum.Volume) ?? 0;
|
|
}
|
|
}
|
|
public float TotalFertCum
|
|
{
|
|
get
|
|
{
|
|
return cums?.Sum(cum => cum.FertVolume) ?? 0;
|
|
}
|
|
}
|
|
public float TotalCumPercent
|
|
{
|
|
get
|
|
{
|
|
return cums?.Sum(cum => cum.Volume) / Props.maxCumCapacity ?? 0;
|
|
}
|
|
}
|
|
public float CumCapacity
|
|
{
|
|
get
|
|
{
|
|
float res = Props.maxCumCapacity * Pawn.BodySize;
|
|
if (curStage != Stage.Pregnant || (pregnancy?.Severity ?? 0f) < 0.175f) res *= 500f;
|
|
return res;
|
|
}
|
|
}
|
|
//make follicular interval into half and double egg lifespan
|
|
public float CycleFactor
|
|
{
|
|
get
|
|
{
|
|
if (xxx.has_quirk(Pawn, "Breeder")) return 0.5f;
|
|
|
|
return 1.0f;
|
|
}
|
|
}
|
|
//effect on implant chance
|
|
public float ImplantFactor
|
|
{
|
|
get
|
|
{
|
|
float factor = 1.0f;
|
|
if (Pawn.Has(Quirk.Breeder)) factor = 10.0f;
|
|
return Pawn.health.capacities.GetLevel(xxx.reproduction) * Props.baseImplantationChanceFactor * FertilityModifier * factor;
|
|
}
|
|
}
|
|
public IEnumerable<string> GetCumsInfo
|
|
{
|
|
get
|
|
{
|
|
if (cums.NullOrEmpty()) yield return Translations.Info_noCum;
|
|
else foreach (Cum cum in cums)
|
|
{
|
|
if (!cum.notcum) yield return string.Format("{0}: {1:0.##}ml", cum.pawn?.Label, cum.Volume);
|
|
else yield return string.Format("{0}: {1:0.##}ml", cum.notcumLabel, cum.Volume);
|
|
}
|
|
}
|
|
}
|
|
public Color GetCumMixtureColor
|
|
{
|
|
get
|
|
{
|
|
Color mixedcolor = Color.white;
|
|
|
|
if (cums.NullOrEmpty()) return mixedcolor;
|
|
|
|
float mixedsofar = 0;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
if (cum.Volume > 0)
|
|
{
|
|
mixedcolor = Colors.CMYKLerp(mixedcolor, cum.Color, cum.Volume / (mixedsofar + cum.Volume));
|
|
mixedsofar += cum.Volume;
|
|
}
|
|
}
|
|
return mixedcolor;
|
|
}
|
|
}
|
|
|
|
public Stage CurrentVisibleStage
|
|
{
|
|
get
|
|
{
|
|
if (curStage == Stage.Pregnant)
|
|
{
|
|
if (Configurations.InfoDetail == Configurations.DetailLevel.All || (pregnancy?.Visible ?? false))
|
|
return Stage.Pregnant;
|
|
else
|
|
return Stage.Luteal;
|
|
}
|
|
return curStage;
|
|
}
|
|
}
|
|
|
|
public string GetCurStageLabel
|
|
{
|
|
get
|
|
{
|
|
switch (CurrentVisibleStage)
|
|
{
|
|
case Stage.Follicular:
|
|
return Translations.Stage_Follicular + (EggHealth < 1f ? Translations.Stage_Climacteric : "");
|
|
case Stage.Ovulatory:
|
|
return Translations.Stage_Ovulatory + (EggHealth < 1f ? Translations.Stage_Climacteric : "");
|
|
case Stage.Luteal:
|
|
return Translations.Stage_Luteal + (EggHealth < 1f ? Translations.Stage_Climacteric : "");
|
|
case Stage.Bleeding:
|
|
return Translations.Stage_Bleeding + (EggHealth < 1f ? Translations.Stage_Climacteric : "");
|
|
case Stage.Pregnant:
|
|
return Translations.Stage_Pregnant;
|
|
case Stage.Recover:
|
|
return Translations.Stage_Recover;
|
|
case Stage.None:
|
|
case Stage.Infertile:
|
|
if (EggHealth <= 0f) return Translations.Stage_Menopause;
|
|
else return Translations.Stage_None;
|
|
case Stage.Anestrus:
|
|
return Translations.Stage_Anestrus;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
}
|
|
public virtual string GetCurStageDesc
|
|
{
|
|
get
|
|
{
|
|
switch (CurrentVisibleStage)
|
|
{
|
|
case Stage.Follicular:
|
|
return Translations.Stage_Follicular_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : "");
|
|
case Stage.Ovulatory:
|
|
return Translations.Stage_Ovulatory_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : "");
|
|
case Stage.Luteal:
|
|
return Translations.Stage_Luteal_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : "");
|
|
case Stage.Bleeding:
|
|
return Translations.Stage_Bleeding_Desc + (EggHealth < 1f ? Translations.Stage_Climacteric_Desc : "");
|
|
case Stage.Pregnant:
|
|
return Translations.Stage_Pregnant_Desc;
|
|
case Stage.Recover:
|
|
return Translations.Stage_Recover_Desc;
|
|
case Stage.None:
|
|
case Stage.Infertile:
|
|
if (EggHealth <= 0f) return Translations.Stage_Menopause_Desc;
|
|
else return Translations.Stage_None_Desc;
|
|
case Stage.Anestrus:
|
|
return Translations.Stage_Anestrus_Desc;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
public string WombTex
|
|
{
|
|
get
|
|
{
|
|
return customwombtex ?? Props.wombTex;
|
|
}
|
|
set
|
|
{
|
|
customwombtex = value;
|
|
}
|
|
}
|
|
public string VagTex
|
|
{
|
|
get
|
|
{
|
|
return customvagtex ?? Props.vagTex;
|
|
}
|
|
set
|
|
{
|
|
customvagtex = value;
|
|
}
|
|
}
|
|
public string GetFertilizingInfo
|
|
{
|
|
get
|
|
{
|
|
if (eggs.NullOrEmpty()) return "";
|
|
|
|
StringBuilder res = new StringBuilder();
|
|
int fertilized = eggs.Count(egg => egg.fertilized);
|
|
if (fertilized != 0) res.AppendFormat("{0} {1}", fertilized, Translations.Dialog_WombInfo05);
|
|
if (fertilized != 0 && eggs.Count - fertilized != 0) res.Append(", ");
|
|
if (cums.NullOrEmpty() || TotalFertCum == 0)
|
|
{
|
|
if (eggs.Count - fertilized != 0) res.AppendFormat("{0} {1}", eggs.Count - fertilized, Translations.Dialog_WombInfo07);
|
|
}
|
|
else
|
|
{
|
|
if (eggs.Count - fertilized != 0) res.AppendFormat("{0} {1}", eggs.Count - fertilized, Translations.Dialog_WombInfo06);
|
|
}
|
|
return res.ToString();
|
|
}
|
|
}
|
|
public bool IsEggFertilizing
|
|
{
|
|
get
|
|
{
|
|
if (eggs.NullOrEmpty()) return false;
|
|
return cums?.Any(cum => cum.FertVolume > 0) ?? false;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// returns fertstage. if not fertilized returns -1
|
|
/// </summary>
|
|
public int IsFertilized
|
|
{
|
|
get
|
|
{
|
|
if (eggs?.All(egg => !egg.fertilized) ?? true) return -1;
|
|
return eggs.Max(egg => egg.fertstage);
|
|
}
|
|
}
|
|
public IEnumerable<Pawn> GetCummersAndFertilizers()
|
|
{
|
|
if (!cums.NullOrEmpty())
|
|
foreach (Cum cum in cums)
|
|
yield return cum.pawn;
|
|
if (!eggs.NullOrEmpty())
|
|
foreach (Egg egg in eggs)
|
|
yield return egg.fertilizer;
|
|
}
|
|
public bool IsEggExist
|
|
{
|
|
get
|
|
{
|
|
return !eggs.NullOrEmpty();
|
|
}
|
|
}
|
|
public virtual bool IsDangerDay
|
|
{
|
|
get
|
|
{
|
|
if (Pawn.HasIUD()) return false;
|
|
|
|
switch (curStage)
|
|
{
|
|
case Stage.Follicular:
|
|
return curStageHrs > 0.7f * currentIntervalHours;
|
|
case Stage.Ovulatory:
|
|
return true;
|
|
case Stage.Luteal:
|
|
return curStageHrs < Props.eggLifespanDays * 24;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
public int GetNumOfEggs
|
|
{
|
|
get
|
|
{
|
|
return eggs?.Count ?? 0;
|
|
}
|
|
}
|
|
public Color BloodColor
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
Color c = Pawn.def.race.BloodDef.graphicData.color;
|
|
return c;
|
|
}
|
|
catch
|
|
{
|
|
return Colors.blood;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public float OriginVagSize
|
|
{
|
|
get
|
|
{
|
|
if (originvagsize == null)
|
|
{
|
|
originvagsize = parent.Severity;
|
|
}
|
|
return originvagsize ?? 0.1f;
|
|
}
|
|
set
|
|
{
|
|
originvagsize = value;
|
|
}
|
|
}
|
|
|
|
public float CurStageIntervalHours
|
|
{
|
|
get
|
|
{
|
|
return currentIntervalHours;
|
|
}
|
|
}
|
|
|
|
public float StageProgress
|
|
{
|
|
get
|
|
{
|
|
if (pregnancy == null) return Mathf.Clamp01(curStageHrs / CurStageIntervalHours);
|
|
bool is_discovered = false;
|
|
switch (pregnancy)
|
|
{
|
|
case Hediff_BasePregnancy rjw_preg:
|
|
is_discovered = rjw_preg.is_discovered;
|
|
break;
|
|
case Hediff_Pregnant vanilla_preg:
|
|
is_discovered = vanilla_preg.Visible;
|
|
break;
|
|
case Hediff_Labor _:
|
|
case Hediff_LaborPushing _:
|
|
return 1.0f;
|
|
}
|
|
|
|
if (is_discovered || Configurations.infoDetail == Configurations.DetailLevel.All) return pregnancy.Severity;
|
|
|
|
// Luteal will appear to progress, hitting the end of the phase when the pregnancy is discovered
|
|
float discoveryTime = 0.5f;
|
|
if (Pawn.story?.bodyType == BodyTypeDefOf.Thin) discoveryTime = 0.25f;
|
|
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 * 24));
|
|
|
|
return GenMath.LerpDouble(0, discoveryTime, lutealProgressWhenImplanted, 1.0f, pregnancy.Severity);
|
|
}
|
|
}
|
|
|
|
public Texture2D GetStageTexture
|
|
{
|
|
get
|
|
{
|
|
if (!StageTexture.TryGetValue(CurrentVisibleStage, out Texture2D tex)) tex = TextureCache.PregnantTexture;
|
|
return tex;
|
|
}
|
|
}
|
|
|
|
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 cycleSpeed, "cycleSpeed", cycleSpeed, true);
|
|
Scribe_Values.Look(ref cycleVariability, "cycleVariability", cycleVariability, true);
|
|
Scribe_Values.Look(ref currentIntervalHours, "currentIntervalHours", currentIntervalHours, true);
|
|
Scribe_Values.Look(ref crampPain, "crampPain", crampPain, true);
|
|
Scribe_Values.Look(ref ovarypower, "ovarypower", ovarypower, true);
|
|
Scribe_Values.Look(ref eggstack, "eggstack", eggstack, true);
|
|
Scribe_Values.Look(ref estrusflag, "estrusflag", estrusflag, true);
|
|
Scribe_Values.Look(ref originvagsize, "originvagsize", originvagsize, true);
|
|
Scribe_Values.Look(ref DoCleanWomb, "DoCleanWomb", DoCleanWomb, true);
|
|
Scribe_References.Look(ref pregnancy, "pregnancy");
|
|
}
|
|
|
|
|
|
public override void CompPostPostAdd(DamageInfo? dinfo)
|
|
{
|
|
if (!loaded)
|
|
{
|
|
Initialize();
|
|
}
|
|
}
|
|
|
|
public bool ShouldSimulate()
|
|
{
|
|
if (!Configurations.EnableAnimalCycle && Pawn.IsAnimal()) return false;
|
|
if (Pawn.Spawned || Pawn.IsCaravanMember() || PawnUtility.IsTravelingInTransportPodWorldObject(Pawn)) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void CompPostTick(ref float severityAdjustment)
|
|
{
|
|
base.CompPostTick(ref severityAdjustment);
|
|
// If an exception makes it out, RW will remove the hediff, so catch it here
|
|
try
|
|
{
|
|
if (!ShouldSimulate()) return;
|
|
|
|
// Initialize immediately if needed, but if there's an error, then don't spam it every tick
|
|
if (!loaded && !initError)
|
|
{
|
|
Log.Warning($"{Pawn}'s womb is ticking, but was not initialized first");
|
|
Initialize();
|
|
}
|
|
|
|
if (!Pawn.IsHashIntervalTick(tickInterval)) return;
|
|
|
|
if (initError) Log.Warning($"Attempting to process {Pawn}'s womb uninitialized");
|
|
|
|
if (Pregnancy != null && curStage != Stage.Pregnant)
|
|
{
|
|
Log.Warning($"{Pawn}'s womb has a pregnancy, but was not in the pregnant stage");
|
|
curStage = Stage.Pregnant;
|
|
}
|
|
|
|
CumOut();
|
|
|
|
if (pregnancy == null && (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())) curStage = Stage.Infertile;
|
|
switch (curStage)
|
|
{
|
|
case Stage.Follicular:
|
|
FollicularAction();
|
|
break;
|
|
case Stage.Ovulatory:
|
|
OvulatoryAction();
|
|
break;
|
|
case Stage.Luteal:
|
|
LutealAction();
|
|
break;
|
|
case Stage.Bleeding:
|
|
BleedingAction();
|
|
break;
|
|
case Stage.Pregnant:
|
|
PregnantAction();
|
|
break;
|
|
case Stage.Recover:
|
|
RecoverAction();
|
|
break;
|
|
case Stage.None:
|
|
break;
|
|
case Stage.Infertile:
|
|
InfertileAction();
|
|
break;
|
|
case Stage.Anestrus:
|
|
AnestrusAction();
|
|
break;
|
|
default:
|
|
GoNextStage(Stage.Follicular);
|
|
break;
|
|
}
|
|
AfterSimulator();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error($"Error processing womb of {Pawn}: {ex}");
|
|
}
|
|
}
|
|
|
|
public override void CompPostPostRemoved()
|
|
{
|
|
// If a hediff is removed from a pawn that does not have it, CompPostPostRemoved is still called on the pawn that does.
|
|
// If it was a legitimate removal, then it won't be in this pawn's hediff list anymore, as that removal occurs first
|
|
if (Pawn.health.hediffSet.hediffs.Contains(parent))
|
|
{
|
|
Log.Warning($"Attempted to remove menstruation comp from wrong pawn ({Pawn}).");
|
|
return;
|
|
}
|
|
switch (pregnancy)
|
|
{
|
|
case null:
|
|
case Hediff_MechanoidPregnancy _:
|
|
break;
|
|
case Hediff_BasePregnancy rjw_preg:
|
|
rjw_preg.Miscarry();
|
|
break;
|
|
case Hediff_Pregnant vanilla_preg:
|
|
Pawn.health.RemoveHediff(vanilla_preg);
|
|
break;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return cums?.Find(cum => !cum.notcum && cum.pawn == pawn);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inject pawn's cum into womb
|
|
/// </summary>
|
|
/// <param name="pawn"></param>
|
|
/// <param name="volume"></param>
|
|
/// <param name="fertility"></param>
|
|
/// <param name="filthdef"></param>
|
|
public void CumIn(Pawn pawn, float volume, float fertility = 1.0f, ThingDef filthdef = null)
|
|
{
|
|
if (volume <= 0) return;
|
|
if (Pawn.HasIUD()) fertility /= 100f;
|
|
float cumd = TotalCumPercent;
|
|
float tmp = TotalCum + volume;
|
|
if (tmp > CumCapacity)
|
|
{
|
|
float cumoutrate = 1 - (CumCapacity / tmp);
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.pawn.Equals(pawn))
|
|
{
|
|
cum.MergeWithCum(volume, fertility, filthdef);
|
|
merged = true;
|
|
}
|
|
cum.DismishForce(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.MergeWithCum(volume, fertility, filthdef);
|
|
merged = true;
|
|
}
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume, fertility, filthdef));
|
|
}
|
|
cumd = TotalCumPercent - cumd;
|
|
|
|
Pawn.records.AddTo(VariousDefOf.AmountofCreampied, volume);
|
|
AfterCumIn(pawn);
|
|
AfterFluidIn(cumd);
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
if (volume <= 0) return;
|
|
float tmp = TotalCum + volume;
|
|
float cumd = TotalCumPercent;
|
|
if (tmp > CumCapacity)
|
|
{
|
|
float cumoutrate = 1 - (CumCapacity / tmp);
|
|
bool merged = false;
|
|
if (!cums.NullOrEmpty()) foreach (Cum cum in cums)
|
|
{
|
|
if (cum.notcum && cum.pawn.Equals(pawn) && cum.notcumLabel.Equals(notcumlabel))
|
|
{
|
|
cum.MergeWithFluid(volume, decayresist, filthdef);
|
|
merged = true;
|
|
}
|
|
cum.DismishForce(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.MergeWithFluid(volume, decayresist, filthdef);
|
|
merged = true;
|
|
}
|
|
}
|
|
if (!merged) cums.Add(new Cum(pawn, volume, notcumlabel, decayresist, filthdef));
|
|
}
|
|
cumd = TotalCumPercent - cumd;
|
|
AfterNotCumIn();
|
|
AfterFluidIn(cumd);
|
|
}
|
|
|
|
protected virtual void AfterCumIn(Pawn cummer)
|
|
{
|
|
ThoughtCumInside(cummer);
|
|
TaleCumInside(cummer);
|
|
}
|
|
|
|
protected virtual void AfterNotCumIn()
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Action for both Cum and NotCum
|
|
/// </summary>
|
|
/// <param name="fd">Fluid deviation</param>
|
|
protected virtual void AfterFluidIn(float fd)
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
protected void BeforeCumOut(out Absorber absorber)
|
|
{
|
|
Hediff asa = Pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_ASA);
|
|
float asafactor = asa?.Severity ?? 0f;
|
|
|
|
if (Pawn.HasIUD()) antisperm = 0.70f + asafactor;
|
|
else antisperm = 0.0f + asafactor;
|
|
|
|
absorber = (Absorber)Pawn.apparel?.WornApparel?.Find(x => x is Absorber);
|
|
if (absorber != null)
|
|
{
|
|
absorber.WearEffect();
|
|
if (absorber.dirty && absorber.EffectAfterDirty) absorber.DirtyEffect();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// For natural leaking
|
|
/// </summary>
|
|
protected virtual void AfterCumOut()
|
|
{
|
|
Pawn.needs?.mood?.thoughts?.memories?.TryGainMemory(VariousDefOf.LeakingFluids);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For all type of leaking
|
|
/// </summary>
|
|
/// <param name="fd"></param>
|
|
protected virtual void AfterFluidOut(float fd)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Excrete cums in womb naturally
|
|
/// </summary>
|
|
public void CumOut()
|
|
{
|
|
float leakfactor = 1.0f;
|
|
float totalleak = 0f;
|
|
float cumd = TotalCumPercent;
|
|
List<string> filthlabels = new List<string>();
|
|
BeforeCumOut(out Absorber absorber);
|
|
if (cums.NullOrEmpty()) return;
|
|
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 (Pawn.CurJobDef == xxx.knotted) leakfactor = 0f;
|
|
List<Cum> removecums = new List<Cum>();
|
|
foreach (Cum cum in cums)
|
|
{
|
|
cum.CumEffects(Pawn);
|
|
float vd = cum.DismishNatural(leakfactor, this, antisperm);
|
|
cum.MakeThinner(Configurations.CycleAcceleration);
|
|
totalleak += AbsorbCum(vd, absorber);
|
|
string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
|
|
filthlabels.Add(tmp.Replace(" x1", ""));
|
|
if (cum.ShouldRemove()) removecums.Add(cum);
|
|
}
|
|
if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
|
|
else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
|
|
foreach (Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
cumd = TotalCumPercent - cumd;
|
|
if (totalleak >= 1.0f) AfterCumOut();
|
|
AfterFluidOut(cumd);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force excrete cums in womb and get excreted amount of specific cum.
|
|
/// </summary>
|
|
/// <param name="targetcum"></param>
|
|
/// <param name="portion"></param>
|
|
/// <returns>Amount of target cum</returns>
|
|
public float CumOut(Cum targetcum, float portion = 0.1f)
|
|
{
|
|
if (cums.NullOrEmpty()) return 0;
|
|
float totalleak = 0;
|
|
List<string> filthlabels = new List<string>();
|
|
float outcum = 0;
|
|
float cumd = TotalCumPercent;
|
|
List<Cum> removecums = new List<Cum>();
|
|
foreach (Cum cum in cums)
|
|
{
|
|
float vd = cum.DismishForce(portion);
|
|
if (cum.Equals(targetcum)) outcum = vd;
|
|
//MakeCumFilth(cum, vd - cum.volume);
|
|
string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
|
|
filthlabels.Add(tmp.Replace(" x1", ""));
|
|
totalleak += vd;
|
|
if (cum.ShouldRemove()) removecums.Add(cum);
|
|
}
|
|
if (cums.Count > 1) MakeCumFilthMixture(totalleak, filthlabels);
|
|
else if (cums.Count == 1) MakeCumFilth(cums.First(), totalleak);
|
|
foreach (Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
cumd = TotalCumPercent - cumd;
|
|
AfterFluidOut(cumd);
|
|
return outcum;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force excrete cums in womb and get mixture of cum.
|
|
/// </summary>
|
|
/// <param name="mixtureDef"></param>
|
|
/// <param name="portion"></param>
|
|
/// <returns></returns>
|
|
public CumMixture MixtureOut(ThingDef mixtureDef, float portion = 0.1f)
|
|
{
|
|
if (cums.NullOrEmpty()) return null;
|
|
Color color = GetCumMixtureColor;
|
|
float totalleak = 0;
|
|
List<string> cumlabels = new List<string>();
|
|
List<Cum> removecums = new List<Cum>();
|
|
bool pure = true;
|
|
foreach (Cum cum in cums)
|
|
{
|
|
float vd = cum.DismishForce(portion);
|
|
string tmp = "FilthLabelWithSource".Translate(cum.FilthDef.label, cum.pawn?.LabelShort ?? "Unknown", 1.ToString());
|
|
cumlabels.Add(tmp.Replace(" x1", ""));
|
|
totalleak += vd;
|
|
if (cum.ShouldRemove()) removecums.Add(cum);
|
|
if (cum.notcum) pure = false;
|
|
}
|
|
foreach (Cum cum in removecums)
|
|
{
|
|
cums.Remove(cum);
|
|
}
|
|
return new CumMixture(Pawn, totalleak, cumlabels, color, mixtureDef, pure);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Debug: Remove all cums from a womb
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public void RemoveAllCums()
|
|
{
|
|
cums.Clear();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Fertilize eggs and return the result
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected void FertilizationCheck()
|
|
{
|
|
if (eggs.NullOrEmpty()) return;
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
if (!egg.fertilized) egg.fertilizer = Fertilize();
|
|
if (egg.fertilizer != null)
|
|
{
|
|
egg.fertilized = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
initError = true;
|
|
Props = (CompProperties_Menstruation)props;
|
|
|
|
if (Props.infertile)
|
|
{
|
|
if (cums == null) cums = new List<Cum>();
|
|
curStage = Stage.None;
|
|
loaded = true;
|
|
initError = false;
|
|
return;
|
|
}
|
|
|
|
if (cycleSpeed < 0f) cycleSpeed = Utility.RandGaussianLike(0.8f, 1.2f);
|
|
if (cycleVariability < 0f) cycleVariability = MenstruationUtility.RandomVariabilityPercent();
|
|
if (currentIntervalHours < 0)
|
|
{
|
|
if (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || Pawn.SterileGenes()) curStage = Stage.Infertile;
|
|
else if (!IsBreedingSeason()) curStage = Stage.Anestrus;
|
|
else curStage = RandomStage();
|
|
if (curStage == Stage.Follicular)
|
|
currentIntervalHours = PeriodRandomizer(Stage.Follicular) - PeriodRandomizer(Stage.Bleeding);
|
|
else
|
|
currentIntervalHours = PeriodRandomizer(curStage);
|
|
if (currentIntervalHours <= 0) currentIntervalHours = 1;
|
|
else if (currentIntervalHours < curStageHrs) curStageHrs = currentIntervalHours;
|
|
}
|
|
if (crampPain < 0) crampPain = PainRandomizer();
|
|
if (cums == null) cums = new List<Cum>();
|
|
if (eggs == null) eggs = new List<Egg>();
|
|
|
|
|
|
InitOvary();
|
|
TakeLoosePregnancy();
|
|
|
|
//Log.Message(Pawn.Label + " - Initialized menstruation comp");
|
|
loaded = true;
|
|
initError = false;
|
|
}
|
|
|
|
protected virtual float RaceCyclesPerYear()
|
|
{
|
|
int breedingSeasons = 0;
|
|
if (Props.breedingSeason == SeasonalBreed.Always) breedingSeasons = 4;
|
|
else
|
|
{
|
|
if ((Props.breedingSeason & SeasonalBreed.Spring) != 0) breedingSeasons++;
|
|
if ((Props.breedingSeason & SeasonalBreed.Summer) != 0) breedingSeasons++;
|
|
if ((Props.breedingSeason & SeasonalBreed.Fall) != 0) breedingSeasons++;
|
|
if ((Props.breedingSeason & SeasonalBreed.Winter) != 0) breedingSeasons++;
|
|
}
|
|
float breedingRatio = breedingSeasons / 4.0f;
|
|
return breedingRatio * GenDate.DaysPerYear / ((float)(Props.follicularIntervalDays + Props.lutealIntervalDays) / Configurations.CycleAccelerationDefault);
|
|
}
|
|
|
|
protected virtual int PawnEggsUsed(float pawnCyclesElapsed, float avglittersize)
|
|
{
|
|
return (int)(pawnCyclesElapsed * avglittersize);
|
|
}
|
|
|
|
public int GetOvaryPowerByAge()
|
|
{
|
|
float avglittersize;
|
|
try
|
|
{
|
|
avglittersize = Mathf.Max(Rand.ByCurveAverage(Pawn.def.race.litterSizeCurve), 1.0f);
|
|
}
|
|
catch (NullReferenceException)
|
|
{
|
|
avglittersize = 1.0f;
|
|
}
|
|
|
|
float fertStartAge = Pawn.RaceProps.lifeStageAges?.Find(stage => stage.def.reproductive)?.minAge ?? 0.0f;
|
|
float fertEndAge = Pawn.RaceProps.lifeExpectancy * (Pawn.IsAnimal() ? RJWPregnancySettings.fertility_endage_female_animal : RJWPregnancySettings.fertility_endage_female_humanlike);
|
|
if (fertEndAge < fertStartAge) fertEndAge = fertStartAge;
|
|
|
|
float raceCyclesPerYear = RaceCyclesPerYear();
|
|
int lifetimeCycles = (int)(raceCyclesPerYear * (fertEndAge - fertStartAge));
|
|
int lifetimeEggs = (int)(lifetimeCycles * avglittersize * Props.eggMultiplier * Utility.RandGaussianLike(0.70f, 1.30f, 5));
|
|
|
|
float pawnCyclesPerYear = raceCyclesPerYear * cycleSpeed;
|
|
float pawnCyclesElapsed = Mathf.Max((Pawn.ageTracker.AgeBiologicalYearsFloat - fertStartAge) * pawnCyclesPerYear, 0.0f);
|
|
int pawnEggsUsed = PawnEggsUsed(pawnCyclesElapsed, avglittersize);
|
|
|
|
return Math.Max(lifetimeEggs - pawnEggsUsed, 0);
|
|
}
|
|
|
|
protected void InitOvary()
|
|
{
|
|
if (ovarypower < -50000)
|
|
{
|
|
ovarypower = GetOvaryPowerByAge();
|
|
if (Props.infertile) curStage = Stage.None;
|
|
else if (ovarypower < 1)
|
|
{
|
|
curStage = Stage.Infertile;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RecoverOvary(float multiply = 1.2f)
|
|
{
|
|
ovarypower = Math.Max(0, (int)(ovarypower * multiply));
|
|
}
|
|
|
|
|
|
protected void AfterSimulator()
|
|
{
|
|
if (EggHealth < 1f)
|
|
{
|
|
if (sexNeed == null) sexNeed = Pawn.needs.TryGetNeed(VariousDefOf.SexNeed);
|
|
if (sexNeed?.CurLevel < 0.5) sexNeed.CurLevel += 0.01f / Math.Max(1, Pawn.GetMenstruationComps().Count());
|
|
}
|
|
}
|
|
|
|
protected virtual bool ShouldBeInEstrus()
|
|
{
|
|
if (!loaded)
|
|
Initialize();
|
|
switch (curStage)
|
|
{
|
|
case Stage.Follicular:
|
|
return curStageHrs > currentIntervalHours - Props.estrusDaysBeforeOvulation * 24;
|
|
case Stage.Ovulatory:
|
|
return true;
|
|
case Stage.Luteal:
|
|
return curStageHrs < Props.eggLifespanDays * 24;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public EstrusLevel GetEstrusLevel()
|
|
{
|
|
if (!ShouldBeInEstrus()) return EstrusLevel.None;
|
|
else return Props.concealedEstrus ? EstrusLevel.Concealed : EstrusLevel.Visible;
|
|
}
|
|
|
|
public void SetEstrus()
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(Props.concealedEstrus ? VariousDefOf.Hediff_Estrus_Concealed : VariousDefOf.Hediff_Estrus, Pawn);
|
|
Pawn.health.AddHediff(hediff);
|
|
}
|
|
|
|
public bool IsBreedingSeason()
|
|
{
|
|
if (Props.breedingSeason == SeasonalBreed.Always) return true;
|
|
|
|
int tile = Pawn.Tile;
|
|
if (tile < 0) tile = Find.AnyPlayerHomeMap?.Tile ?? -1;
|
|
if (tile < 0) return true;
|
|
switch (GenLocalDate.Season(tile))
|
|
{
|
|
case Season.Spring:
|
|
return (Props.breedingSeason & SeasonalBreed.Spring) != 0;
|
|
case Season.Summer:
|
|
case Season.PermanentSummer:
|
|
return (Props.breedingSeason & SeasonalBreed.Summer) != 0;
|
|
case Season.Fall:
|
|
return (Props.breedingSeason & SeasonalBreed.Fall) != 0;
|
|
case Season.Winter:
|
|
case Season.PermanentWinter:
|
|
return (Props.breedingSeason & SeasonalBreed.Winter) != 0;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected Pawn Fertilize()
|
|
{
|
|
if (cums.NullOrEmpty()) return null;
|
|
List<Cum> eligibleCum = cums.FindAll(cum => !cum.notcum && cum.FertVolume > 0 && cum.pawn != null && (RJWPregnancySettings.bestial_pregnancy_enabled || xxx.is_animal(Pawn) == xxx.is_animal(cum.pawn)));
|
|
if (eligibleCum.Count == 0) return null;
|
|
|
|
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))
|
|
return null;
|
|
|
|
Pawn.records.AddTo(VariousDefOf.AmountofFertilizedEggs, 1);
|
|
float selection = Rand.Range(0.0f, totalFertPower);
|
|
|
|
foreach (Cum cum in eligibleCum)
|
|
{
|
|
selection -= cum.FertVolume;
|
|
if (selection <= 0) return cum.pawn;
|
|
}
|
|
|
|
// We shouldn't reach here, but floating point errors exist, so just to be sure, select whomever came the most
|
|
return eligibleCum.MaxBy(cum => cum.FertVolume).pawn;
|
|
}
|
|
|
|
|
|
protected bool Implant()
|
|
{
|
|
if (eggs.NullOrEmpty()) return false;
|
|
|
|
List<Egg> deadeggs = new List<Egg>();
|
|
bool pregnant = false;
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
if (!egg.fertilized ||
|
|
egg.fertstage < minImplantAgeHours ||
|
|
egg.position < Math.Min(Props.lutealIntervalDays * 24 / 2, maxImplantDelayHours))
|
|
continue;
|
|
else if (egg.fertilizer == null)
|
|
{
|
|
if (Configurations.Debug) Log.Message($"Could not implant {Pawn}'s egg due to null father");
|
|
deadeggs.Add(egg);
|
|
continue;
|
|
}
|
|
else if (Pawn.health.hediffSet.GetFirstHediff<Hediff_InsectEgg>() != null || pregnancy is Hediff_MechanoidPregnancy)
|
|
{
|
|
if (Configurations.Debug) Log.Message($"Could not implant {Pawn}'s egg due to insect or mechanoid pregnancy");
|
|
deadeggs.Add(egg);
|
|
continue;
|
|
}
|
|
else if (Rand.Range(0.0f, 1.0f) <= Configurations.ImplantationChance * ImplantFactor * InterspeciesImplantFactor(egg.fertilizer))
|
|
{
|
|
if (Configurations.Debug) Log.Message($"Implanting fertilized egg of {Pawn} into {parent}, father {egg.fertilizer}");
|
|
if (pregnancy != null)
|
|
{
|
|
// TODO: Modified Biotech pregnancy
|
|
if (Configurations.PregnancySource == Configurations.PregnancyType.MultiplePregnancy && Configurations.EnableHeteroOvularTwins)
|
|
{
|
|
if (pregnancy is Hediff_MultiplePregnancy h)
|
|
{
|
|
if (Configurations.Debug) Log.Message($"Adding to existing pregnancy {h}");
|
|
h.AddNewBaby(Pawn, egg.fertilizer);
|
|
}
|
|
pregnant = true;
|
|
deadeggs.Add(egg);
|
|
}
|
|
else
|
|
{
|
|
pregnant = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Configurations.PregnancyType usePregnancy = xxx.is_human(Pawn) ? Configurations.PregnancySource : Configurations.PregnancyType.MultiplePregnancy;
|
|
switch (usePregnancy)
|
|
{
|
|
case Configurations.PregnancyType.BaseRJW:
|
|
|
|
if (Configurations.Debug) Log.Message($"Creating new base RJW pregnancy");
|
|
PregnancyHelper.PregnancyDecider(Pawn, egg.fertilizer);
|
|
// I hate having to do this, but it gets the newest pregnancy
|
|
List<Hediff_BasePregnancy> pregnancies = new List<Hediff_BasePregnancy>();
|
|
Pawn.health.hediffSet.GetHediffs(ref pregnancies);
|
|
pregnancy = pregnancies.MaxBy(hediff => hediff.loadID);
|
|
pregnant = true;
|
|
break;
|
|
|
|
case Configurations.PregnancyType.MultiplePregnancy:
|
|
if (Configurations.Debug) Log.Message($"Creating new menstruation pregnancy");
|
|
pregnancy = Hediff_BasePregnancy.Create<Hediff_MultiplePregnancy>(Pawn, egg.fertilizer);
|
|
pregnant = true;
|
|
deadeggs.Add(egg);
|
|
break;
|
|
|
|
case Configurations.PregnancyType.Biotech:
|
|
if (Configurations.Debug) Log.Message($"Creating new biotech pregnancy");
|
|
pregnancy = HediffMaker.MakeHediff(HediffDefOf.PregnantHuman, Pawn);
|
|
((Hediff_Pregnant)pregnancy).SetParents(Pawn, egg.fertilizer, PregnancyUtility.GetInheritedGeneSet(egg.fertilizer, Pawn));
|
|
Pawn.health.AddHediff(pregnancy);
|
|
break;
|
|
}
|
|
if (pregnancy is Hediff_BasePregnancy rjw_preg)
|
|
{
|
|
// TODO: advance biotech pregnancy
|
|
rjw_preg.p_start_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
|
|
{
|
|
if (Configurations.Debug) Log.Message($"Fertilized egg of {Pawn} failed to implant (father {egg.fertilizer})");
|
|
deadeggs.Add(egg);
|
|
}
|
|
}
|
|
|
|
if (pregnant && (Configurations.PregnancySource != Configurations.PregnancyType.MultiplePregnancy || !Configurations.EnableHeteroOvularTwins))
|
|
{
|
|
eggs.Clear();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
foreach (Egg egg in deadeggs)
|
|
{
|
|
eggs.Remove(egg);
|
|
}
|
|
}
|
|
return pregnant;
|
|
}
|
|
|
|
protected void BleedOut()
|
|
{
|
|
CumIn(Pawn, Rand.Range(0.02f * Configurations.BleedingAmount, 0.04f * Configurations.BleedingAmount), Translations.Menstrual_Blood, -5.0f, Pawn.def.race?.BloodDef ?? ThingDefOf.Filth_Blood);
|
|
Cum blood = GetNotCum(Translations.Menstrual_Blood);
|
|
if (blood != null) blood.Color = BloodColor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make filth ignoring absorber
|
|
/// </summary>
|
|
/// <param name="cum"></param>
|
|
/// <param name="amount"></param>
|
|
protected void MakeCumFilth(Cum cum, float amount)
|
|
{
|
|
if (Pawn.Map == null) return;
|
|
if (amount >= minmakefilthvalue) FilthMaker.TryMakeFilth(Pawn.Position, Pawn.Map, cum.FilthDef, cum.pawn?.LabelShort ?? "Unknown");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Absorb cum and return leaked amount
|
|
/// </summary>
|
|
/// <param name="amount"></param>
|
|
/// <param name="absorber"></param>
|
|
///
|
|
/// <returns></returns>
|
|
protected float AbsorbCum(float amount, Absorber absorber)
|
|
{
|
|
|
|
if (absorber == null)
|
|
{
|
|
//if (amount >= minmakefilthvalue) FilthMaker.TryMakeFilth(Pawn.Position, Pawn.Map, cum.FilthDef, cum.pawn.LabelShort);
|
|
return amount;
|
|
}
|
|
|
|
float absorbable = absorber.GetStatValue(VariousDefOf.MaxAbsorbable);
|
|
absorber.SetColor(Colors.CMYKLerp(GetCumMixtureColor, absorber.DrawColor, 1f - amount / absorbable));
|
|
if (absorber.dirty)
|
|
{
|
|
//if (absorber.LeakAfterDirty) FilthMaker.TryMakeFilth(Pawn.Position, Pawn.Map, cum.FilthDef, cum.pawn.LabelShort);
|
|
return amount;
|
|
}
|
|
absorber.absorbedfluids += amount;
|
|
if (absorber.absorbedfluids > absorbable && !Pawn.apparel.IsLocked(absorber))
|
|
{
|
|
absorber.def = absorber.DirtyDef;
|
|
//absorber.fluidColor = GetCumMixtureColor;
|
|
absorber.dirty = true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
protected float MakeCumFilthMixture(float amount, List<string> cumlabels)
|
|
{
|
|
if (Pawn.Map == null) return 0;
|
|
if (amount >= minmakefilthvalue)
|
|
{
|
|
FilthMaker_Colored.TryMakeFilth(Pawn.Position, Pawn.Map, VariousDefOf.FilthMixture, cumlabels, GetCumMixtureColor, false);
|
|
}
|
|
return amount;
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void EggDecay()
|
|
{
|
|
List<Egg> deadeggs = new List<Egg>();
|
|
foreach (Egg egg in eggs)
|
|
{
|
|
egg.position += Configurations.CycleAcceleration;
|
|
if (egg.fertilized) egg.fertstage += Configurations.CycleAcceleration;
|
|
else
|
|
{
|
|
egg.lifespanhrs -= Configurations.CycleAcceleration;
|
|
if (egg.lifespanhrs < 0) deadeggs.Add(egg);
|
|
}
|
|
}
|
|
foreach (Egg egg in deadeggs)
|
|
{
|
|
eggs.Remove(egg);
|
|
}
|
|
}
|
|
|
|
protected void AddCrampPain()
|
|
{
|
|
Hediff hediff = HediffMaker.MakeHediff(VariousDefOf.Hediff_MenstrualCramp, Pawn);
|
|
hediff.Severity = crampPain * Rand.Range(0.9f, 1.1f);
|
|
HediffCompProperties_SeverityPerDay Prop = (HediffCompProperties_SeverityPerDay)hediff.TryGetComp<HediffComp_SeverityPerDay>().props;
|
|
Prop.severityPerDay = -hediff.Severity / (currentIntervalHours / 24) * Configurations.CycleAcceleration;
|
|
Pawn.health.AddHediff(hediff, Genital_Helper.get_genitalsBPR(Pawn));
|
|
}
|
|
|
|
protected virtual void FollicularAction()
|
|
{
|
|
if (!IsBreedingSeason())
|
|
{
|
|
estrusflag = false;
|
|
GoNextStage(Stage.Anestrus);
|
|
return;
|
|
}
|
|
else if (curStageHrs >= currentIntervalHours)
|
|
{
|
|
GoOvulatoryStage();
|
|
}
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
if (!estrusflag && curStageHrs > currentIntervalHours - Props.estrusDaysBeforeOvulation * 24)
|
|
{
|
|
estrusflag = true;
|
|
SetEstrus();
|
|
}
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
|
|
protected virtual void OvulatoryAction()
|
|
{
|
|
estrusflag = false;
|
|
int eggnum;
|
|
try
|
|
{
|
|
eggnum = Math.Max((int)Rand.ByCurve(Pawn.def.race.litterSizeCurve), 1);
|
|
}
|
|
catch (NullReferenceException)
|
|
{
|
|
eggnum = 1;
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
Log.Warning($"Invalid litterSizeCurve for {Pawn.def}: {e}");
|
|
eggnum = 1;
|
|
}
|
|
eggnum += eggstack;
|
|
|
|
for (int i = 0; i < eggnum; i++)
|
|
eggs.Add(new Egg((int)(Props.eggLifespanDays * 24 / CycleFactor)));
|
|
ovarypower -= eggnum;
|
|
|
|
eggstack = 0;
|
|
if (EggHealth <= 0)
|
|
{
|
|
eggs.Clear();
|
|
ovarypower = 0;
|
|
GoNextStage(Stage.Infertile);
|
|
}
|
|
else
|
|
{
|
|
GoNextStage(Stage.Luteal);
|
|
}
|
|
}
|
|
|
|
protected virtual void LutealAction()
|
|
{
|
|
if (curStageHrs > currentIntervalHours)
|
|
{
|
|
eggs.Clear();
|
|
if (EggHealth < 1f / 4f || (EggHealth < 1f / 3f && Rand.Range(0.0f, 1.0f) < 0.3f)) //skips bleeding
|
|
{
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
else
|
|
{
|
|
GoFollicularOrBleeding();
|
|
}
|
|
}
|
|
else if (!eggs.NullOrEmpty())
|
|
{
|
|
FertilizationCheck();
|
|
EggDecay();
|
|
if (Implant())
|
|
{
|
|
GoNextStage(Stage.Pregnant);
|
|
}
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
|
|
}
|
|
|
|
protected virtual void BleedingAction()
|
|
{
|
|
if (curStageHrs >= currentIntervalHours)
|
|
{
|
|
Hediff hediff = Pawn.health.hediffSet.GetFirstHediffOfDef(VariousDefOf.Hediff_MenstrualCramp);
|
|
if (hediff != null) Pawn.health.RemoveHediff(hediff);
|
|
int totalFollicularHours = PeriodRandomizer(Stage.Follicular); // The total amount of time for both bleeding and follicular
|
|
if (totalFollicularHours <= currentIntervalHours) // We've bled for so long that we completely missed the follicular phase
|
|
GoOvulatoryStage();
|
|
else
|
|
{
|
|
currentIntervalHours = totalFollicularHours - currentIntervalHours; // I.e., the remaining follicular hours equals the total minus the bleeding hours elapsed
|
|
GoNextStage(Stage.Follicular, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (curStageHrs < currentIntervalHours / 4) for (int i = 0; i < Configurations.CycleAcceleration; i++) BleedOut();
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
|
|
protected virtual void PregnantAction()
|
|
{
|
|
if (!eggs.NullOrEmpty())
|
|
{
|
|
FertilizationCheck();
|
|
EggDecay();
|
|
Implant();
|
|
}
|
|
|
|
if (pregnancy != null && Pawn.health.hediffSet.hediffs.Contains(pregnancy))
|
|
{
|
|
curStageHrs += 1;
|
|
StayCurrentStageConst(Stage.Pregnant);
|
|
}
|
|
else
|
|
{
|
|
if (pregnancy != null) pregnancy = null;
|
|
GoNextStage(Stage.Recover);
|
|
}
|
|
}
|
|
|
|
protected virtual void RecoverAction()
|
|
{
|
|
if (curStageHrs >= currentIntervalHours)
|
|
{
|
|
if (Pawn.health.capacities.GetLevel(xxx.reproduction) == 0 || EggHealth <= 0 || Pawn.SterileGenes())
|
|
{
|
|
GoNextStage(Stage.Infertile);
|
|
}
|
|
else if (!IsBreedingSeason())
|
|
{
|
|
GoNextStage(Stage.Anestrus);
|
|
}
|
|
else
|
|
{
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curStageHrs += Configurations.CycleAcceleration;
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
|
|
protected virtual void InfertileAction()
|
|
{
|
|
if (Pawn.health.capacities.GetLevel(xxx.reproduction) <= 0 || EggHealth <= 0 || Pawn.SterileGenes())
|
|
{
|
|
StayCurrentStageConst(Stage.Infertile);
|
|
}
|
|
else
|
|
{
|
|
bool breedingSeason = IsBreedingSeason();
|
|
GoNextStage(breedingSeason ? Stage.Follicular : Stage.Anestrus, breedingSeason);
|
|
}
|
|
}
|
|
|
|
protected virtual void AnestrusAction()
|
|
{
|
|
if (IsBreedingSeason())
|
|
{
|
|
GoFollicularOrBleeding();
|
|
}
|
|
else
|
|
{
|
|
StayCurrentStage();
|
|
}
|
|
}
|
|
|
|
protected virtual void ThoughtCumInside(Pawn cummer)
|
|
{
|
|
if (!xxx.is_human(Pawn) || !xxx.is_human(cummer)) return;
|
|
|
|
if ((cummer.Has(Quirk.Teratophile) != (Pawn.GetStatValue(StatDefOf.PawnBeauty) >= 0)) ||
|
|
cummer.Has(Quirk.ImpregnationFetish) ||
|
|
cummer.Has(Quirk.Breeder))
|
|
{
|
|
if (cummer.relations.OpinionOf(Pawn) <= -25)
|
|
{
|
|
cummer.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideM, Pawn);
|
|
}
|
|
else
|
|
{
|
|
cummer.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideM, Pawn);
|
|
}
|
|
}
|
|
|
|
if (IsDangerDay)
|
|
{
|
|
if (Pawn.Has(Quirk.Breeder) || Pawn.Has(Quirk.ImpregnationFetish))
|
|
{
|
|
Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetish, cummer);
|
|
}
|
|
else if (Pawn.relations.OpinionOf(cummer) <= -5)
|
|
{
|
|
Pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDefWhereOtherPawnIs(VariousDefOf.CameInsideF, cummer);
|
|
Pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDefWhereOtherPawnIs(VariousDefOf.HaterCameInsideFEstrus, cummer);
|
|
Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideF, cummer);
|
|
}
|
|
else if (Pawn.IsInEstrus() && Pawn.relations.OpinionOf(cummer) < RJWHookupSettings.MinimumRelationshipToHookup)
|
|
{
|
|
Pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDefWhereOtherPawnIs(VariousDefOf.CameInsideF, cummer);
|
|
Pawn.needs.mood.thoughts.memories.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) Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFLowFert, cummer);
|
|
else Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideF, cummer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Pawn.Has(Quirk.Breeder) || Pawn.Has(Quirk.ImpregnationFetish))
|
|
{
|
|
Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.CameInsideFFetishSafe, cummer);
|
|
}
|
|
else if (Pawn.relations.OpinionOf(cummer) <= -5)
|
|
{
|
|
Pawn.needs.mood.thoughts.memories.TryGainMemory(VariousDefOf.HaterCameInsideFSafe, cummer);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void TaleCumInside(Pawn cummer)
|
|
{
|
|
// Only make the tale for human-on-human, consentual sex. Otherwise the art just gets too hard to phrase properly
|
|
if (!xxx.is_human(Pawn) || !xxx.is_human(cummer) || Pawn == cummer) return;
|
|
if (Pawn.CurJobDef != xxx.casual_sex && Pawn.CurJobDef != xxx.gettin_loved) return;
|
|
if (!(Pawn.IsColonist || Pawn.IsPrisonerOfColony) && !(cummer.IsColonist || cummer.IsPrisonerOfColony)) return;
|
|
if (!IsDangerDay) return;
|
|
TaleRecorder.RecordTale(VariousDefOf.TaleCameInside, new object[] { cummer, Pawn });
|
|
}
|
|
|
|
protected void GoNextStage(Stage nextstage, bool calculateHours = true)
|
|
{
|
|
curStageHrs = 0;
|
|
if (calculateHours) currentIntervalHours = PeriodRandomizer(nextstage);
|
|
curStage = nextstage;
|
|
}
|
|
|
|
protected virtual void GoOvulatoryStage()
|
|
{
|
|
if (EggHealth < 1.0f / 3.0f && Rand.Range(0.0f, 1.0f) < 0.2f) // Skip ovulation if deep into climacteric
|
|
{
|
|
estrusflag = false;
|
|
GoNextStage(Stage.Luteal);
|
|
}
|
|
else GoNextStage(Stage.Ovulatory);
|
|
}
|
|
|
|
//stage can be interrupted in other reasons
|
|
protected void StayCurrentStage()
|
|
{
|
|
}
|
|
|
|
//stage never changes
|
|
protected void StayCurrentStageConst(Stage curstage)
|
|
{
|
|
}
|
|
|
|
protected void GoFollicularOrBleeding()
|
|
{
|
|
if (Props.bleedingIntervalDays == 0)
|
|
{
|
|
GoNextStage(Stage.Follicular);
|
|
}
|
|
else
|
|
{
|
|
GoNextStage(Stage.Bleeding);
|
|
if (crampPain >= 0.05f)
|
|
{
|
|
AddCrampPain();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected int PeriodRandomizer(Stage stage)
|
|
{
|
|
float variabilityFactor = (EggHealth < 1.0f) ? 6.0f : 1.0f;
|
|
// Most cycle lengthening or shortening occurs in the follicular phase, so weight towards that
|
|
switch (stage)
|
|
{
|
|
case Stage.Follicular:
|
|
return (int)(Props.follicularIntervalDays * 24 * (1 + Rand.Range(-cycleVariability, cycleVariability) * 1.5f * variabilityFactor) / (1 + (cycleSpeed - 1) * 1.5f));
|
|
case Stage.Luteal:
|
|
return (int)(Props.lutealIntervalDays * 24 * (1 + Rand.Range(-cycleVariability, cycleVariability) * 0.5f * variabilityFactor) / (1 + (cycleSpeed - 1) * 0.5f));
|
|
case Stage.Bleeding:
|
|
return (int)(Props.bleedingIntervalDays * 24 * (1 + Rand.Range(-cycleVariability, cycleVariability) * 0.5f * variabilityFactor) / (1 + (cycleSpeed - 1) * 0.5f));
|
|
case Stage.Recover:
|
|
return (int)(Props.recoveryIntervalDays * 24 * Rand.Range(0.95f, 1.05f));
|
|
case Stage.Pregnant:
|
|
return (int)MenstruationUtility.GestationHours(pregnancy);
|
|
default: // Often unused
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
protected float InterspeciesImplantFactor(Pawn fertilizer)
|
|
{
|
|
if (fertilizer.def.defName == Pawn.def.defName) return 1.0f;
|
|
else
|
|
{
|
|
if (RJWPregnancySettings.complex_interspecies) return SexUtility.BodySimilarity(Pawn, fertilizer);
|
|
else return RJWPregnancySettings.interspecies_impregnation_modifier;
|
|
}
|
|
}
|
|
|
|
protected 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);
|
|
}
|
|
|
|
protected Stage RandomStage()
|
|
{
|
|
Stage stage = Rand.ElementByWeight(
|
|
Stage.Follicular, Props.follicularIntervalDays - Props.bleedingIntervalDays,
|
|
Stage.Luteal, Props.lutealIntervalDays,
|
|
Stage.Bleeding, Props.bleedingIntervalDays);
|
|
|
|
switch (stage)
|
|
{
|
|
case Stage.Follicular:
|
|
curStageHrs = Rand.Range(0, (Props.follicularIntervalDays - Props.bleedingIntervalDays) * 24);
|
|
break;
|
|
case Stage.Luteal:
|
|
curStageHrs = Rand.Range(0, Props.lutealIntervalDays * 24);
|
|
break;
|
|
case Stage.Bleeding:
|
|
curStageHrs = Rand.Range(0, Props.bleedingIntervalDays * 24);
|
|
break;
|
|
}
|
|
return stage;
|
|
}
|
|
|
|
// Searches for a pregnancy unclaimed by any womb and put it in this one
|
|
public void TakeLoosePregnancy()
|
|
{
|
|
if (pregnancy != null) return;
|
|
|
|
IEnumerable<Hediff> pregnancies = Pawn.health.hediffSet.hediffs.Where(hediff =>
|
|
hediff is Hediff_BasePregnancy ||
|
|
hediff is Hediff_Pregnant ||
|
|
hediff is Hediff_Labor ||
|
|
hediff is Hediff_LaborPushing);
|
|
|
|
pregnancy =
|
|
pregnancies.Except(
|
|
Pawn.GetMenstruationComps().Select(comp => comp.pregnancy).Where(preg => preg != null)
|
|
).FirstOrDefault();
|
|
if (pregnancy != null)
|
|
GoNextStage(Stage.Pregnant);
|
|
}
|
|
|
|
public void CopyCycleProperties(HediffComp_Menstruation original)
|
|
{
|
|
cycleSpeed = original.cycleSpeed;
|
|
cycleVariability = original.cycleVariability;
|
|
ovarypower = original.ovarypower;
|
|
crampPain = original.crampPain;
|
|
}
|
|
|
|
public class Egg : IExposable
|
|
{
|
|
public bool fertilized;
|
|
public int lifespanhrs;
|
|
public Pawn fertilizer;
|
|
public int position;
|
|
public int fertstage = 0;
|
|
|
|
public Egg()
|
|
{
|
|
fertilized = false;
|
|
lifespanhrs = (int)(96 * Configurations.EggLifespanMultiplier);
|
|
fertilizer = null;
|
|
position = 0;
|
|
}
|
|
|
|
public Egg(int lifespanhrs)
|
|
{
|
|
fertilized = false;
|
|
this.lifespanhrs = (int)(lifespanhrs * Configurations.EggLifespanMultiplier);
|
|
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);
|
|
Scribe_Values.Look(ref fertstage, "fertstage", fertstage, true);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
}
|
|
}
|