rjw_menstruation/1.3/source/RJW_Menstruation/RJW_Menstruation/HediffComps/HediffComp_Breast.cs

396 lines
14 KiB
C#

using RimWorld;
using RimWorld.Planet;
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 = 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 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 (
!Pawn.IsHashIntervalTick(tickInterval) ||
!ShouldSimulate()
)
{
return;
}
CalculateBreastSize();
CalculateNipples();
UpdateNipples();
}
catch (Exception ex)
{
Log.Error($"Error processing breasts of {Pawn}: {ex}");
}
}
public override void CompPostPostAdd(DamageInfo? dinfo)
{
if (!loaded) Initialize();
if (ageOfLastBirth > Pawn.ageTracker.AgeChronologicalTicks) ageOfLastBirth = CalculateLastBirth(); // catch transplant issues
}
public override void CompPostPostRemoved()
{
if (Pawn.health.hediffSet.hediffs.Contains(parent))
{
Log.Warning($"Attempted to remove breast comp from wrong pawn ({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 ((Pawn.relations == null)) return youngestAge;
bool hasChild = Pawn.relations.Children.
Where(child => !Pawn.health.hediffSet.GetHediffs<Hediff_BasePregnancy>().Any(preg => preg.babies.Contains(child))). // no fetuses
Where(child => child.GetMother() == Pawn). // not Dad
TryMinBy(child => child.ageTracker.AgeBiologicalTicks, out Pawn youngest);
if (hasChild) youngestAge = Pawn.ageTracker.AgeBiologicalTicks - youngest.ageTracker.AgeBiologicalTicks;
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 > 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 (Pawn.IsRJWPregnant())
{
float pregnancySize = Mathf.InverseLerp(breastGrowthStart, breastGrowthEnd, 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 > Pawn.ageTracker.AgeBiologicalTicks)
newNippleProgress = 1f;
else if (Pawn.IsRJWPregnant())
newNippleProgress = nippleTransitions.Evaluate(Pawn.GetFarthestPregnancyProgress());
else
newNippleProgress = 0f;
if (newNippleProgress < 0) newNippleProgress = 0;
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;
nippleProgress = newNippleProgress;
}
else
{
nippleProgress -= tickInterval / (BabyHalfAge * GenDate.TicksPerYear);
if (nippleProgress < newNippleProgress) nippleProgress = newNippleProgress;
}
}
public void GaveBirth()
{
ageOfLastBirth = Pawn.ageTracker.AgeBiologicalTicks;
}
public void AdjustNippleProgress(float amount)
{
nippleProgress = Mathf.Clamp01(nippleProgress + amount);
UpdateNipples();
}
public void AdjustNippleSizeImmediately(float amount)
{
baseNipple = Mathf.Clamp01(baseNipple + amount);
UpdateNipples();
}
public void AdjustNippleColorImmediately(float amount)
{
baseAlpha = Mathf.Clamp01(baseAlpha + 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(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;
}
}
}