Animal thermoregulation (insulation) + wound infection

Two health threads finished, both via the existing hediff engine in
AnimalHealthSystem (now also takes Climate + the new hediff defs):

- Thermoregulation revives the dead GeneInsulation. A comfort band is derived
  per animal: fur (insulation) widens cold tolerance but worsens heat tolerance,
  larger body resists cold. Outside it, Hypothermia/Heatstroke severity builds
  (Poisoned-pattern: Intensify zeroes immunity so it can't recover while still
  exposed), capacities drop, severity 1 = death; back in comfort it heals.
  Seasonal/climate mortality and a real cold↔heat adaptation tradeoff.
- Wound infection marries the injury and disease systems: while bleeding, a
  chance (∝ wound severity) to contract Infection, a progressing disease fought
  by immunity × blood filtration — weak kidneys/liver or many wounds = worse.

New hediffs Hypothermia/Heatstroke/Infection (+ru/en). Build + --check-content
clean. Not GUI-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 16:53:27 +03:00
co-authored by Claude Opus 4.8
parent 5b0cf1dd4a
commit 562e365be1
5 changed files with 97 additions and 1 deletions
+23
View File
@@ -32,6 +32,29 @@
"defName": "Poisoned", "label": "hediff.poisoned", "defName": "Poisoned", "label": "hediff.poisoned",
"initialSeverity": 0.0, "immunityPerDay": 0.65, "lethalSeverity": 1.0, "pain": 0.2, "initialSeverity": 0.0, "immunityPerDay": 0.65, "lethalSeverity": 1.0, "pain": 0.2,
"capMods": { "Moving": 0.85, "Consciousness": 0.9, "Digestion": 0.7 } "capMods": { "Moving": 0.85, "Consciousness": 0.9, "Digestion": 0.7 }
},
// Терморегуляция (как Poisoned-паттерн): сами не прогрессируют — тяжесть копится, пока зверю
// холодно/жарко (см. AnimalHealthSystem, зависит от GeneInsulation + размера тела), а в комфорте
// immunityPerDay их рассасывает. Тяжесть 1 = смерть от переохлаждения/перегрева.
{
"defName": "Hypothermia", "label": "hediff.hypothermia",
"initialSeverity": 0.0, "immunityPerDay": 0.5, "lethalSeverity": 1.0, "pain": 0.15,
"capMods": { "Moving": 0.7, "Consciousness": 0.8 }
},
{
"defName": "Heatstroke", "label": "hediff.heatstroke",
"initialSeverity": 0.0, "immunityPerDay": 0.5, "lethalSeverity": 1.0, "pain": 0.15,
"capMods": { "Moving": 0.75, "Consciousness": 0.8 }
},
// Заражение раны (предатор/шипы → кровотечение → инфекция): прогрессирует, иммунитет (× фильтрация
// крови) душит. Слабая фильтрация/много ран → выше риск и хуже исход.
{
"defName": "Infection", "label": "hediff.infection",
"initialSeverity": 0.08, "severityPerDay": 0.2, "immunityPerDay": 0.28,
"lethalSeverity": 1.0, "pain": 0.25,
"capMods": { "Moving": 0.8, "Consciousness": 0.85, "BloodFiltration": 0.8 }
} }
] ]
} }
+3
View File
@@ -73,6 +73,9 @@
"hediff.fever": "fever", "hediff.fever": "fever",
"hediff.bleeding": "bleeding", "hediff.bleeding": "bleeding",
"hediff.poisoned": "poisoned", "hediff.poisoned": "poisoned",
"hediff.hypothermia": "hypothermia",
"hediff.heatstroke": "heatstroke",
"hediff.infection": "infection",
"inspect.hint": "LMB — select · RMB/Esc — clear", "inspect.hint": "LMB — select · RMB/Esc — clear",
"hud.paused": "PAUSED", "hud.paused": "PAUSED",
"menu.title": "LittleSim", "menu.title": "LittleSim",
+3
View File
@@ -73,6 +73,9 @@
"hediff.fever": "лихорадка", "hediff.fever": "лихорадка",
"hediff.bleeding": "кровотечение", "hediff.bleeding": "кровотечение",
"hediff.poisoned": "отравление", "hediff.poisoned": "отравление",
"hediff.hypothermia": "переохлаждение",
"hediff.heatstroke": "тепловой удар",
"hediff.infection": "заражение",
"inspect.hint": "ЛКМ — выбрать · ПКМ/Esc — снять", "inspect.hint": "ЛКМ — выбрать · ПКМ/Esc — снять",
"hud.paused": "ПАУЗА", "hud.paused": "ПАУЗА",
"menu.title": "LittleSim", "menu.title": "LittleSim",
+4
View File
@@ -242,7 +242,11 @@ public sealed class WorldScene : Scene
CellSize, CellSize,
Context.Clock, Context.Clock,
SecondsPerDay, SecondsPerDay,
climate,
content.Defs.All<HediffDef>().Where(h => h.AmbientPerDay > 0f).ToArray(), content.Defs.All<HediffDef>().Where(h => h.AmbientPerDay > 0f).ToArray(),
content.Defs.TryGet<HediffDef>("Hypothermia", out var hypo) ? hypo : null,
content.Defs.TryGet<HediffDef>("Heatstroke", out var heat) ? heat : null,
content.Defs.TryGet<HediffDef>("Infection", out var infe) ? infe : null,
_config.Seed + 0x3EAD _config.Seed + 0x3EAD
) )
); );
+64 -1
View File
@@ -1833,7 +1833,11 @@ public sealed class AnimalHealthSystem : BaseSystem
private readonly int _cellSize; private readonly int _cellSize;
private readonly GameClock _clock; private readonly GameClock _clock;
private readonly float _secondsPerDay; private readonly float _secondsPerDay;
private readonly Climate _climate;
private readonly HediffDef[] _ambient; private readonly HediffDef[] _ambient;
private readonly HediffDef? _hypothermia;
private readonly HediffDef? _heatstroke;
private readonly HediffDef? _infection;
private readonly Random _rng; private readonly Random _rng;
private readonly ArchetypeQuery<Health, AnimalOrganism, AnimalGrowth> _query; private readonly ArchetypeQuery<Health, AnimalOrganism, AnimalGrowth> _query;
private readonly List<Entity> _deaths = []; private readonly List<Entity> _deaths = [];
@@ -1844,13 +1848,28 @@ public sealed class AnimalHealthSystem : BaseSystem
private const float SenescenceStart = 0.6f; private const float SenescenceStart = 0.6f;
private const float SenescenceMax = 0.45f; private const float SenescenceMax = 0.45f;
// Термокомфорт (°C) при теплоизоляции 0.5 и размере тела 1; границы сдвигаются мехом и размером тела.
private const float ColdComfort = 2f; // ниже — мёрзнет
private const float HeatComfort = 30f; // выше — перегревается
private const float ColdInsulationBonus = 30f; // мех (insulation 0..1) расширяет холодоустойчивость
private const float HeatInsulationPenalty = 14f; // тот же мех ухудшает жароустойчивость
private const float ColdBodyBonus = 8f; // крупное тело (тепловая инерция) держит холод лучше
private const float ThermalRatePerDegree = 0.06f; // прирост тяжести за °C за пределом комфорта в день
// Заражение раны: шанс в день при кровотечении тяжести 1 подхватить инфекцию.
private const float InfectionChancePerDay = 0.25f;
public AnimalHealthSystem( public AnimalHealthSystem(
EntityStore store, EntityStore store,
AnimalSet animals, AnimalSet animals,
int cellSize, int cellSize,
GameClock clock, GameClock clock,
float secondsPerDay, float secondsPerDay,
Climate climate,
HediffDef[] ambient, HediffDef[] ambient,
HediffDef? hypothermia,
HediffDef? heatstroke,
HediffDef? infection,
int seed int seed
) )
{ {
@@ -1859,7 +1878,11 @@ public sealed class AnimalHealthSystem : BaseSystem
_cellSize = cellSize; _cellSize = cellSize;
_clock = clock; _clock = clock;
_secondsPerDay = secondsPerDay; _secondsPerDay = secondsPerDay;
_climate = climate;
_ambient = ambient; _ambient = ambient;
_hypothermia = hypothermia;
_heatstroke = heatstroke;
_infection = infection;
_rng = new Random(seed); _rng = new Random(seed);
_query = store.Query<Health, AnimalOrganism, AnimalGrowth>(); _query = store.Query<Health, AnimalOrganism, AnimalGrowth>();
} }
@@ -1912,8 +1935,48 @@ public sealed class AnimalHealthSystem : BaseSystem
} }
} }
// Терморегуляция: вне комфорта (зависит от GeneInsulation + размера тела) копится тяжесть
// гипотермии/теплового удара; в комфорте immunityPerDay их рассасывает. Мех расширяет
// холодоустойчивость, но ухудшает жароустойчивость; крупное тело держит холод.
var insul = Math.Clamp(o[i].Traits.Insulation, 0f, 1f);
var coldLimit =
ColdComfort
- ColdInsulationBonus * insul
- ColdBodyBonus * MathF.Max(0f, o[i].Traits.BodySize - 1f);
var heatLimit = HeatComfort - HeatInsulationPenalty * insul;
var temp = _climate.Temperature;
if (temp < coldLimit && _hypothermia is not null)
{
state.Intensify(_hypothermia, (coldLimit - temp) * ThermalRatePerDegree * days);
}
else if (temp > heatLimit && _heatstroke is not null)
{
state.Intensify(_heatstroke, (temp - heatLimit) * ThermalRatePerDegree * days);
}
// Заражение раны: пока есть кровотечение, шанс (∝ его тяжести) подхватить инфекцию.
if (_infection is not null && !state.Has(_infection.DefName))
{
var bleedSeverity = 0f;
foreach (var hd in state.Hediffs)
{
if (hd.Def.BloodLossPerDay > 0f && hd.Severity > bleedSeverity)
{
bleedSeverity = hd.Severity;
}
}
if (
bleedSeverity > 0f
&& _rng.NextSingle() < InfectionChancePerDay * bleedSeverity * days
)
{
state.Add(_infection);
}
}
// Фильтрация крови (почки/печень) ускоряет вывод токсинов и набор иммунитета: повреждённый // Фильтрация крови (почки/печень) ускоряет вывод токсинов и набор иммунитета: повреждённый
// орган → медленнее выздоровление от яда/болезни (для тела без неё Capacity вернёт 1). // орган → медленнее выздоровление от яда/болезни/инфекции (для тела без неё Capacity вернёт 1).
var lethal = state.AdvanceHediffs( var lethal = state.AdvanceHediffs(
days, days,
state.Capacity(AnimalCapacities.BloodFiltration) state.Capacity(AnimalCapacities.BloodFiltration)