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
|
||||
)
|
||||
);
|
||||
UpdateSystems.Add(
|
||||
new SeedDispersalSystem(
|
||||
Store,
|
||||
_plants,
|
||||
Context.Clock,
|
||||
SecondsPerDay,
|
||||
_config.Width,
|
||||
_config.Height,
|
||||
CellSize,
|
||||
_cellFertility,
|
||||
_cellLand
|
||||
)
|
||||
);
|
||||
|
||||
UpdateSystems.Add(
|
||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||
|
||||
@@ -484,6 +484,23 @@ public struct Egg : IComponent
|
||||
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>
|
||||
public struct ThoughtInstance
|
||||
{
|
||||
|
||||
@@ -791,6 +791,8 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась»
|
||||
private const float FleeSpeedMult = 1.2f; // страх подгоняет жертву чуть быстрее обычного шага
|
||||
private const float PostReproductiveFrac = 0.9f; // доля жизни, после которой самка уже не зачинает
|
||||
private const float SeedIngestChance = 0.15f; // шанс проглотить семя при выедании зрелого растения
|
||||
private const float GutSeedDropDays = 2f; // через сколько дней проглоченное семя высаживается
|
||||
|
||||
private readonly EntityStore _store;
|
||||
private readonly PlantSet _plants;
|
||||
@@ -815,6 +817,7 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
private readonly List<Entity> _consumed = []; // трупы, доеденные до нуля
|
||||
private readonly List<Entity> _kills = []; // добыча, забитая в этом проходе (смерть после итерации)
|
||||
private readonly List<(Entity Self, Entity Partner)> _matings = [];
|
||||
private readonly List<(Entity Eater, GutSeed Seed)> _ingested = []; // проглоченные семена (эндозоохория)
|
||||
|
||||
public AnimalActionSystem(
|
||||
EntityStore store,
|
||||
@@ -859,6 +862,7 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
_consumed.Clear();
|
||||
_kills.Clear();
|
||||
_matings.Clear();
|
||||
_ingested.Clear();
|
||||
|
||||
foreach (
|
||||
var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks
|
||||
@@ -905,6 +909,8 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
o[i].Traits.ToxinTolerance,
|
||||
days
|
||||
);
|
||||
// Эндозоохория: шанс проглотить семя зрелого растения (высадит позже).
|
||||
TryIngestSeed(brain.TargetPlant, entities.EntityAt(i));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -984,9 +990,55 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
AnimalFactory.Die(_store, _animalSet, prey, _cellSize); // забитая добыча → труп
|
||||
}
|
||||
|
||||
foreach (var (eater, seed) in _ingested)
|
||||
{
|
||||
if (!eater.IsNull && !eater.HasComponent<GutSeed>())
|
||||
{
|
||||
eater.AddComponent(seed); // структурное добавление — после прохода
|
||||
}
|
||||
}
|
||||
|
||||
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).
|
||||
@@ -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