Животные 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:
co-authored by
Claude Opus 4.8
parent
9db12273c9
commit
9f8ff93591
@@ -3,6 +3,15 @@
|
|||||||
// Хедифы — состояния организма (фаза A4: лёгкий каркас). Гон (Rut) навешивается сезонно на взрослых
|
// Хедифы — состояния организма (фаза A4: лёгкий каркас). Гон (Rut) навешивается сезонно на взрослых
|
||||||
// и поднимает половое влечение. Раны/болезни/возрастные эффекты со стадиями придут в A5/A6.
|
// и поднимает половое влечение. Раны/болезни/возрастные эффекты со стадиями придут в A5/A6.
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "Rut", "label": "hediff.rut" }
|
{ "defName": "Rut", "label": "hediff.rut" },
|
||||||
|
|
||||||
|
// Болезнь (фаза A6): прогрессирует, параллельно растёт иммунитет (гонка). Обычно иммунитет
|
||||||
|
// успевает победить (~3 дня), но больной зверь медленнее и слабее; слабые/невезучие гибнут.
|
||||||
|
{
|
||||||
|
"defName": "Fever", "label": "hediff.fever",
|
||||||
|
"initialSeverity": 0.05, "severityPerDay": 0.25, "immunityPerDay": 0.32,
|
||||||
|
"lethalSeverity": 1.0, "pain": 0.3, "ambientPerDay": 0.03,
|
||||||
|
"capMods": { "Moving": 0.6, "Consciousness": 0.85, "Digestion": 0.8 }
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
> 1 (каркас под болезни A6/хищников). Команда `body <species>`. Сборка/линт чистые (10 типов дефов).
|
> 1 (каркас под болезни A6/хищников). Команда `body <species>`. Сборка/линт чистые (10 типов дефов).
|
||||||
> Осталось в A5: смерть через здоровье (vital-часть/кровь) + истощение как hediff — когда появятся раны.
|
> Осталось в A5: смерть через здоровье (vital-часть/кровь) + истощение как hediff — когда появятся раны.
|
||||||
>
|
>
|
||||||
|
> **A6 (готово): болезни и иммунитет.** `HediffDef` расширён (severity/immunity/lethal/pain/capMods/
|
||||||
|
> ambient); хедифы теперь инстансы (`Hediff` с тяжестью/иммунитетом). `AnimalHealthSystem` (медленный
|
||||||
|
> тик ~час): фоновое заражение, гонка тяжесть↔иммунитет (выздоровление или смерть), пересчёт способностей
|
||||||
|
> с учётом боли и capMods; смерть по здоровью (летальная болезнь/кровь/потеря сознания) → труп. **Первый
|
||||||
|
> реальный урон** — `Moving` подключён к скорости (больные звери медленнее). Болезнь `Fever` в
|
||||||
|
> `hediffs.json`; команда `infect [hediff]`. Сборка/линт чистые.
|
||||||
|
>
|
||||||
> **A4 (готово):** нужда `Mating`; лёгкий hediff-каркас (`HediffDef`, `hediffs.json` с `Rut`,
|
> **A4 (готово):** нужда `Mating`; лёгкий hediff-каркас (`HediffDef`, `hediffs.json` с `Rut`,
|
||||||
> компонент `Health`/`HealthState`); гены `GeneBreedingSeason`/`GeneGestationDays`/`GeneLitterSize`;
|
> компонент `Health`/`HealthState`); гены `GeneBreedingSeason`/`GeneGestationDays`/`GeneLitterSize`;
|
||||||
> `AnimalRutSystem` (сезонный гон-хедиф поднимает влечение — вне сезона зачатия нет); действие
|
> `AnimalRutSystem` (сезонный гон-хедиф поднимает влечение — вне сезона зачатия нет); действие
|
||||||
|
|||||||
@@ -357,8 +357,31 @@ public sealed class BodyDef : Def
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Состояние/хедиф организма (Defs/hediffs.json): гон, позже — раны, болезни, возрастные эффекты.
|
/// Состояние/хедиф организма (Defs/hediffs.json): гон, болезни, позже раны/возрастные эффекты. Фаза A6:
|
||||||
/// Фаза A4 — лёгкий каркас: пока идентичность + локализуемый <see cref="Def.Label"/>; стадии,
|
/// болезнь прогрессирует (<see cref="SeverityPerDay"/>), параллельно растёт иммунитет
|
||||||
/// модификаторы способностей и иммунитет придут в A5/A6.
|
/// (<see cref="ImmunityPerDay"/>) — гонка: иммунитет→1 раньше = выздоровление, severity→
|
||||||
|
/// <see cref="LethalSeverity"/> раньше = смерть. Снижает способности (<see cref="CapMods"/>) и даёт боль.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HediffDef : Def { }
|
public sealed class HediffDef : Def
|
||||||
|
{
|
||||||
|
/// <summary>Начальная тяжесть при навешивании (гон — 1; болезнь — малая, напр. 0.05).</summary>
|
||||||
|
public float InitialSeverity { get; init; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Прирост тяжести за игровой день (0 — статичный хедиф вроде гона).</summary>
|
||||||
|
public float SeverityPerDay { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Прирост иммунитета за день (0 — иммунитета нет, не выздоравливает сам).</summary>
|
||||||
|
public float ImmunityPerDay { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Тяжесть, при которой наступает смерть (0 — не летально).</summary>
|
||||||
|
public float LethalSeverity { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Боль при тяжести 1 (масштабируется тяжестью) — вход в сознание/настроение.</summary>
|
||||||
|
public float Pain { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Шанс подхватить фоном за день (0 — не фоновая болезнь).</summary>
|
||||||
|
public float AmbientPerDay { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Модификаторы способностей: capacity id → множитель при тяжести 1 (лерп 1→factor по severity).</summary>
|
||||||
|
public Dictionary<string, float> CapMods { get; init; } = new();
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,6 +216,17 @@ public sealed class WorldScene : Scene
|
|||||||
var shore = ComputeShore();
|
var shore = ComputeShore();
|
||||||
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs));
|
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs));
|
||||||
UpdateSystems.Add(new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut")));
|
UpdateSystems.Add(new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut")));
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new AnimalHealthSystem(
|
||||||
|
Store,
|
||||||
|
_animals,
|
||||||
|
CellSize,
|
||||||
|
Context.Clock,
|
||||||
|
SecondsPerDay,
|
||||||
|
content.Defs.All<HediffDef>().Where(h => h.AmbientPerDay > 0f).ToArray(),
|
||||||
|
_config.Seed + 0x3EAD
|
||||||
|
)
|
||||||
|
);
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new AnimalDecisionSystem(
|
new AnimalDecisionSystem(
|
||||||
Store,
|
Store,
|
||||||
@@ -714,6 +725,11 @@ public sealed class WorldScene : Scene
|
|||||||
"body <species> — dump a species body parts and a healthy individual's capacities",
|
"body <species> — dump a species body parts and a healthy individual's capacities",
|
||||||
(c, args) => RunBodyDemo(c, content, args)
|
(c, args) => RunBodyDemo(c, content, args)
|
||||||
);
|
);
|
||||||
|
console.Register(
|
||||||
|
"infect",
|
||||||
|
"infect [hediff] — give every animal a disease (default Fever) to test the health system",
|
||||||
|
(c, args) => RunInfect(c, content, args)
|
||||||
|
);
|
||||||
console.Register(
|
console.Register(
|
||||||
"menu",
|
"menu",
|
||||||
"menu — return to the main menu",
|
"menu — return to the main menu",
|
||||||
@@ -960,6 +976,29 @@ public sealed class WorldScene : Scene
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Тест системы здоровья (фаза A6): навешивает болезнь всем живым животным.
|
||||||
|
private void RunInfect(DevConsole console, GameContent content, string[] args)
|
||||||
|
{
|
||||||
|
var name = args.Length > 0 ? args[0] : "Fever";
|
||||||
|
if (!content.Defs.TryGet<HediffDef>(name, out var def))
|
||||||
|
{
|
||||||
|
console.WriteLine($"no hediff '{name}'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
Store
|
||||||
|
.Query<Health>()
|
||||||
|
.ForEachEntity(
|
||||||
|
(ref Health h, Entity _) =>
|
||||||
|
{
|
||||||
|
h.State?.Add(def);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.WriteLine($"infected {count} animals with {def.DefName}");
|
||||||
|
}
|
||||||
|
|
||||||
private static string ProductLabel(GameContent content, string productDefName) =>
|
private static string ProductLabel(GameContent content, string productDefName) =>
|
||||||
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
||||||
? content.Languages.Get(product.Label)
|
? content.Languages.Get(product.Label)
|
||||||
|
|||||||
@@ -129,18 +129,31 @@ public struct PartInstance
|
|||||||
public readonly float Fraction => MaxHp > 0f ? Math.Clamp(Hp / MaxHp, 0f, 1f) : 0f;
|
public readonly float Fraction => MaxHp > 0f ? Math.Clamp(Hp / MaxHp, 0f, 1f) : 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Инстанс хедифа на особи: деф + текущая тяжесть и набранный иммунитет (гонка, фаза A6).</summary>
|
||||||
|
public struct Hediff
|
||||||
|
{
|
||||||
|
/// <summary>Деф хедифа (гон/болезнь/…).</summary>
|
||||||
|
public HediffDef Def;
|
||||||
|
|
||||||
|
/// <summary>Текущая тяжесть.</summary>
|
||||||
|
public float Severity;
|
||||||
|
|
||||||
|
/// <summary>Набранный иммунитет [0,1]; при 1 — выздоровление.</summary>
|
||||||
|
public float Immunity;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Состояние здоровья особи (фаза A4–A5): список активных хедифов + части тела, уровень крови и
|
/// Состояние здоровья особи (фазы A4–A6): активные хедифы (инстансы) + части тела, кровь и вычисленные
|
||||||
/// вычисленные способности (capacities). Managed-объект, как <see cref="Genome"/> (Friflo допускает
|
/// способности (capacities). Managed-объект, как <see cref="Genome"/> (Friflo допускает ссылку в
|
||||||
/// ссылку в компоненте). Источников урона пока нет — части на полном HP, способности = 1; каркас под
|
/// компоненте). Болезни (A6) — первый источник урона: снижают способности и могут убить. Растёт по
|
||||||
/// болезни (A6) и хищников (горизонт). Растёт по фазам — контейнер один.
|
/// фазам — контейнер один.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HealthState
|
public sealed class HealthState
|
||||||
{
|
{
|
||||||
private readonly List<HediffDef> _hediffs = [];
|
private readonly List<Hediff> _hediffs = [];
|
||||||
|
|
||||||
/// <summary>Активные хедифы (гон, позже — раны/болезни).</summary>
|
/// <summary>Активные хедифы (гон, болезни).</summary>
|
||||||
public IReadOnlyList<HediffDef> Hediffs => _hediffs;
|
public IReadOnlyList<Hediff> Hediffs => _hediffs;
|
||||||
|
|
||||||
/// <summary>Части тела особи (из анатомии вида); пусто — у вида нет тела.</summary>
|
/// <summary>Части тела особи (из анатомии вида); пусто — у вида нет тела.</summary>
|
||||||
public PartInstance[] Parts { get; private set; } = [];
|
public PartInstance[] Parts { get; private set; } = [];
|
||||||
@@ -160,9 +173,71 @@ public sealed class HealthState
|
|||||||
/// <summary>Способность по id (1, если не вычислена — напр. у вида без тела), для множителей действий.</summary>
|
/// <summary>Способность по id (1, если не вычислена — напр. у вида без тела), для множителей действий.</summary>
|
||||||
public float Capacity(string id) => Capacities.TryGetValue(id, out var v) ? v : 1f;
|
public float Capacity(string id) => Capacities.TryGetValue(id, out var v) ? v : 1f;
|
||||||
|
|
||||||
/// <summary>Пересчитывает способности из текущих частей/крови/боли.</summary>
|
/// <summary>
|
||||||
public void RecomputeCapacities() =>
|
/// Пересчитывает боль (из хедифов) и способности: базовые из частей/крови/боли, затем модификаторы
|
||||||
Capacities = CapacityCalc.Compute(Parts, BloodLevel, Pain);
|
/// способностей от хедифов (лерп 1→factor по тяжести). Вызывать после изменения частей/крови/хедифов.
|
||||||
|
/// </summary>
|
||||||
|
public void RecomputeCapacities()
|
||||||
|
{
|
||||||
|
var pain = 0f;
|
||||||
|
foreach (var h in _hediffs)
|
||||||
|
{
|
||||||
|
pain += h.Def.Pain * h.Severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pain = Math.Clamp(pain, 0f, 1f);
|
||||||
|
|
||||||
|
var caps = CapacityCalc.Compute(Parts, BloodLevel, Pain);
|
||||||
|
foreach (var h in _hediffs)
|
||||||
|
{
|
||||||
|
if (h.Def.CapMods.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = Math.Clamp(h.Severity, 0f, 1f);
|
||||||
|
foreach (var (capacity, factor) in h.Def.CapMods)
|
||||||
|
{
|
||||||
|
caps[capacity] = caps.GetValueOrDefault(capacity, 1f) * (1f + (factor - 1f) * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Capacities = caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Прогрессирует болезни (тяжесть и иммунитет растут со временем); снимает выздоровевшие
|
||||||
|
/// (иммунитет ≥ 1); возвращает true, если какая-то болезнь достигла летальной тяжести.
|
||||||
|
/// </summary>
|
||||||
|
public bool AdvanceHediffs(float days)
|
||||||
|
{
|
||||||
|
var lethal = false;
|
||||||
|
for (var i = _hediffs.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var h = _hediffs[i];
|
||||||
|
if (h.Def.SeverityPerDay <= 0f && h.Def.ImmunityPerDay <= 0f)
|
||||||
|
{
|
||||||
|
continue; // статичный хедиф (гон) не прогрессирует
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Severity += h.Def.SeverityPerDay * days;
|
||||||
|
h.Immunity += h.Def.ImmunityPerDay * days;
|
||||||
|
if (h.Immunity >= 1f)
|
||||||
|
{
|
||||||
|
_hediffs.RemoveAt(i); // иммунитет победил — выздоровление
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h.Def.LethalSeverity > 0f && h.Severity >= h.Def.LethalSeverity)
|
||||||
|
{
|
||||||
|
lethal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hediffs[i] = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lethal;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Строит здоровье особи: части из анатомии вида (HP × размер тела) + начальные способности.</summary>
|
/// <summary>Строит здоровье особи: части из анатомии вида (HP × размер тела) + начальные способности.</summary>
|
||||||
public static HealthState Create(Content.BodyDef? body, float bodySize)
|
public static HealthState Create(Content.BodyDef? body, float bodySize)
|
||||||
@@ -193,7 +268,7 @@ public sealed class HealthState
|
|||||||
{
|
{
|
||||||
foreach (var h in _hediffs)
|
foreach (var h in _hediffs)
|
||||||
{
|
{
|
||||||
if (h.DefName == defName)
|
if (h.Def.DefName == defName)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -202,12 +277,12 @@ public sealed class HealthState
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Навешивает хедиф (если ещё не навешан).</summary>
|
/// <summary>Навешивает хедиф (если ещё не навешан) с начальной тяжестью из дефа.</summary>
|
||||||
public void Add(HediffDef def)
|
public void Add(HediffDef def)
|
||||||
{
|
{
|
||||||
if (!Has(def.DefName))
|
if (!Has(def.DefName))
|
||||||
{
|
{
|
||||||
_hediffs.Add(def);
|
_hediffs.Add(new Hediff { Def = def, Severity = def.InitialSeverity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +291,7 @@ public sealed class HealthState
|
|||||||
{
|
{
|
||||||
for (var i = 0; i < _hediffs.Count; i++)
|
for (var i = 0; i < _hediffs.Count; i++)
|
||||||
{
|
{
|
||||||
if (_hediffs[i].DefName == defName)
|
if (_hediffs[i].Def.DefName == defName)
|
||||||
{
|
{
|
||||||
_hediffs.RemoveAt(i);
|
_hediffs.RemoveAt(i);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -433,7 +433,13 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
private readonly GameClock _clock;
|
private readonly GameClock _clock;
|
||||||
private readonly float _secondsPerDay;
|
private readonly float _secondsPerDay;
|
||||||
private readonly RectF _bounds;
|
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> _eaten = [];
|
||||||
private readonly List<(Entity Self, Entity Partner)> _matings = [];
|
private readonly List<(Entity Self, Entity Partner)> _matings = [];
|
||||||
|
|
||||||
@@ -454,7 +460,7 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
_clock = clock;
|
_clock = clock;
|
||||||
_secondsPerDay = secondsPerDay;
|
_secondsPerDay = secondsPerDay;
|
||||||
_bounds = bounds;
|
_bounds = bounds;
|
||||||
_query = store.Query<AnimalBrain, AnimalNeeds, AnimalOrganism, Transform2D>();
|
_query = store.Query<AnimalBrain, AnimalNeeds, AnimalOrganism, Transform2D, Health>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUpdateGroup()
|
protected override void OnUpdateGroup()
|
||||||
@@ -469,19 +475,22 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
_eaten.Clear();
|
_eaten.Clear();
|
||||||
_matings.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 b = brains.Span;
|
||||||
var n = needsChunk.Span;
|
var n = needsChunk.Span;
|
||||||
var o = organisms.Span;
|
var o = organisms.Span;
|
||||||
var t = transforms.Span;
|
var t = transforms.Span;
|
||||||
|
var hh = healths.Span;
|
||||||
for (var i = 0; i < b.Length; i++)
|
for (var i = 0; i < b.Length; i++)
|
||||||
{
|
{
|
||||||
ref var brain = ref b[i];
|
ref var brain = ref b[i];
|
||||||
var values = n[i].Values;
|
var values = n[i].Values;
|
||||||
ref var pos = ref t[i].Position;
|
ref var pos = ref t[i].Position;
|
||||||
var def = _animalSet[o[i].Species].Def;
|
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)
|
switch (brain.Action)
|
||||||
{
|
{
|
||||||
@@ -1006,3 +1015,109 @@ public sealed class AnimalPregnancySystem : BaseSystem
|
|||||||
public Vector2 Position;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user