Seed dispersal by herbivores (endozoochory)
Eating a mature plant has a chance to lodge a seed in the herbivore's gut (GutSeed component, cloned plant genome). SeedDispersalSystem ticks the timer and plants a seedling of that species wherever the animal has wandered to (if the cell is land), then drops the component. Herbivores become agents of plant spread — flora follows grazing routes — closing a plant<->animal loop on top of the existing Fruiting/PlantFactory machinery. GutSeed is transient (not serialized), like AI targets. Build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
562e365be1
commit
5ac34f4dc7
@@ -309,6 +309,19 @@ public sealed class WorldScene : Scene
|
|||||||
_config.Seed + 0x6E66
|
_config.Seed + 0x6E66
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new SeedDispersalSystem(
|
||||||
|
Store,
|
||||||
|
_plants,
|
||||||
|
Context.Clock,
|
||||||
|
SecondsPerDay,
|
||||||
|
_config.Width,
|
||||||
|
_config.Height,
|
||||||
|
CellSize,
|
||||||
|
_cellFertility,
|
||||||
|
_cellLand
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
|
|||||||
@@ -484,6 +484,23 @@ public struct Egg : IComponent
|
|||||||
public float IncubateDays;
|
public float IncubateDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проглоченное семя (эндозоохория): травоядное, съевшее плодоносящее растение, какое-то время несёт
|
||||||
|
/// его семя в кишечнике, затем «высаживает» его в другом месте (<see cref="Sim.SeedDispersalSystem"/>) —
|
||||||
|
/// зверь становится разносчиком флоры. Геном — managed-ссылка (клон родительского растения).
|
||||||
|
/// </summary>
|
||||||
|
public struct GutSeed : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Индекс вида растения в <see cref="Content.PlantSet"/>.</summary>
|
||||||
|
public int PlantSpecies;
|
||||||
|
|
||||||
|
/// <summary>Геном будущего ростка (клон съеденного растения).</summary>
|
||||||
|
public Genome Genome;
|
||||||
|
|
||||||
|
/// <summary>Остаток времени до высадки в игровых днях; ≤0 — росток падает на землю.</summary>
|
||||||
|
public float DropInDays;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
||||||
public struct ThoughtInstance
|
public struct ThoughtInstance
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -791,6 +791,8 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась»
|
private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась»
|
||||||
private const float FleeSpeedMult = 1.2f; // страх подгоняет жертву чуть быстрее обычного шага
|
private const float FleeSpeedMult = 1.2f; // страх подгоняет жертву чуть быстрее обычного шага
|
||||||
private const float PostReproductiveFrac = 0.9f; // доля жизни, после которой самка уже не зачинает
|
private const float PostReproductiveFrac = 0.9f; // доля жизни, после которой самка уже не зачинает
|
||||||
|
private const float SeedIngestChance = 0.15f; // шанс проглотить семя при выедании зрелого растения
|
||||||
|
private const float GutSeedDropDays = 2f; // через сколько дней проглоченное семя высаживается
|
||||||
|
|
||||||
private readonly EntityStore _store;
|
private readonly EntityStore _store;
|
||||||
private readonly PlantSet _plants;
|
private readonly PlantSet _plants;
|
||||||
@@ -815,6 +817,7 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
private readonly List<Entity> _consumed = []; // трупы, доеденные до нуля
|
private readonly List<Entity> _consumed = []; // трупы, доеденные до нуля
|
||||||
private readonly List<Entity> _kills = []; // добыча, забитая в этом проходе (смерть после итерации)
|
private readonly List<Entity> _kills = []; // добыча, забитая в этом проходе (смерть после итерации)
|
||||||
private readonly List<(Entity Self, Entity Partner)> _matings = [];
|
private readonly List<(Entity Self, Entity Partner)> _matings = [];
|
||||||
|
private readonly List<(Entity Eater, GutSeed Seed)> _ingested = []; // проглоченные семена (эндозоохория)
|
||||||
|
|
||||||
public AnimalActionSystem(
|
public AnimalActionSystem(
|
||||||
EntityStore store,
|
EntityStore store,
|
||||||
@@ -859,6 +862,7 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
_consumed.Clear();
|
_consumed.Clear();
|
||||||
_kills.Clear();
|
_kills.Clear();
|
||||||
_matings.Clear();
|
_matings.Clear();
|
||||||
|
_ingested.Clear();
|
||||||
|
|
||||||
foreach (
|
foreach (
|
||||||
var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks
|
var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks
|
||||||
@@ -905,6 +909,8 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
o[i].Traits.ToxinTolerance,
|
o[i].Traits.ToxinTolerance,
|
||||||
days
|
days
|
||||||
);
|
);
|
||||||
|
// Эндозоохория: шанс проглотить семя зрелого растения (высадит позже).
|
||||||
|
TryIngestSeed(brain.TargetPlant, entities.EntityAt(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -984,9 +990,55 @@ public sealed class AnimalActionSystem : BaseSystem
|
|||||||
AnimalFactory.Die(_store, _animalSet, prey, _cellSize); // забитая добыча → труп
|
AnimalFactory.Die(_store, _animalSet, prey, _cellSize); // забитая добыча → труп
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var (eater, seed) in _ingested)
|
||||||
|
{
|
||||||
|
if (!eater.IsNull && !eater.HasComponent<GutSeed>())
|
||||||
|
{
|
||||||
|
eater.AddComponent(seed); // структурное добавление — после прохода
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ApplyMatings();
|
ApplyMatings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Эндозоохория: при выедании ЗРЕЛОГО растения шанс проглотить его семя — зверь понесёт его и
|
||||||
|
// высадит позже в другом месте (SeedDispersalSystem). Геном клонируется (отдельная особь-росток).
|
||||||
|
private void TryIngestSeed(int plantId, Entity eater)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
eater.IsNull
|
||||||
|
|| eater.HasComponent<GutSeed>()
|
||||||
|
|| plantId < 0
|
||||||
|
|| _rng.NextSingle() >= SeedIngestChance
|
||||||
|
|| !_store.TryGetEntityById(plantId, out var plant)
|
||||||
|
|| plant.IsNull
|
||||||
|
|| !plant.HasComponent<PlantGrowth>()
|
||||||
|
|| !plant.HasComponent<PlantOrganism>()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref readonly var grow = ref plant.GetComponent<PlantGrowth>();
|
||||||
|
if (grow.Stage < _plants[grow.Species].Stages.Length - 1)
|
||||||
|
{
|
||||||
|
return; // только зрелое растение даёт семя
|
||||||
|
}
|
||||||
|
|
||||||
|
var genome = new Genome(plant.GetComponent<PlantOrganism>().Genome.ToDictionary());
|
||||||
|
_ingested.Add(
|
||||||
|
(
|
||||||
|
eater,
|
||||||
|
new GutSeed
|
||||||
|
{
|
||||||
|
PlantSpecies = grow.Species,
|
||||||
|
Genome = genome,
|
||||||
|
DropInDays = GutSeedDropDays,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Охота/падальщество: ведёт к цели и при контакте либо ест труп (утоляет голод, расходует мясо),
|
// Охота/падальщество: ведёт к цели и при контакте либо ест труп (утоляет голод, расходует мясо),
|
||||||
// либо кусает живую добычу — урон части тела, острая кровопотеря, рана-кровотечение (первый
|
// либо кусает живую добычу — урон части тела, острая кровопотеря, рана-кровотечение (первый
|
||||||
// травматический урон). Добитая добыча помечается на смерть (труп оставит общий путь Die).
|
// травматический урон). Добитая добыча помечается на смерть (труп оставит общий путь Die).
|
||||||
@@ -2021,3 +2073,102 @@ public sealed class AnimalHealthSystem : BaseSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Разнос семян (эндозоохория): тикает таймер проглоченного семени (<see cref="GutSeed"/>); по
|
||||||
|
/// истечении высаживает росток того вида растения там, где сейчас зверь (если клетка — суша),
|
||||||
|
/// геномом-клоном съеденного растения, и снимает компонент. Делает травоядных разносчиками флоры —
|
||||||
|
/// растения расселяются вдоль кормовых маршрутов.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SeedDispersalSystem : BaseSystem
|
||||||
|
{
|
||||||
|
private readonly EntityStore _store;
|
||||||
|
private readonly PlantSet _plants;
|
||||||
|
private readonly GameClock _clock;
|
||||||
|
private readonly float _secondsPerDay;
|
||||||
|
private readonly int _width;
|
||||||
|
private readonly int _height;
|
||||||
|
private readonly int _cellSize;
|
||||||
|
private readonly float[] _cellFertility;
|
||||||
|
private readonly bool[] _cellLand;
|
||||||
|
private readonly ArchetypeQuery<GutSeed, Transform2D> _query;
|
||||||
|
private readonly List<Entity> _dropped = [];
|
||||||
|
|
||||||
|
public SeedDispersalSystem(
|
||||||
|
EntityStore store,
|
||||||
|
PlantSet plants,
|
||||||
|
GameClock clock,
|
||||||
|
float secondsPerDay,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int cellSize,
|
||||||
|
float[] cellFertility,
|
||||||
|
bool[] cellLand
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_store = store;
|
||||||
|
_plants = plants;
|
||||||
|
_clock = clock;
|
||||||
|
_secondsPerDay = secondsPerDay;
|
||||||
|
_width = width;
|
||||||
|
_height = height;
|
||||||
|
_cellSize = cellSize;
|
||||||
|
_cellFertility = cellFertility;
|
||||||
|
_cellLand = cellLand;
|
||||||
|
_query = store.Query<GutSeed, Transform2D>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
var days = _clock.DeltaTime / _secondsPerDay;
|
||||||
|
if (days <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dropped.Clear();
|
||||||
|
foreach (var (seeds, transforms, entities) in _query.Chunks)
|
||||||
|
{
|
||||||
|
var s = seeds.Span;
|
||||||
|
var t = transforms.Span;
|
||||||
|
for (var i = 0; i < s.Length; i++)
|
||||||
|
{
|
||||||
|
ref var seed = ref s[i];
|
||||||
|
seed.DropInDays -= days;
|
||||||
|
if (seed.DropInDays > 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dropped.Add(entities.EntityAt(i));
|
||||||
|
var pos = t[i].Position;
|
||||||
|
var cx = Math.Clamp((int)(pos.X / _cellSize), 0, _width - 1);
|
||||||
|
var cy = Math.Clamp((int)(pos.Y / _cellSize), 0, _height - 1);
|
||||||
|
var cell = cy * _width + cx;
|
||||||
|
if (!_cellLand[cell])
|
||||||
|
{
|
||||||
|
continue; // упало в воду — семя пропадает
|
||||||
|
}
|
||||||
|
|
||||||
|
PlantFactory.Create(
|
||||||
|
_store,
|
||||||
|
_plants,
|
||||||
|
seed.PlantSpecies,
|
||||||
|
pos,
|
||||||
|
ageDays: 0f,
|
||||||
|
seed.Genome,
|
||||||
|
_cellFertility[cell],
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var eater in _dropped)
|
||||||
|
{
|
||||||
|
if (!eater.IsNull && eater.HasComponent<GutSeed>())
|
||||||
|
{
|
||||||
|
eater.RemoveComponent<GutSeed>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user