Животные A5 (часть 1): смерть → труп → разложение
Компонент Corpse + CorpseSystem: после смерти животное оставляет труп (AnimalFactory.CreateCorpse, мясо ∝ размер тела) вместо деспавна; труп разлагается Fresh→Rotting→Skeleton→исчезает со скоростью от температуры, тинтуясь по стадии. AnimalDef.CorpseTexture + спрайт трупа в AnimalSet; смертность спавнит труп. Продукты мясо/кости/шкура заданы (разделка/падальщики — позже). Сборка чистая, --check-content (9 типов дефов). Части тела/кровь/capacities — часть 2 A5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cbc74c0794
commit
c90bf32460
@@ -11,6 +11,7 @@
|
|||||||
"texture": "things/pawn/animal/deer/DeerFemale_east",
|
"texture": "things/pawn/animal/deer/DeerFemale_east",
|
||||||
"maleTexture": "things/pawn/animal/deer/DeerMale_east",
|
"maleTexture": "things/pawn/animal/deer/DeerMale_east",
|
||||||
"babyTexture": "things/pawn/animal/deer/DeerBaby_east",
|
"babyTexture": "things/pawn/animal/deer/DeerBaby_east",
|
||||||
|
"corpseTexture": "things/pawn/animal/deer/Dessicated_DeerFemale_east",
|
||||||
"diet": ["plant"],
|
"diet": ["plant"],
|
||||||
"spawnPer1000Cells": 2.5,
|
"spawnPer1000Cells": 2.5,
|
||||||
"genome": {
|
"genome": {
|
||||||
|
|||||||
+9
-2
@@ -1,7 +1,14 @@
|
|||||||
# Животные (зоология)
|
# Животные (зоология)
|
||||||
|
|
||||||
> Статус: **A1–A4 реализованы**, остальное по плану. Это живой план — правится по мере
|
> Статус: **A1–A4 + рефактор расширяемости + A5(труп) реализованы**, остальное по плану. Это живой
|
||||||
> реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md).
|
> план — правится по мере реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md).
|
||||||
|
>
|
||||||
|
> **A5 часть 1 (готово): смерть → труп → разложение.** `Corpse`-компонент + `CorpseSystem`
|
||||||
|
> (Fresh→Rotting→Skeleton→исчезает, скорость от температуры, тинт по стадии); смерть теперь оставляет
|
||||||
|
> труп (`AnimalFactory.CreateCorpse`, мясо ∝ размер тела) вместо деспавна; `AnimalDef.CorpseTexture` +
|
||||||
|
> `corpseTexture` оленя (`Dessicated_*`, скелет — бледный тинт, своего спрайта пока нет). Продукты
|
||||||
|
> `ProductMeat/Bone/Leather` в `products.json` (разделка/падальщики — позже). Сборка/линт чистые.
|
||||||
|
> Осталось в A5: части тела (`BodyDef`), кровь, capacities (часть 2).
|
||||||
>
|
>
|
||||||
> **A4 (готово):** нужда `Mating`; лёгкий hediff-каркас (`HediffDef`, `hediffs.json` с `Rut`,
|
> **A4 (готово):** нужда `Mating`; лёгкий hediff-каркас (`HediffDef`, `hediffs.json` с `Rut`,
|
||||||
> компонент `Health`/`HealthState`); гены `GeneBreedingSeason`/`GeneGestationDays`/`GeneLitterSize`;
|
> компонент `Health`/`HealthState`); гены `GeneBreedingSeason`/`GeneGestationDays`/`GeneLitterSize`;
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public sealed class AnimalSet
|
|||||||
/// <summary>Спрайт детёныша/подростка (fallback — самка).</summary>
|
/// <summary>Спрайт детёныша/подростка (fallback — самка).</summary>
|
||||||
public required Texture2DRegion Baby { get; init; }
|
public required Texture2DRegion Baby { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Спрайт трупа (fallback — самка).</summary>
|
||||||
|
public required Texture2DRegion Corpse { get; init; }
|
||||||
|
|
||||||
/// <summary>Стадии роста (из дефа или встроенный дефолт), по возрастанию возраста входа.</summary>
|
/// <summary>Стадии роста (из дефа или встроенный дефолт), по возрастанию возраста входа.</summary>
|
||||||
public required AnimalStageDef[] Stages { get; init; }
|
public required AnimalStageDef[] Stages { get; init; }
|
||||||
|
|
||||||
@@ -73,6 +76,9 @@ public sealed class AnimalSet
|
|||||||
Baby = string.IsNullOrEmpty(def.BabyTexture)
|
Baby = string.IsNullOrEmpty(def.BabyTexture)
|
||||||
? female
|
? female
|
||||||
: atlases.GetRegion(device, def.BabyTexture),
|
: atlases.GetRegion(device, def.BabyTexture),
|
||||||
|
Corpse = string.IsNullOrEmpty(def.CorpseTexture)
|
||||||
|
? female
|
||||||
|
: atlases.GetRegion(device, def.CorpseTexture),
|
||||||
Stages = def.Stages.Length > 0 ? def.Stages : DefaultStages,
|
Stages = def.Stages.Length > 0 ? def.Stages : DefaultStages,
|
||||||
Template = BuildTemplate(def.Genome, genes),
|
Template = BuildTemplate(def.Genome, genes),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -249,6 +249,9 @@ public sealed class AnimalDef : PawnDef
|
|||||||
/// <summary>Текстура детёныша/подростка (фаза A3); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
/// <summary>Текстура детёныша/подростка (фаза A3); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
||||||
public string BabyTexture { get; init; } = "";
|
public string BabyTexture { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Текстура трупа (фаза A5); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
||||||
|
public string CorpseTexture { get; init; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Геном вида как ДАННЫЕ: ключ — id гена (<see cref="MrGameEng.Genetics.GeneDef"/>), значение —
|
/// Геном вида как ДАННЫЕ: ключ — id гена (<see cref="MrGameEng.Genetics.GeneDef"/>), значение —
|
||||||
/// центр аллелей особи. Состав открыт — модер добавляет/убирает ген одной строкой JSON, без правки
|
/// центр аллелей особи. Состав открыт — модер добавляет/убирает ген одной строкой JSON, без правки
|
||||||
|
|||||||
@@ -240,7 +240,8 @@ public sealed class WorldScene : Scene
|
|||||||
);
|
);
|
||||||
UpdateSystems.Add(new AnimalAppearanceSystem(_needs));
|
UpdateSystems.Add(new AnimalAppearanceSystem(_needs));
|
||||||
UpdateSystems.Add(new AnimalGrowthSystem(Context.Clock, SecondsPerDay, _animals, CellSize));
|
UpdateSystems.Add(new AnimalGrowthSystem(Context.Clock, SecondsPerDay, _animals, CellSize));
|
||||||
UpdateSystems.Add(new AnimalMortalitySystem(Store));
|
UpdateSystems.Add(new AnimalMortalitySystem(Store, _animals, CellSize));
|
||||||
|
UpdateSystems.Add(new CorpseSystem(Store, Context.Clock, SecondsPerDay, climate));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new AnimalPregnancySystem(
|
new AnimalPregnancySystem(
|
||||||
Store,
|
Store,
|
||||||
|
|||||||
@@ -65,6 +65,38 @@ public static class AnimalFactory
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Сколько мяса даёт труп на единицу размера тела (для будущей разделки/падальщиков).</summary>
|
||||||
|
public const float MeatPerBodySize = 25f;
|
||||||
|
|
||||||
|
// Тинт свежего трупа (стадии гниения/скелета задаёт CorpseSystem).
|
||||||
|
private static readonly Color CorpseFreshTint = new(176, 148, 128);
|
||||||
|
|
||||||
|
/// <summary>Создаёт сущность-труп после смерти животного: спрайт-падаль, запас мяса (∝ размер тела).</summary>
|
||||||
|
public static Entity CreateCorpse(
|
||||||
|
EntityStore store,
|
||||||
|
AnimalSet.Species sp,
|
||||||
|
Vector2 position,
|
||||||
|
float bodySize,
|
||||||
|
int cellSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var region = sp.Corpse;
|
||||||
|
var sprite = new Sprite(region, GameLayers.Beings);
|
||||||
|
sprite.CenterOrigin();
|
||||||
|
sprite.Color = CorpseFreshTint;
|
||||||
|
var sizeCells = bodySize > 0f ? bodySize : sp.Def.SizeCells;
|
||||||
|
return store.CreateEntity(
|
||||||
|
new Transform2D(position, scale: new Vector2(cellSize * sizeCells / region.Width)),
|
||||||
|
sprite,
|
||||||
|
new Corpse
|
||||||
|
{
|
||||||
|
RotDays = 0f,
|
||||||
|
Stage = 0,
|
||||||
|
Meat = bodySize * MeatPerBodySize,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Пол из локуса GeneSex: наличие аллеля Y (значение ≈1) → самец; иначе самка (XX).</summary>
|
/// <summary>Пол из локуса GeneSex: наличие аллеля Y (значение ≈1) → самец; иначе самка (XX).</summary>
|
||||||
public static bool IsMaleGenome(Genome genome) =>
|
public static bool IsMaleGenome(Genome genome) =>
|
||||||
genome.Has("GeneSex") && (genome["GeneSex"].A > 0.5f || genome["GeneSex"].B > 0.5f);
|
genome.Has("GeneSex") && (genome["GeneSex"].A > 0.5f || genome["GeneSex"].B > 0.5f);
|
||||||
|
|||||||
@@ -171,6 +171,23 @@ public struct Health : IComponent
|
|||||||
public HealthState State;
|
public HealthState State;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Труп животного (фаза A5): отдельная сущность после смерти. Разлагается во времени по стадиям
|
||||||
|
/// (Fresh→Rotting→Skeleton→исчезает), скорость зависит от температуры. Несёт запас мяса (∝ размер тела)
|
||||||
|
/// для будущей разделки/падальщиков. Управляется <see cref="Sim.CorpseSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public struct Corpse : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Накоплено игровых дней разложения.</summary>
|
||||||
|
public float RotDays;
|
||||||
|
|
||||||
|
/// <summary>Стадия: 0 свежий, 1 гниющий, 2 скелет.</summary>
|
||||||
|
public int Stage;
|
||||||
|
|
||||||
|
/// <summary>Доступное мясо (∝ размер тела) — для разделки/падальщиков (фаза-горизонт).</summary>
|
||||||
|
public float Meat;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Беременность самки (фаза A4): managed-ссылка на геном отца («слепок» на момент зачатия), остаток
|
/// Беременность самки (фаза A4): managed-ссылка на геном отца («слепок» на момент зачатия), остаток
|
||||||
/// срока вынашивания и размер помёта. По истечении срока <see cref="Sim.AnimalPregnancySystem"/>
|
/// срока вынашивания и размер помёта. По истечении срока <see cref="Sim.AnimalPregnancySystem"/>
|
||||||
|
|||||||
@@ -742,19 +742,25 @@ public sealed class AnimalGrowthSystem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Смертность (фаза A3): простая гибель с деспавном — от старости (возраст превысил продолжительность
|
/// Смертность (фаза A3/A5): гибель от старости (возраст превысил продолжительность жизни) или истощения
|
||||||
/// жизни) или от истощения (счётчик голода/жажды превысил порог). Замыкает контур ёмкости среды.
|
/// (счётчик голода/жажды превысил порог). Замыкает контур ёмкости среды. Фаза A5: вместо деспавна
|
||||||
/// На A5 «апгрейдится» в смерть через здоровье и оставит труп вместо деспавна.
|
/// оставляет <see cref="Corpse"/> (через <see cref="AnimalFactory.CreateCorpse"/>), затем удаляет особь.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AnimalMortalitySystem : BaseSystem
|
public sealed class AnimalMortalitySystem : BaseSystem
|
||||||
{
|
{
|
||||||
private const float LethalStarveDays = 3f;
|
private const float LethalStarveDays = 3f;
|
||||||
|
|
||||||
|
private readonly EntityStore _store;
|
||||||
|
private readonly AnimalSet _animals;
|
||||||
|
private readonly int _cellSize;
|
||||||
private readonly ArchetypeQuery<AnimalGrowth, AnimalOrganism, AnimalNeeds> _query;
|
private readonly ArchetypeQuery<AnimalGrowth, AnimalOrganism, AnimalNeeds> _query;
|
||||||
private readonly List<Entity> _deaths = [];
|
private readonly List<Entity> _deaths = [];
|
||||||
|
|
||||||
public AnimalMortalitySystem(EntityStore store)
|
public AnimalMortalitySystem(EntityStore store, AnimalSet animals, int cellSize)
|
||||||
{
|
{
|
||||||
|
_store = store;
|
||||||
|
_animals = animals;
|
||||||
|
_cellSize = cellSize;
|
||||||
_query = store.Query<AnimalGrowth, AnimalOrganism, AnimalNeeds>();
|
_query = store.Query<AnimalGrowth, AnimalOrganism, AnimalNeeds>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,7 +781,99 @@ public sealed class AnimalMortalitySystem : BaseSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in _deaths)
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Разложение трупа (фаза A5): копит дни гниения (ускоряется в тепле, замедляется на морозе),
|
||||||
|
/// переключает стадии Fresh→Rotting→Skeleton по порогам и тинтит спрайт; по достижении предела труп
|
||||||
|
/// исчезает. Скелет рисуется бледным тинтом падали (отдельного спрайта пока нет — см. docs).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CorpseSystem : BaseSystem
|
||||||
|
{
|
||||||
|
private const float RottingDay = 2f;
|
||||||
|
private const float SkeletonDay = 6f;
|
||||||
|
private const float GoneDay = 14f;
|
||||||
|
|
||||||
|
private static readonly Color FreshTint = new(176, 148, 128);
|
||||||
|
private static readonly Color RottingTint = new(120, 118, 98);
|
||||||
|
private static readonly Color SkeletonTint = new(228, 226, 214);
|
||||||
|
|
||||||
|
private readonly GameClock _clock;
|
||||||
|
private readonly float _secondsPerDay;
|
||||||
|
private readonly Climate _climate;
|
||||||
|
private readonly ArchetypeQuery<Corpse, Sprite> _query;
|
||||||
|
private readonly List<Entity> _gone = [];
|
||||||
|
|
||||||
|
public CorpseSystem(EntityStore store, GameClock clock, float secondsPerDay, Climate climate)
|
||||||
|
{
|
||||||
|
_clock = clock;
|
||||||
|
_secondsPerDay = secondsPerDay;
|
||||||
|
_climate = climate;
|
||||||
|
_query = store.Query<Corpse, Sprite>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
var days = _clock.DeltaTime / _secondsPerDay;
|
||||||
|
if (days <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Тепло ускоряет гниение, около нуля и ниже — резко замедляет (заморозка).
|
||||||
|
var factor = Math.Clamp((_climate.Temperature + 5f) / 20f, 0.1f, 2.5f);
|
||||||
|
var advance = days * factor;
|
||||||
|
_gone.Clear();
|
||||||
|
|
||||||
|
foreach (var (corpses, sprites, entities) in _query.Chunks)
|
||||||
|
{
|
||||||
|
var c = corpses.Span;
|
||||||
|
var s = sprites.Span;
|
||||||
|
for (var i = 0; i < c.Length; i++)
|
||||||
|
{
|
||||||
|
ref var corpse = ref c[i];
|
||||||
|
corpse.RotDays += advance;
|
||||||
|
if (corpse.RotDays >= GoneDay)
|
||||||
|
{
|
||||||
|
_gone.Add(entities.EntityAt(i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stage =
|
||||||
|
corpse.RotDays >= SkeletonDay ? 2
|
||||||
|
: corpse.RotDays >= RottingDay ? 1
|
||||||
|
: 0;
|
||||||
|
if (stage != corpse.Stage)
|
||||||
|
{
|
||||||
|
corpse.Stage = stage;
|
||||||
|
s[i].Color =
|
||||||
|
stage == 2 ? SkeletonTint
|
||||||
|
: stage == 1 ? RottingTint
|
||||||
|
: FreshTint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in _gone)
|
||||||
{
|
{
|
||||||
entity.DeleteEntity();
|
entity.DeleteEntity();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user