Животные A6: болезни и иммунитет

HediffDef расширен (initialSeverity/severityPerDay/immunityPerDay/lethalSeverity/pain/ambientPerDay/capMods); хедифы стали инстансами (Hediff: тяжесть+иммунитет). AnimalHealthSystem (медленный тик ~час): фоновое заражение, гонка тяжесть<->иммунитет (выздоровление при иммунитете 1 / смерть при летальной тяжести), пересчёт способностей с болью и capMods; смерть по здоровью оставляет труп. Первый реальный источник урона: Moving подключён к скорости (больные медленнее). Болезнь Fever; команда infect для теста.

Сборка чистая, --check-content (10 типов дефов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 05:48:05 +03:00
co-authored by Claude Opus 4.8
parent 9db12273c9
commit 9f8ff93591
6 changed files with 291 additions and 23 deletions
+119 -4
View File
@@ -433,7 +433,13 @@ public sealed class AnimalActionSystem : BaseSystem
private readonly GameClock _clock;
private readonly float _secondsPerDay;
private readonly RectF _bounds;
private readonly ArchetypeQuery<AnimalBrain, AnimalNeeds, AnimalOrganism, Transform2D> _query;
private readonly ArchetypeQuery<
AnimalBrain,
AnimalNeeds,
AnimalOrganism,
Transform2D,
Health
> _query;
private readonly List<Entity> _eaten = [];
private readonly List<(Entity Self, Entity Partner)> _matings = [];
@@ -454,7 +460,7 @@ public sealed class AnimalActionSystem : BaseSystem
_clock = clock;
_secondsPerDay = secondsPerDay;
_bounds = bounds;
_query = store.Query<AnimalBrain, AnimalNeeds, AnimalOrganism, Transform2D>();
_query = store.Query<AnimalBrain, AnimalNeeds, AnimalOrganism, Transform2D, Health>();
}
protected override void OnUpdateGroup()
@@ -469,19 +475,22 @@ public sealed class AnimalActionSystem : BaseSystem
_eaten.Clear();
_matings.Clear();
foreach (var (brains, needsChunk, organisms, transforms, entities) in _query.Chunks)
foreach (var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks)
{
var b = brains.Span;
var n = needsChunk.Span;
var o = organisms.Span;
var t = transforms.Span;
var hh = healths.Span;
for (var i = 0; i < b.Length; i++)
{
ref var brain = ref b[i];
var values = n[i].Values;
ref var pos = ref t[i].Position;
var def = _animalSet[o[i].Species].Def;
var speed = def.BaseSpeed * MathF.Max(0.2f, o[i].Traits.MoveSpeed);
// Способность Moving (падает от ран/болезней) — множитель к скорости: больные медленнее.
var moving = hh[i].State.Capacity(AnimalCapacities.Moving);
var speed = def.BaseSpeed * MathF.Max(0.2f, o[i].Traits.MoveSpeed) * moving;
switch (brain.Action)
{
@@ -1006,3 +1015,109 @@ public sealed class AnimalPregnancySystem : BaseSystem
public Vector2 Position;
}
}
/// <summary>
/// Здоровье и болезни (фаза A6): медленный тик (~игровой час). Фоновое заражение, прогресс болезней
/// (гонка тяжесть↔иммунитет — выздоровление или смерть), пересчёт способностей с учётом хедифов. Смерть
/// по здоровью (летальная болезнь / кровь на нуле / потеря сознания) оставляет труп. Первый реальный
/// источник урона — оживляет каркас A5 (больные звери теряют Moving → медленнее).
/// </summary>
public sealed class AnimalHealthSystem : BaseSystem
{
private const float TickDays = 1f / 24f; // ~игровой час
private readonly EntityStore _store;
private readonly AnimalSet _animals;
private readonly int _cellSize;
private readonly GameClock _clock;
private readonly float _secondsPerDay;
private readonly HediffDef[] _ambient;
private readonly Random _rng;
private readonly ArchetypeQuery<Health, AnimalOrganism> _query;
private readonly List<Entity> _deaths = [];
private float _accum;
public AnimalHealthSystem(
EntityStore store,
AnimalSet animals,
int cellSize,
GameClock clock,
float secondsPerDay,
HediffDef[] ambient,
int seed
)
{
_store = store;
_animals = animals;
_cellSize = cellSize;
_clock = clock;
_secondsPerDay = secondsPerDay;
_ambient = ambient;
_rng = new Random(seed);
_query = store.Query<Health, AnimalOrganism>();
}
protected override void OnUpdateGroup()
{
_accum += _clock.DeltaTime / _secondsPerDay;
if (_accum < TickDays)
{
return;
}
var days = _accum;
_accum = 0f;
_deaths.Clear();
foreach (var (healths, organisms, entities) in _query.Chunks)
{
var h = healths.Span;
for (var i = 0; i < h.Length; i++)
{
var state = h[i].State;
if (state is null)
{
continue;
}
foreach (var disease in _ambient)
{
if (!state.Has(disease.DefName) && _rng.NextSingle() < disease.AmbientPerDay * days)
{
state.Add(disease);
}
}
var lethal = state.AdvanceHediffs(days);
state.RecomputeCapacities();
if (
lethal
|| state.BloodLevel <= 0f
|| state.Capacity(AnimalCapacities.Consciousness) <= 0.01f
)
{
_deaths.Add(entities.EntityAt(i));
}
}
}
foreach (var dead in _deaths)
{
if (dead.IsNull || !dead.HasComponent<AnimalOrganism>())
{
continue;
}
ref readonly var org = ref dead.GetComponent<AnimalOrganism>();
var position = dead.GetComponent<Transform2D>().Position;
AnimalFactory.CreateCorpse(
_store,
_animals[org.Species],
position,
org.Traits.BodySize,
_cellSize
);
dead.DeleteEntity();
}
}
}