using HugsLib; using RimWorld; using rjw; using System; using System.Collections.Generic; using UnityEngine; using Verse; namespace RJW_Menstruation { public class CompProperties_Breast : HediffCompProperties { 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 float DEFAULTALPHA = -1; public const float DEFAULTAREOLA = -1; public const float DEFAULTNIPPLE = -1; public const float VARIANT = 0.2f; public const int TICKINTERVAL = 3750; public const float MAX_BREAST_INCREMENT = 0.10f; public const float BREAST_GROWTH_START = 1f / 6f; public const float BREAST_GROWTH_END = 1f / 3f; public CompProperties_Breast Props; protected float alphaPermanent = -1; protected float alphaCurrent = -1; protected float alpha = -1; protected float areolaSizePermanent = -1f; protected float areolaSizeCurrent = -1f; protected float areolaSize = -1f; protected float nippleSizePermanent = -1f; protected float nippleSizeCurrent = -1f; protected float nippleSize = -1f; protected long ageOfLastBirth = 0; protected float breastSizeIncreased = 0f; protected string debugGrowthStatus = "(Growth/shrink not yet calculated; run for 1.5h to update)"; protected float originalpha = -1f; protected float originareola = -1f; protected float originnipple = -1f; protected Color cachedcolor; protected bool loaded = false; protected bool pregnant = false; public Action action; protected float BabyHalfAge { get { float res = 0; List ages = parent.pawn.RaceProps.lifeStageAges; if (ages?.Count > 1) res = ages[1].minAge / 2; if (res <= 0) res = 1.2f / 2; // Default to human if (RJWPregnancySettings.phantasy_pregnancy) res /= GenDate.DaysPerYear; return res; } } 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 * MAX_BREAST_INCREMENT / (BabyHalfAge * GenDate.TicksPerYear); float shrinkAmount = Mathf.Min(shrinkRate, breastSizeIncreased); breastSizeIncreased -= shrinkAmount; parent.Severity -= shrinkAmount; } public float MaxAlpha { get { return originalpha + Configurations.NippleMaximumTransition; } } public float MaxAreola { get { return originareola + Configurations.NippleMaximumTransition; } } public float MaxNipple { get { return originnipple + Configurations.NippleMaximumTransition; } } public float OriginAlpha => originalpha; public float OriginNipple => originnipple; public float OriginAreola => originareola; public Color OriginColor => Colors.CMYKLerp(parent?.pawn?.story?.SkinColor ?? Color.white, Props.BlackNippleColor, originalpha); public Color NippleColor { get { return cachedcolor; } } public float Alpha { get { return alphaCurrent; } } public float NippleSize { get { return nippleSizeCurrent; } } public float AreolaSize { get { return areolaSizeCurrent; } } public float BreastSizeIncreased { get { return breastSizeIncreased; } } public override void CompExposeData() { base.CompExposeData(); Scribe_Values.Look(ref alphaPermanent, "alphaPermanent", DEFAULTALPHA, true); Scribe_Values.Look(ref alphaCurrent, "alphaCurrent", DEFAULTALPHA, true); Scribe_Values.Look(ref alpha, "alpha", DEFAULTALPHA, true); Scribe_Values.Look(ref areolaSizePermanent, "areolaSizePermanent", DEFAULTAREOLA, true); Scribe_Values.Look(ref areolaSizeCurrent, "areolaSizeCurrent", DEFAULTAREOLA, true); Scribe_Values.Look(ref areolaSize, "areolaSize", DEFAULTAREOLA, true); Scribe_Values.Look(ref nippleSizePermanent, "nippleSizePermanent", DEFAULTNIPPLE, true); Scribe_Values.Look(ref nippleSizeCurrent, "nippleSizeCurrent", DEFAULTNIPPLE, true); Scribe_Values.Look(ref nippleSize, "nippleSize", DEFAULTNIPPLE, true); Scribe_Values.Look(ref ageOfLastBirth, "ageOfLastBirth", ageOfLastBirth, true); Scribe_Values.Look(ref breastSizeIncreased, "breastSizeIncreased", breastSizeIncreased, true); Scribe_Values.Look(ref originalpha, "originalpha", originalpha, true); Scribe_Values.Look(ref originareola, "originareola", originareola, true); Scribe_Values.Look(ref originnipple, "originnipple", originnipple, true); Scribe_Values.Look(ref pregnant, "pregnant", pregnant, true); } public override void CompPostTick(ref float severityAdjustment) { } 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; } HugsLibController.Instance.TickDelayScheduler.TryUnscheduleCallback(action); if (Configurations.Debug) Log.Message(parent.pawn.Label + " breast tick scheduler removed"); 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; foreach (Hediff_BasePregnancy preg in parent.pawn.health.hediffSet.GetHediffs()) { if (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; action = Transition; if (ageOfLastBirth == 0) { ageOfLastBirth = CalculateLastBirth(); } if (alphaPermanent < 0f) { alphaPermanent = (Utility.RandGaussianLike(0.0f, 0.3f) + Rand.Range(0.0f, 0.5f)) / 2; originalpha = alphaPermanent; alpha = alphaPermanent; alphaCurrent = alphaPermanent; } if (areolaSizePermanent < 0f) { areolaSizePermanent = Utility.RandGaussianLike(0f, parent.Severity); originareola = areolaSizePermanent; areolaSize = areolaSizePermanent; areolaSizeCurrent = areolaSizePermanent; } if (nippleSizePermanent < 0f) { nippleSizePermanent = Utility.RandGaussianLike(0f, parent.Severity); originnipple = nippleSizePermanent; nippleSize = nippleSizePermanent; nippleSizeCurrent = nippleSizePermanent; } UpdateColor(); loaded = true; HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(action, TICKINTERVAL, parent.pawn); } public void Transition() { alphaCurrent = Mathf.Lerp(alphaCurrent, alpha, Configurations.NippleTransitionRatio); areolaSizeCurrent = Mathf.Lerp(areolaSizeCurrent, areolaSize, Configurations.NippleTransitionRatio); nippleSizeCurrent = Mathf.Lerp(nippleSizeCurrent, nippleSize, Configurations.NippleTransitionRatio); UpdateColor(); HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(action, TICKINTERVAL, parent.pawn); // 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 < MAX_BREAST_INCREMENT) { parent.Severity += (MAX_BREAST_INCREMENT - breastSizeIncreased); breastSizeIncreased = MAX_BREAST_INCREMENT; } } // Scenario B: Pregnant, grow in the second half of first trimester else if (parent.pawn.IsPregnant()) { float pregnancySize = Mathf.InverseLerp(BREAST_GROWTH_START, BREAST_GROWTH_END, parent.pawn.GetFarthestPregnancyProgress()) * MAX_BREAST_INCREMENT; if (breastSizeIncreased > pregnancySize) { debugGrowthStatus = "Shrinking due to being oversize for pregnancy"; // Breasts still large from the last kid ShrinkBreasts(); } else if (breastSizeIncreased < MAX_BREAST_INCREMENT) { // 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"; } public void ChangeColorFermanant(float alpha) { alphaPermanent = alpha; } public void ChangeColor(float alpha) { this.alpha = alpha; } public void PregnancyTransition() { alphaPermanent = Math.Min(MaxAlpha, alphaPermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT)); areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT)); nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + Configurations.NipplePermanentTransitionVariance.VariationRange(VARIANT)); alpha = Math.Min(MaxAlpha, alpha + Configurations.NippleTransitionVariance.VariationRange(VARIANT)); areolaSize = Math.Min(MaxAreola, areolaSize + Configurations.NippleTransitionVariance.VariationRange(VARIANT)); nippleSize = Math.Min(MaxNipple, nippleSize + Configurations.NippleTransitionVariance.VariationRange(VARIANT)); pregnant = true; } public void BirthTransition() { alpha = alphaPermanent; areolaSize = areolaSizePermanent; nippleSize = nippleSizePermanent; pregnant = false; ageOfLastBirth = parent.pawn.ageTracker.AgeBiologicalTicks; } public void AdjustBreastSize(float amount) { parent.Severity += amount; breastSizeIncreased += amount; } public void AdjustNippleSize(float amount) { nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + amount); nippleSize = Math.Min(MaxNipple, nippleSize + amount); } public void AdjustAreolaSize(float amount) { areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + amount); areolaSize = Math.Min(MaxAreola, areolaSize + amount); } public void RestoreBreastSize(float ratio) { float variance = breastSizeIncreased * Math.Min(ratio, 1.0f); breastSizeIncreased -= variance; parent.Severity -= variance; } public void AdjustNippleSizeImmidiately(float amount) { originnipple = Math.Max(0, originnipple + amount); nippleSizePermanent = Math.Min(MaxNipple, nippleSizePermanent + amount); nippleSize = Math.Min(MaxNipple, nippleSize + amount); nippleSizeCurrent = nippleSize; } public void AdjustAreolaSizeImmidiately(float amount) { originareola = Math.Max(0, originareola + amount); areolaSizePermanent = Math.Min(MaxAreola, areolaSizePermanent + amount); areolaSize = Math.Min(MaxAreola, areolaSize + amount); areolaSizeCurrent = areolaSize; } public void UpdateColor() { cachedcolor = Colors.CMYKLerp(parent?.pawn?.story?.SkinColor ?? Color.white, Props.BlackNippleColor, Alpha); } public void CopyBreastProperties(HediffComp_Breast original) { alphaPermanent = original.alphaPermanent; alphaCurrent = original.alphaCurrent; alpha = original.alpha; areolaSizePermanent = original.areolaSizePermanent; areolaSizeCurrent = original.areolaSizeCurrent; areolaSize = original.areolaSize; nippleSizePermanent = original.nippleSizePermanent; nippleSizeCurrent = original.nippleSizeCurrent; nippleSize = original.nippleSize; originalpha = original.originalpha; originareola = original.originareola; originnipple = original.originnipple; cachedcolor = original.cachedcolor; } public string DebugInfo() { return "Increase: " + breastSizeIncreased + "\n" + debugGrowthStatus + "\nAlpha: " + alpha + "\nNippleSize: " + nippleSize + "\nAreolaSize: " + areolaSize + "\nAlphaCurrent: " + alphaCurrent + "\nNippleSizeCurrent: " + nippleSizeCurrent + "\nAreolaSizeCurrent: " + areolaSizeCurrent + "\nAlphaOrigin: " + originalpha + "\nNippleSizeOrigin: " + originnipple + "\nAreolaSizeOrigin: " + originareola + "\nAlphaMax: " + MaxAlpha + "\nNippleSizeMax: " + MaxNipple + "\nAreolaSizeMax: " + MaxAreola + "\nPermanentAlpha:" + alphaPermanent + "\nPermanentNipple:" + nippleSizePermanent + "\nPermanentAreola:" + areolaSizePermanent; } } }