mirror of
https://gitgud.io/lutepickle/rjw_menstruation.git
synced 2024-08-14 22:46:52 +00:00
382 lines
14 KiB
C#
382 lines
14 KiB
C#
using RimWorld;
|
|
using rjw;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using Verse;
|
|
|
|
namespace RJW_Menstruation
|
|
{
|
|
public class CompProperties_Breast : HediffCompProperties
|
|
{
|
|
public static readonly ColorInt DefaultBlacknippleColor = new ColorInt(55, 20, 0);
|
|
public string BreastTex = "Breasts/Breast";
|
|
public ColorInt BlacknippleColor = new ColorInt(55, 20, 0);
|
|
|
|
|
|
public Color BlackNippleColor
|
|
{
|
|
get
|
|
{
|
|
return BlacknippleColor.ToColor;
|
|
}
|
|
}
|
|
|
|
|
|
public CompProperties_Breast()
|
|
{
|
|
compClass = typeof(HediffComp_Breast);
|
|
}
|
|
}
|
|
|
|
public class HediffComp_Breast : HediffComp
|
|
{
|
|
public const int tickInterval = GenDate.TicksPerHour * 3 / 2;
|
|
public const float breastGrowthStart = 1f / 6f;
|
|
public const float breastGrowthEnd = 1f / 3f;
|
|
public static readonly SimpleCurve nippleTransitions = new SimpleCurve()
|
|
{
|
|
new CurvePoint(0f,0f),
|
|
new CurvePoint(0.1f,0f),
|
|
new CurvePoint(0.333f,0.167f),
|
|
new CurvePoint(0.667f,0.833f),
|
|
new CurvePoint(1.0f,1.0f)
|
|
};
|
|
public const float nippleChange = 0.2f;
|
|
|
|
public CompProperties_Breast Props;
|
|
|
|
protected long ageOfLastBirth = 0;
|
|
protected float maxBreastIncrement = -1f;
|
|
protected float breastSizeIncreased = 0f;
|
|
protected string debugGrowthStatus = "(Growth/shrink not yet calculated; run for 1.5h to update)";
|
|
protected float nippleProgress = 0f;
|
|
protected float baseAlpha = -1f; // Will grow in response to pregnancy
|
|
protected float baseAreola = -1f;
|
|
protected float baseNipple = -1f;
|
|
protected float cachedAlpha = -1f; // Calculated dynamically instead of saved
|
|
protected float cachedAreola = -1f; // Actual size = these * breast size
|
|
protected float cachedNipple = -1f;
|
|
protected float babyHalfAge = -1f;
|
|
|
|
protected Color cachedColor;
|
|
protected bool loaded = false;
|
|
|
|
protected float BabyHalfAge
|
|
{
|
|
get
|
|
{
|
|
if (babyHalfAge > 0f) return babyHalfAge;
|
|
List<LifeStageAge> ages = parent.pawn.def.race.lifeStageAges;
|
|
if (ages?.Count > 1)
|
|
babyHalfAge = ages[1].minAge / 2;
|
|
|
|
if (babyHalfAge <= 0) babyHalfAge = 1.2f / 2; // Default to human
|
|
|
|
if (RJWPregnancySettings.phantasy_pregnancy)
|
|
babyHalfAge /= GenDate.DaysPerYear;
|
|
|
|
return babyHalfAge;
|
|
}
|
|
}
|
|
|
|
protected void ShrinkBreasts()
|
|
{
|
|
// The natural rate will take them from full to empty during the second half of their child's babyhood
|
|
float shrinkRate = tickInterval * MaxBreastIncrement / (BabyHalfAge * GenDate.TicksPerYear);
|
|
float shrinkAmount = Mathf.Min(shrinkRate, breastSizeIncreased);
|
|
breastSizeIncreased -= shrinkAmount;
|
|
parent.Severity -= shrinkAmount;
|
|
}
|
|
|
|
protected float MaxBreastIncrement
|
|
{
|
|
get
|
|
{
|
|
return maxBreastIncrement * Configurations.MaxBreastIncrementFactor;
|
|
}
|
|
}
|
|
|
|
public Color NippleColor
|
|
{
|
|
get
|
|
{
|
|
return cachedColor;
|
|
}
|
|
}
|
|
public float Alpha
|
|
{
|
|
get
|
|
{
|
|
return cachedAlpha;
|
|
}
|
|
}
|
|
public float NippleSize
|
|
{
|
|
get
|
|
{
|
|
return cachedNipple * parent.Severity;
|
|
}
|
|
}
|
|
public float AreolaSize
|
|
{
|
|
get
|
|
{
|
|
return cachedAreola * parent.Severity;
|
|
}
|
|
}
|
|
|
|
public float BreastSizeIncreased
|
|
{
|
|
get
|
|
{
|
|
return breastSizeIncreased;
|
|
}
|
|
}
|
|
|
|
public override void CompExposeData()
|
|
{
|
|
base.CompExposeData();
|
|
|
|
|
|
if (Scribe.mode == LoadSaveMode.LoadingVars)
|
|
{ // For compatibility
|
|
Scribe_Values.Look(ref baseAlpha, "alphaPermanent", baseAlpha / 2, false);
|
|
Scribe_Values.Look(ref baseAreola, "areolaSizePermanent", baseAreola / 2, false);
|
|
Scribe_Values.Look(ref baseNipple, "nippleSizePermanent", baseNipple / 2, false);
|
|
baseAlpha *= 2;
|
|
baseAreola *= 2;
|
|
baseNipple *= 2;
|
|
}
|
|
Scribe_Values.Look(ref ageOfLastBirth, "ageOfLastBirth", ageOfLastBirth, true);
|
|
Scribe_Values.Look(ref maxBreastIncrement, "maxBreastIncrement", maxBreastIncrement, true);
|
|
Scribe_Values.Look(ref breastSizeIncreased, "breastSizeIncreased", breastSizeIncreased, true);
|
|
Scribe_Values.Look(ref nippleProgress, "nippleProgress", nippleProgress, true);
|
|
Scribe_Values.Look(ref baseAlpha, "baseAlpha", baseAlpha, true);
|
|
Scribe_Values.Look(ref baseAreola, "baseAreola", baseAreola, true);
|
|
Scribe_Values.Look(ref baseNipple, "baseNipple", baseNipple, true);
|
|
}
|
|
|
|
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 (
|
|
!parent.pawn.IsHashIntervalTick(tickInterval) ||
|
|
!parent.pawn.Spawned || // TODO: Add option to simulate off-map pawns
|
|
parent.pawn.health.Dead
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
CalculateBreastSize();
|
|
CalculateNipples();
|
|
UpdateNipples();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error($"Error processing breasts of {parent.pawn}: {ex}");
|
|
}
|
|
|
|
}
|
|
|
|
public override void CompPostPostAdd(DamageInfo? dinfo)
|
|
{
|
|
if (!loaded) Initialize();
|
|
if (ageOfLastBirth > parent.pawn.ageTracker.AgeChronologicalTicks) ageOfLastBirth = CalculateLastBirth(); // catch transplant issues
|
|
}
|
|
|
|
public override void CompPostPostRemoved()
|
|
{
|
|
if (parent.pawn.health.hediffSet.hediffs.Contains(parent))
|
|
{
|
|
Log.Warning($"Attempted to remove breast comp from wrong pawn ({parent.pawn}).");
|
|
return;
|
|
}
|
|
base.CompPostPostRemoved();
|
|
}
|
|
|
|
protected long CalculateLastBirth()
|
|
{
|
|
long youngestAge = (long)(BabyHalfAge * GenDate.TicksPerYear) * -2; // So a newborn isn't considered a new mother, either
|
|
if ((parent.pawn.relations?.ChildrenCount ?? 0) > 0)
|
|
{
|
|
foreach (Pawn child in parent.pawn.relations.Children)
|
|
{
|
|
bool isFetus = false;
|
|
if (parent.pawn.health.hediffSet.GetHediffs<Hediff_BasePregnancy>().Any(preg => preg.babies.Contains(child)))
|
|
{
|
|
isFetus = true;
|
|
break;
|
|
}
|
|
|
|
if (
|
|
parent.pawn.ageTracker.BirthAbsTicks - child.ageTracker.BirthAbsTicks > ageOfLastBirth &&
|
|
!isFetus &&
|
|
child.GetMother() == parent.pawn // Don't do Dad's boobs
|
|
)
|
|
youngestAge = parent.pawn.ageTracker.BirthAbsTicks - child.ageTracker.BirthAbsTicks;
|
|
}
|
|
}
|
|
return youngestAge;
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
Props = (CompProperties_Breast)props;
|
|
|
|
if (maxBreastIncrement <= 0f)
|
|
{
|
|
maxBreastIncrement = Utility.RandGaussianLike(0.088f, 0.202f);
|
|
}
|
|
if (ageOfLastBirth == 0)
|
|
{
|
|
ageOfLastBirth = CalculateLastBirth();
|
|
}
|
|
if (baseAlpha <= 0f)
|
|
{
|
|
baseAlpha = Utility.RandGaussianLike(0.0f, 0.3f) + Rand.Range(0.0f, 0.5f);
|
|
}
|
|
if (baseAreola <= 0f)
|
|
{
|
|
baseAreola = Utility.RandGaussianLike(0.0f, 1.0f);
|
|
}
|
|
if (baseNipple <= 0f)
|
|
{
|
|
baseNipple = Utility.RandGaussianLike(0.0f, 1.0f);
|
|
}
|
|
UpdateNipples();
|
|
loaded = true;
|
|
}
|
|
|
|
protected void CalculateBreastSize()
|
|
{
|
|
// Scenario A: the youngest child is less than halfway into babyhood: Full size
|
|
if (ageOfLastBirth + BabyHalfAge * GenDate.TicksPerYear > parent.pawn.ageTracker.AgeBiologicalTicks)
|
|
{
|
|
debugGrowthStatus = "Full size due to young child";
|
|
if (breastSizeIncreased < MaxBreastIncrement)
|
|
{
|
|
parent.Severity += (MaxBreastIncrement - breastSizeIncreased);
|
|
breastSizeIncreased = MaxBreastIncrement;
|
|
}
|
|
}
|
|
// Scenario B: Pregnant, grow in the second half of first trimester
|
|
else if (parent.pawn.IsPregnant())
|
|
{
|
|
float pregnancySize = Mathf.InverseLerp(breastGrowthStart, breastGrowthEnd, parent.pawn.GetFarthestPregnancyProgress()) * MaxBreastIncrement;
|
|
if (breastSizeIncreased > pregnancySize)
|
|
{
|
|
debugGrowthStatus = "Shrinking due to being oversize for pregnancy";
|
|
// Breasts still large from the last kid
|
|
ShrinkBreasts();
|
|
}
|
|
else if (breastSizeIncreased < MaxBreastIncrement)
|
|
{
|
|
// Time to grow
|
|
float growAmount = pregnancySize - breastSizeIncreased;
|
|
if (growAmount != 0)
|
|
debugGrowthStatus = "Growing due to pregnancy";
|
|
else
|
|
debugGrowthStatus = "Pregnant, but not time to grow";
|
|
breastSizeIncreased += growAmount;
|
|
parent.Severity += growAmount;
|
|
}
|
|
else debugGrowthStatus = "Pregnant and full size";
|
|
}
|
|
// Scenario C: Not (or very early) pregnant and youngest child nonexistent or more than halfway into babyhood, time to shrink
|
|
else if (breastSizeIncreased > 0)
|
|
{
|
|
debugGrowthStatus = "Shrinking due to no pregnancy nor young child";
|
|
ShrinkBreasts();
|
|
}
|
|
else debugGrowthStatus = "Base size";
|
|
}
|
|
|
|
protected void CalculateNipples()
|
|
{
|
|
float newNippleProgress;
|
|
if (ageOfLastBirth + BabyHalfAge * GenDate.TicksPerYear > parent.pawn.ageTracker.AgeBiologicalTicks)
|
|
newNippleProgress = 1f;
|
|
else if (parent.pawn.IsPregnant())
|
|
newNippleProgress = nippleTransitions.Evaluate(parent.pawn.GetFarthestPregnancyProgress());
|
|
else
|
|
newNippleProgress = 0f;
|
|
|
|
if (newNippleProgress == nippleProgress) return; // Nothing to change
|
|
else if (newNippleProgress > nippleProgress)
|
|
{
|
|
float progressDifference = newNippleProgress - nippleProgress;
|
|
// All nipple growth has a slight effect on the base
|
|
// Not mathematically precise in hitting the goal at the end of the term, but close enough
|
|
baseAlpha *= 1.0f + progressDifference * Configurations.PermanentNippleChange;
|
|
if (baseAlpha > 1.0f) baseAlpha = 1.0f;
|
|
baseAreola *= 1.0f + progressDifference * Configurations.PermanentNippleChange;
|
|
if (baseAreola > 1.0f) baseAreola = 1.0f;
|
|
baseNipple *= 1.0f + progressDifference * Configurations.PermanentNippleChange;
|
|
if (baseNipple > 1.0f) baseNipple = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
nippleProgress -= tickInterval / (BabyHalfAge * GenDate.TicksPerYear);
|
|
if (nippleProgress < newNippleProgress) nippleProgress = newNippleProgress;
|
|
}
|
|
}
|
|
|
|
public void AdjustNippleProgress(float amount)
|
|
{
|
|
nippleProgress = Mathf.Clamp01(nippleProgress + amount);
|
|
UpdateNipples();
|
|
}
|
|
|
|
public void AdjustNippleSizeImmediately(float amount)
|
|
{
|
|
baseNipple = Mathf.Clamp01(baseNipple + amount);
|
|
UpdateNipples();
|
|
}
|
|
|
|
public void AdjustAreolaSizeImmediately(float amount)
|
|
{
|
|
baseAreola = Mathf.Clamp01(baseAreola + amount);
|
|
UpdateNipples();
|
|
}
|
|
|
|
public void UpdateNipples()
|
|
{
|
|
cachedAlpha = baseAlpha + nippleProgress * nippleChange;
|
|
cachedAreola = baseAreola + nippleProgress * nippleChange;
|
|
cachedNipple = baseNipple + nippleProgress * nippleChange;
|
|
|
|
// For some reason, Props can go null when RJW relocates the chest (e.g. some animals), so catch that
|
|
cachedColor = Colors.CMYKLerp(parent.pawn.story?.SkinColor ?? Color.white, (Props?.BlackNippleColor ?? CompProperties_Breast.DefaultBlacknippleColor.ToColor), Alpha);
|
|
}
|
|
|
|
public void CopyBreastProperties(HediffComp_Breast original)
|
|
{
|
|
maxBreastIncrement = original.maxBreastIncrement;
|
|
baseAlpha = original.baseAlpha;
|
|
baseAreola = original.baseAreola;
|
|
baseNipple = original.baseNipple;
|
|
UpdateNipples();
|
|
}
|
|
|
|
public string DebugInfo()
|
|
{
|
|
return "Size: " + parent.Severity +
|
|
"\nIncrease: " + breastSizeIncreased +
|
|
"\n" + debugGrowthStatus +
|
|
"\nNipple progress: " + nippleProgress +
|
|
"\nBase alpha: " + baseAlpha +
|
|
"\nAlpha: " + cachedAlpha +
|
|
"\nBase areola: " + baseAreola +
|
|
"\nAreola: " + cachedAreola +
|
|
"\nDisplayed areola: " + AreolaSize +
|
|
"\nBase nipple: " + baseNipple +
|
|
"\nNipple: " + cachedNipple +
|
|
"\nDisplayed nipple: " + NippleSize;
|
|
}
|
|
}
|
|
}
|