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))
{