2022-10-24 01:02:02 +00:00
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 ;
2022-12-25 16:51:42 +00:00
const float pulloutSuccessRate = 0.8f ;
2022-12-25 17:54:49 +00:00
const float fetishPulloutSuccessModifier = 0.25f ;
2022-10-24 01:02:02 +00:00
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 ,
2022-10-24 02:29:01 +00:00
Infertile ,
2022-10-24 01:02:02 +00:00
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 ;
2022-10-27 20:57:25 +00:00
// RJW pregnancy, or Biotech pregnancy/labor/laborpushing
protected Hediff pregnancy = null ;
2022-10-24 01:02:02 +00:00
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 )
} ;
2022-10-27 20:57:25 +00:00
public Hediff Pregnancy {
2022-10-24 01:02:02 +00:00
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
{
2022-11-01 03:53:57 +00:00
if ( opcache > 0 ) return opcache ;
2022-10-24 01:02:02 +00:00
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 ) ) ;
2022-11-01 03:53:57 +00:00
if ( opcache = = 0 ) opcache = 1 ;
2022-10-24 01:02:02 +00:00
return opcache ;
}
}
// >= 1: Normal cycles
// 1 - 0: Climacteric
// <= 0: Menopause
public float EggHealth
{
get
{
2022-11-05 14:57:44 +00:00
if ( ! Configurations . EnableMenopause | | Props . infertile ) return Mathf . Max ( 1.0f , ovarypower / OvaryPowerThreshold ) ;
2022-10-24 01:02:02 +00:00
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 :
2022-10-24 02:29:01 +00:00
case Stage . Infertile :
2022-10-24 01:02:02 +00:00
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 :
2022-10-24 02:29:01 +00:00
case Stage . Infertile :
2022-10-24 01:02:02 +00:00
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
{
2022-11-04 19:06:03 +00:00
if ( Pawn . HasIUD ( ) ) return false ;
2022-10-24 01:02:02 +00:00
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 ) ;
2022-10-27 20:57:25 +00:00
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 _ :
2022-10-28 22:01:28 +00:00
return 1.0f ;
2022-10-27 20:57:25 +00:00
}
if ( is_discovered | | Configurations . infoDetail = = Configurations . DetailLevel . All ) return pregnancy . Severity ;
2022-10-24 01:02:02 +00:00
// 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 ;
}
2022-12-25 23:41:20 +00:00
BeforeSimulator ( ) ;
2022-10-24 01:02:02 +00:00
2022-11-04 17:51:01 +00:00
if ( pregnancy = = null & & ( Pawn . health . capacities . GetLevel ( xxx . reproduction ) < = 0 | | EggHealth < = 0 | | Pawn . SterileGenes ( ) ) ) curStage = Stage . Infertile ;
2022-10-24 01:02:02 +00:00
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 ;
2022-10-24 02:29:01 +00:00
case Stage . Infertile :
InfertileAction ( ) ;
2022-10-24 01:02:02 +00:00
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 ;
}
2022-10-27 20:57:25 +00:00
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 ;
}
2022-10-24 01:02:02 +00:00
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>
2022-12-25 19:46:01 +00:00
/// <param name="precum"></param>
public void CumIn ( Pawn pawn , float volume , float fertility = 1.0f , bool precum = false )
2022-10-24 01:02:02 +00:00
{
if ( volume < = 0 ) return ;
2022-12-25 19:46:01 +00:00
if ( ! precum & & fertility > 0 & & IsDangerDay & & pawn . relations . GetPregnancyApproachForPartner ( Pawn ) = = PregnancyApproach . AvoidPregnancy )
2022-12-25 17:54:49 +00:00
{
float successChance = pulloutSuccessRate ;
if ( pawn . Has ( Quirk . ImpregnationFetish ) ) successChance * = fetishPulloutSuccessModifier ;
if ( Pawn . Has ( Quirk . ImpregnationFetish ) ) successChance * = fetishPulloutSuccessModifier ;
if ( Rand . Chance ( successChance ) ) return ;
}
2022-11-04 19:06:03 +00:00
if ( Pawn . HasIUD ( ) ) fertility / = 100f ;
2022-10-24 01:02:02 +00:00
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 ) )
{
2022-12-25 19:46:01 +00:00
cum . MergeWithCum ( volume , fertility ) ;
2022-10-24 01:02:02 +00:00
merged = true ;
}
cum . DismishForce ( cumoutrate ) ;
}
2022-12-25 19:46:01 +00:00
if ( ! merged ) cums . Add ( new Cum ( pawn , volume * ( 1 - cumoutrate ) , fertility ) ) ;
2022-10-24 01:02:02 +00:00
}
else
{
bool merged = false ;
if ( ! cums . NullOrEmpty ( ) ) foreach ( Cum cum in cums )
{
if ( cum . pawn . Equals ( pawn ) )
{
2022-12-25 19:46:01 +00:00
cum . MergeWithCum ( volume , fertility ) ;
2022-10-24 01:02:02 +00:00
merged = true ;
}
}
2022-12-25 19:46:01 +00:00
if ( ! merged ) cums . Add ( new Cum ( pawn , volume , fertility ) ) ;
2022-10-24 01:02:02 +00:00
}
cumd = TotalCumPercent - cumd ;
2022-12-25 19:46:01 +00:00
if ( ! precum )
{
Pawn . records . AddTo ( VariousDefOf . AmountofCreampied , volume ) ;
AfterCumIn ( pawn ) ;
AfterFluidIn ( cumd ) ;
}
2022-10-24 01:02:02 +00:00
}
/// <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 ;
2022-11-04 19:06:03 +00:00
if ( Pawn . HasIUD ( ) ) antisperm = 0.70f + asafactor ;
2022-10-24 01:02:02 +00:00
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 )
{
2022-11-10 16:19:56 +00:00
if ( Pawn . health . capacities . GetLevel ( xxx . reproduction ) < = 0 | | Pawn . SterileGenes ( ) ) curStage = Stage . Infertile ;
2022-10-24 01:02:02 +00:00
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 ( ) ;
2022-12-25 23:41:20 +00:00
InitializeExtraValues ( ) ;
2022-10-24 01:02:02 +00:00
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 ;
}
2022-12-25 23:41:20 +00:00
protected virtual void InitializeExtraValues ( )
{
}
2022-10-24 01:02:02 +00:00
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 )
{
2022-10-24 02:29:01 +00:00
curStage = Stage . Infertile ;
2022-10-24 01:02:02 +00:00
}
}
}
public void RecoverOvary ( float multiply = 1.2f )
{
ovarypower = Math . Max ( 0 , ( int ) ( ovarypower * multiply ) ) ;
}
2022-12-25 23:41:20 +00:00
protected virtual void BeforeSimulator ( )
{
CumOut ( ) ;
}
2022-10-24 01:02:02 +00:00
2022-12-25 23:41:20 +00:00
protected virtual void AfterSimulator ( )
2022-10-24 01:02:02 +00:00
{
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 ;
}
2022-10-24 02:23:00 +00:00
else if ( Pawn . health . hediffSet . GetFirstHediff < Hediff_InsectEgg > ( ) ! = null | | pregnancy is Hediff_MechanoidPregnancy )
2022-10-24 01:02:02 +00:00
{
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 )
{
2022-10-30 17:36:38 +00:00
// TODO: Modified Biotech pregnancy
if ( Configurations . PregnancySource = = Configurations . PregnancyType . MultiplePregnancy & & Configurations . EnableHeteroOvularTwins )
2022-10-24 01:02:02 +00:00
{
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
{
2022-10-30 17:36:38 +00:00
Configurations . PregnancyType usePregnancy = xxx . is_human ( Pawn ) ? Configurations . PregnancySource : Configurations . PregnancyType . MultiplePregnancy ;
switch ( usePregnancy )
2022-10-24 01:02:02 +00:00
{
2022-10-30 17:36:38 +00:00
case Configurations . PregnancyType . BaseRJW :
if ( Configurations . Debug ) Log . Message ( $"Creating new base RJW pregnancy" ) ;
2022-11-16 15:46:02 +00:00
PregnancyHelper . AddPregnancyHediff ( Pawn , egg . fertilizer ) ;
2022-10-30 17:36:38 +00:00
// 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 ;
2022-10-24 01:02:02 +00:00
}
2022-10-27 20:57:25 +00:00
if ( pregnancy is Hediff_BasePregnancy rjw_preg )
{
2022-10-30 17:36:38 +00:00
// TODO: advance biotech pregnancy
2022-10-27 20:57:25 +00:00
rjw_preg . p_start_tick - = egg . fertstage / Configurations . CycleAcceleration * GenDate . TicksPerHour ;
rjw_preg . p_end_tick - = egg . fertstage / Configurations . CycleAcceleration * GenDate . TicksPerHour ;
}
2022-10-30 17:36:38 +00:00
if ( ! ( pregnancy is Hediff_MultiplePregnancy ) ) break ;
2022-10-24 01:02:02 +00:00
}
}
else
{
if ( Configurations . Debug ) Log . Message ( $"Fertilized egg of {Pawn} failed to implant (father {egg.fertilizer})" ) ;
deadeggs . Add ( egg ) ;
}
}
2022-10-30 17:36:38 +00:00
if ( pregnant & & ( Configurations . PregnancySource ! = Configurations . PregnancyType . MultiplePregnancy | | ! Configurations . EnableHeteroOvularTwins ) )
2022-10-24 01:02:02 +00:00
{
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 ;
2022-10-24 02:29:01 +00:00
GoNextStage ( Stage . Infertile ) ;
2022-10-24 01:02:02 +00:00
}
else
{
GoNextStage ( Stage . Luteal ) ;
}
}
protected virtual void LutealAction ( )
{
2022-12-25 22:32:52 +00:00
if ( curStageHrs > = currentIntervalHours )
2022-10-24 01:02:02 +00:00
{
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 )
{
2022-11-05 14:59:44 +00:00
if ( Pawn . health . capacities . GetLevel ( xxx . reproduction ) = = 0 | | EggHealth < = 0 | | Pawn . SterileGenes ( ) )
2022-10-24 01:02:02 +00:00
{
2022-10-24 02:29:01 +00:00
GoNextStage ( Stage . Infertile ) ;
2022-10-24 01:02:02 +00:00
}
else if ( ! IsBreedingSeason ( ) )
{
GoNextStage ( Stage . Anestrus ) ;
}
else
{
GoNextStage ( Stage . Follicular ) ;
}
}
else
{
curStageHrs + = Configurations . CycleAcceleration ;
StayCurrentStage ( ) ;
}
}
2022-10-24 02:29:01 +00:00
protected virtual void InfertileAction ( )
2022-10-24 01:02:02 +00:00
{
2022-11-05 14:59:44 +00:00
if ( Pawn . health . capacities . GetLevel ( xxx . reproduction ) < = 0 | | EggHealth < = 0 | | Pawn . SterileGenes ( ) )
2022-10-24 01:02:02 +00:00
{
2022-10-24 02:29:01 +00:00
StayCurrentStageConst ( Stage . Infertile ) ;
2022-10-24 01:02:02 +00:00
}
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 ;
}
2022-10-27 20:57:25 +00:00
// Searches for a pregnancy unclaimed by any womb and put it in this one
2022-10-24 01:02:02 +00:00
public void TakeLoosePregnancy ( )
{
if ( pregnancy ! = null ) return ;
2022-10-27 20:57:25 +00:00
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 ) ;
2022-10-24 02:23:00 +00:00
2022-10-24 01:02:02 +00:00
pregnancy =
2022-10-24 02:23:00 +00:00
pregnancies . Except (
2022-10-24 01:02:02 +00:00
Pawn . GetMenstruationComps ( ) . Select ( comp = > comp . pregnancy ) . Where ( preg = > preg ! = null )
) . FirstOrDefault ( ) ;
if ( pregnancy ! = null )
GoNextStage ( Stage . Pregnant ) ;
}
2022-12-25 23:41:20 +00:00
public virtual void CopyCycleProperties ( HediffComp_Menstruation original )
2022-10-24 01:02:02 +00:00
{
cycleSpeed = original . cycleSpeed ;
cycleVariability = original . cycleVariability ;
ovarypower = original . ovarypower ;
crampPain = original . crampPain ;
}
2022-12-25 03:44:07 +00:00
public int EggsRestoredPerBiosculptor ( float yearsWorth )
{
return Math . Max ( 1 , ( int ) ( ( float ) RaceCyclesPerYear ( ) * yearsWorth ) ) ;
}
public void RestoreEggs ( float yearsWorth )
{
ovarypower + = EggsRestoredPerBiosculptor ( yearsWorth ) ;
}
2022-10-24 01:02:02 +00:00
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 )
{
}
}
}