diff --git a/src/LittleSim/Content/GameDefs.cs b/src/LittleSim/Content/GameDefs.cs index 72c75bd..ffe45ff 100644 --- a/src/LittleSim/Content/GameDefs.cs +++ b/src/LittleSim/Content/GameDefs.cs @@ -270,6 +270,15 @@ public sealed class AnimalDef : PawnDef /// Плотность стартового спавна: голов на 1000 клеток суши (0 — вид не спавнится сам). public float SpawnPer1000Cells { get; init; } + + /// Базовая скорость передвижения (мировых единиц/сек при гене moveSpeed = 1). + public float BaseSpeed { get; init; } = 28f; + + /// Базовый радиус восприятия в клетках (× ген vision) — поиск корма/воды/партнёра. + public float VisionCells { get; init; } = 14f; + + /// Сила выедания: на сколько игровых дней роста убавляется растение за день кормёжки. + public float ForageBiteDays { get; init; } = 40f; } /// diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index 5e5b5e4..b29c1e6 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -228,7 +228,15 @@ public sealed class WorldScene : Scene ) ); UpdateSystems.Add( - new AnimalActionSystem(Store, _plants, _needs, Context.Clock, SecondsPerDay, _bounds) + new AnimalActionSystem( + Store, + _plants, + _animals, + _needs, + Context.Clock, + SecondsPerDay, + _bounds + ) ); UpdateSystems.Add(new AnimalAppearanceSystem(_needs)); UpdateSystems.Add(new AnimalGrowthSystem(Context.Clock, SecondsPerDay, _animals, CellSize)); diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index b6200de..858c0e4 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -170,7 +170,6 @@ public sealed class AnimalRutSystem(AnimalSet animals, Climate climate, HediffDe public sealed class AnimalDecisionSystem : BaseSystem { private const float Interval = 0.6f; - private const float VisionCells = 14f; // базовый радиус зрения в клетках (× ген vision) private readonly UtilityAi _brain; private readonly GameClock _clock; @@ -257,6 +256,7 @@ public sealed class AnimalDecisionSystem : BaseSystem brain.DecideIn = Interval + _rng.NextSingle() * Interval; // джиттер — расфазировать скан var pos = t[i].Position; var adult = AnimalFactory.IsAdult(_animalSet[o[i].Species], gr[i].Stage); + var radius = VisionRadius(o[i].Species, o[i].Traits.Vision); var name = _brain.Select(new AnimalContext(n[i].Values))?.Name ?? AnimalActions.Wander; @@ -264,7 +264,7 @@ public sealed class AnimalDecisionSystem : BaseSystem { case AnimalActions.Eat when EatsPlants(o[i].Species) - && TryFindPlant(pos, o[i].Traits.Vision, out var plant, out var pp): + && TryFindPlant(pos, radius, out var plant, out var pp): brain.Action = AnimalActions.Eat; brain.TargetPlant = plant; brain.Target = pp; @@ -280,13 +280,7 @@ public sealed class AnimalDecisionSystem : BaseSystem break; case AnimalActions.Mate when adult - && TryFindMate( - pos, - o[i].IsMale, - o[i].Traits.Vision, - out var mateId, - out var matePos - ): + && TryFindMate(pos, o[i].IsMale, radius, out var mateId, out var matePos): brain.Action = AnimalActions.Mate; brain.TargetPlant = -1; brain.TargetMate = mateId; @@ -306,6 +300,10 @@ public sealed class AnimalDecisionSystem : BaseSystem } } + // Радиус восприятия особи: базовая дальность вида (данные) × ген зрения × размер клетки. + private float VisionRadius(int species, float vision) => + MathF.Max(1f, _animalSet[species].Def.VisionCells) * MathF.Max(0.2f, vision) * _cellSize; + // Ест ли вид растения (диета данными) — гейт поиска корма; хищники (meat) появятся на горизонте. private bool EatsPlants(int species) { @@ -321,10 +319,9 @@ public sealed class AnimalDecisionSystem : BaseSystem return false; } - // Ближайшее растение в радиусе зрения (квадрат расстояния); ничьи — по меньшему id (детерминизм). - private bool TryFindPlant(Vector2 from, float vision, out int plantId, out Vector2 position) + // Ближайшее растение в радиусе (квадрат расстояния); ничьи — по меньшему id (детерминизм). + private bool TryFindPlant(Vector2 from, float radius, out int plantId, out Vector2 position) { - var radius = VisionCells * MathF.Max(0.2f, vision) * _cellSize; var bestSq = radius * radius; plantId = -1; position = default; @@ -374,12 +371,11 @@ public sealed class AnimalDecisionSystem : BaseSystem private bool TryFindMate( Vector2 from, bool selfMale, - float vision, + float radius, out int mateId, out Vector2 position ) { - var radius = VisionCells * MathF.Max(0.2f, vision) * _cellSize; var bestSq = radius * radius; mateId = -1; position = default; @@ -428,12 +424,11 @@ public sealed class AnimalDecisionSystem : BaseSystem /// public sealed class AnimalActionSystem : BaseSystem { - private const float BaseSpeed = 28f; // мировых единиц в секунду при moveSpeed 1 - private const float GrazePerDay = 40f; // на сколько игровых дней роста убавляется выеденное растение private const float GrazeKillAge = 1f; // трава с возрастом ниже этого после выедания исчезает private readonly EntityStore _store; private readonly PlantSet _plants; + private readonly AnimalSet _animalSet; private readonly NeedSet _needs; private readonly GameClock _clock; private readonly float _secondsPerDay; @@ -445,6 +440,7 @@ public sealed class AnimalActionSystem : BaseSystem public AnimalActionSystem( EntityStore store, PlantSet plants, + AnimalSet animals, NeedSet needs, GameClock clock, float secondsPerDay, @@ -453,6 +449,7 @@ public sealed class AnimalActionSystem : BaseSystem { _store = store; _plants = plants; + _animalSet = animals; _needs = needs; _clock = clock; _secondsPerDay = secondsPerDay; @@ -483,7 +480,8 @@ public sealed class AnimalActionSystem : BaseSystem ref var brain = ref b[i]; var values = n[i].Values; ref var pos = ref t[i].Position; - var speed = BaseSpeed * MathF.Max(0.2f, o[i].Traits.MoveSpeed); + var def = _animalSet[o[i].Species].Def; + var speed = def.BaseSpeed * MathF.Max(0.2f, o[i].Traits.MoveSpeed); switch (brain.Action) { @@ -495,7 +493,7 @@ public sealed class AnimalActionSystem : BaseSystem if (MoveTo(ref pos, brain.Target, speed * seconds)) { Refill(values, AnimalActions.Eat, days); - Graze(brain.TargetPlant, days); + Graze(brain.TargetPlant, days, def.ForageBiteDays); } break; @@ -571,7 +569,7 @@ public sealed class AnimalActionSystem : BaseSystem } // Выедание: убавляет рост растения; траву (без ствола), выеденную до нуля, помечает на гибель. - private void Graze(int plantId, float days) + private void Graze(int plantId, float days, float bitePerDay) { if ( plantId < 0 @@ -584,7 +582,7 @@ public sealed class AnimalActionSystem : BaseSystem } ref var grow = ref plant.GetComponent(); - grow.AgeDays = MathF.Max(0f, grow.AgeDays - GrazePerDay * days); + grow.AgeDays = MathF.Max(0f, grow.AgeDays - bitePerDay * days); var isGrass = _plants[grow.Species].Def.TrunkRadiusCells <= 0f; if (isGrass && grow.AgeDays <= GrazeKillAge && !_eaten.Contains(plant)) {