diff --git a/src/LittleSim/Sim/AnimalOrganism.cs b/src/LittleSim/Sim/AnimalOrganism.cs index 1d277a9..6523e76 100644 --- a/src/LittleSim/Sim/AnimalOrganism.cs +++ b/src/LittleSim/Sim/AnimalOrganism.cs @@ -191,9 +191,11 @@ public sealed class HealthState /// /// Пересчитывает боль (из хедифов) и способности: базовые из частей/крови/боли, затем модификаторы - /// способностей от хедифов (лерп 1→factor по тяжести). Вызывать после изменения частей/крови/хедифов. + /// способностей от хедифов (лерп 1→factor по тяжести), затем общий множитель + /// (старение/сенесценс: дряхлеющий организм слабее по всем способностям). Вызывать после изменения + /// частей/крови/хедифов; =1 — без возрастного спада. /// - public void RecomputeCapacities() + public void RecomputeCapacities(float capacityScale = 1f) { var pain = 0f; foreach (var h in _hediffs) @@ -218,6 +220,15 @@ public sealed class HealthState } } + if (capacityScale < 1f) + { + var scale = Math.Clamp(capacityScale, 0f, 1f); + foreach (var capacity in new List(caps.Keys)) + { + caps[capacity] *= scale; + } + } + Capacities = caps; } diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index 92539f5..c7d78bf 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -790,6 +790,7 @@ public sealed class AnimalActionSystem : BaseSystem private const float GrazeKillAge = 1f; // трава с возрастом ниже этого после выедания исчезает private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась» private const float FleeSpeedMult = 1.2f; // страх подгоняет жертву чуть быстрее обычного шага + private const float PostReproductiveFrac = 0.9f; // доля жизни, после которой самка уже не зачинает private readonly EntityStore _store; private readonly PlantSet _plants; @@ -1216,14 +1217,24 @@ public sealed class AnimalActionSystem : BaseSystem if (!mother.HasComponent()) { ref readonly var mom = ref mother.GetComponent(); - mother.AddComponent( - new Pregnant - { - FatherGenome = father.GetComponent().Genome, - DueInDays = MathF.Max(1f, mom.Traits.GestationDays), - Litter = Math.Max(1, (int)MathF.Round(mom.Traits.LitterSize)), - } - ); + // Плодовитость падает со старостью (тот же фактор сенесценса): дряхлая самка приносит + // меньший помёт, а после порога пострепродуктивного возраста уже не зачинает. + var momAge = mother.HasComponent() + ? mother.GetComponent().AgeDays + : 0f; + var ageFrac = mom.Traits.Lifespan > 0f ? momAge / mom.Traits.Lifespan : 0f; + if (ageFrac <= PostReproductiveFrac) + { + var fert = AnimalHealthSystem.SenescenceFactor(momAge, mom.Traits.Lifespan); + mother.AddComponent( + new Pregnant + { + FatherGenome = father.GetComponent().Genome, + DueInDays = MathF.Max(1f, mom.Traits.GestationDays), + Litter = Math.Max(1, (int)MathF.Round(mom.Traits.LitterSize * fert)), + } + ); + } } ResetMating(a, mateNeed); @@ -1720,10 +1731,15 @@ public sealed class AnimalHealthSystem : BaseSystem private readonly float _secondsPerDay; private readonly HediffDef[] _ambient; private readonly Random _rng; - private readonly ArchetypeQuery _query; + private readonly ArchetypeQuery _query; private readonly List _deaths = []; private float _accum; + // Сенесценс (старение): спад способностей начинается с этой доли продолжительности жизни и достигает + // максимума к её концу. Дряхлеющий зверь слабее (медленнее, хуже видит/фильтрует кровь) и уязвимее. + private const float SenescenceStart = 0.6f; + private const float SenescenceMax = 0.45f; + public AnimalHealthSystem( EntityStore store, AnimalSet animals, @@ -1741,7 +1757,19 @@ public sealed class AnimalHealthSystem : BaseSystem _secondsPerDay = secondsPerDay; _ambient = ambient; _rng = new Random(seed); - _query = store.Query(); + _query = store.Query(); + } + + /// Множитель способностей от старения (1 — молод/расцвет, <1 — дряхлеет к концу жизни). + public static float SenescenceFactor(float ageDays, float lifespan) + { + if (lifespan <= 0f) + { + return 1f; + } + + var frac = (ageDays / lifespan - SenescenceStart) / MathF.Max(0.01f, 1f - SenescenceStart); + return 1f - SenescenceMax * Math.Clamp(frac, 0f, 1f); } protected override void OnUpdateGroup() @@ -1756,9 +1784,11 @@ public sealed class AnimalHealthSystem : BaseSystem _accum = 0f; _deaths.Clear(); - foreach (var (healths, organisms, entities) in _query.Chunks) + foreach (var (healths, organisms, growths, entities) in _query.Chunks) { var h = healths.Span; + var o = organisms.Span; + var g = growths.Span; for (var i = 0; i < h.Length; i++) { var state = h[i].State; @@ -1804,7 +1834,9 @@ public sealed class AnimalHealthSystem : BaseSystem ); } - state.RecomputeCapacities(); + // Старение: к концу жизни способности дряхлеют (медленнее/слабее, уязвимее к ранам/болезни). + var senescence = SenescenceFactor(g[i].AgeDays, o[i].Traits.Lifespan); + state.RecomputeCapacities(senescence); if ( lethal || state.BloodLevel <= 0f