Merge branch 'senses-memory' into main
Animal senses (sight/hearing/smell/touch) and memory: smell/touch capacities + organs (reduced by hediffs/damage), acuity + detectability genes, multi-sense threat/prey detection with a coarse smell zone, and a brain-bounded memory that records threats and steers wandering animals away from danger zones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,12 @@
|
|||||||
"GeneMoveSpeed": 1.2,
|
"GeneMoveSpeed": 1.2,
|
||||||
"GeneBloodVolume": 1.4,
|
"GeneBloodVolume": 1.4,
|
||||||
"GeneVision": 1.3,
|
"GeneVision": 1.3,
|
||||||
|
"GeneHearing": 1.4,
|
||||||
|
"GeneSmell": 1.2,
|
||||||
|
"GeneTouch": 1.0,
|
||||||
|
"GeneCamouflage": 0.35,
|
||||||
|
"GeneScent": 0.5,
|
||||||
|
"GeneNoise": 0.4,
|
||||||
"GeneBrainSize": 0.45,
|
"GeneBrainSize": 0.45,
|
||||||
"GeneInsulation": 0.55,
|
"GeneInsulation": 0.55,
|
||||||
"GeneFurColor": 0.45,
|
"GeneFurColor": 0.45,
|
||||||
@@ -59,6 +65,12 @@
|
|||||||
"GeneMoveSpeed": 1.4,
|
"GeneMoveSpeed": 1.4,
|
||||||
"GeneBloodVolume": 1.3,
|
"GeneBloodVolume": 1.3,
|
||||||
"GeneVision": 1.5,
|
"GeneVision": 1.5,
|
||||||
|
"GeneHearing": 1.5,
|
||||||
|
"GeneSmell": 1.8,
|
||||||
|
"GeneTouch": 1.0,
|
||||||
|
"GeneCamouflage": 0.2,
|
||||||
|
"GeneScent": 0.6,
|
||||||
|
"GeneNoise": 0.5,
|
||||||
"GeneBrainSize": 0.5,
|
"GeneBrainSize": 0.5,
|
||||||
"GeneInsulation": 0.6,
|
"GeneInsulation": 0.6,
|
||||||
"GeneFurColor": 0.7,
|
"GeneFurColor": 0.7,
|
||||||
@@ -93,6 +105,12 @@
|
|||||||
"GeneMoveSpeed": 1.0,
|
"GeneMoveSpeed": 1.0,
|
||||||
"GeneBloodVolume": 1.2,
|
"GeneBloodVolume": 1.2,
|
||||||
"GeneVision": 1.0,
|
"GeneVision": 1.0,
|
||||||
|
"GeneHearing": 1.2,
|
||||||
|
"GeneSmell": 1.6,
|
||||||
|
"GeneTouch": 1.0,
|
||||||
|
"GeneCamouflage": 0.2,
|
||||||
|
"GeneScent": 0.7,
|
||||||
|
"GeneNoise": 0.7,
|
||||||
"GeneBrainSize": 0.42,
|
"GeneBrainSize": 0.42,
|
||||||
"GeneInsulation": 0.5,
|
"GeneInsulation": 0.5,
|
||||||
"GeneFurColor": 0.85,
|
"GeneFurColor": 0.85,
|
||||||
@@ -126,6 +144,12 @@
|
|||||||
"GeneMoveSpeed": 0.9,
|
"GeneMoveSpeed": 0.9,
|
||||||
"GeneBloodVolume": 0.7,
|
"GeneBloodVolume": 0.7,
|
||||||
"GeneVision": 1.1,
|
"GeneVision": 1.1,
|
||||||
|
"GeneHearing": 1.0,
|
||||||
|
"GeneSmell": 0.6,
|
||||||
|
"GeneTouch": 1.0,
|
||||||
|
"GeneCamouflage": 0.2,
|
||||||
|
"GeneScent": 0.4,
|
||||||
|
"GeneNoise": 0.6,
|
||||||
"GeneBrainSize": 0.3,
|
"GeneBrainSize": 0.3,
|
||||||
"GeneInsulation": 0.45,
|
"GeneInsulation": 0.45,
|
||||||
"GeneFurColor": 0.6,
|
"GeneFurColor": 0.6,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@
|
|||||||
"capacities": { "Hearing": 0.5 } },
|
"capacities": { "Hearing": 0.5 } },
|
||||||
{ "name": "earRight", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
{ "name": "earRight", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
"capacities": { "Hearing": 0.5 } },
|
"capacities": { "Hearing": 0.5 } },
|
||||||
|
{ "name": "nose", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
|
"capacities": { "Smell": 1.0 } },
|
||||||
|
{ "name": "skin", "coverage": 0.05, "maxHp": 20,
|
||||||
|
"capacities": { "Touch": 1.0 } },
|
||||||
{ "name": "jaw", "parent": "head", "coverage": 0.02, "maxHp": 10,
|
{ "name": "jaw", "parent": "head", "coverage": 0.02, "maxHp": 10,
|
||||||
"capacities": { "Eating": 1.0, "Talking": 0.5 } },
|
"capacities": { "Eating": 1.0, "Talking": 0.5 } },
|
||||||
{ "name": "tongue", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
{ "name": "tongue", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
|
|||||||
@@ -18,6 +18,29 @@
|
|||||||
{ "defName": "GeneVision", "parent": "BaseNumericGene", "label": "gene.vision",
|
{ "defName": "GeneVision", "parent": "BaseNumericGene", "label": "gene.vision",
|
||||||
"default": 1.0, "min": 0.2, "max": 3.0, "tags": ["animal", "senses"],
|
"default": 1.0, "min": 0.2, "max": 3.0, "tags": ["animal", "senses"],
|
||||||
"effects": { "vision": "value" } },
|
"effects": { "vision": "value" } },
|
||||||
|
|
||||||
|
// --- Чувства (острота = множитель радиуса восприятия) и заметность (как видят/слышат/чуют ЭТУ особь) ---
|
||||||
|
// Острота работает вместе со способностью органа (глаза/уши/нос/кожа): ген × capacity × радиус чувства.
|
||||||
|
{ "defName": "GeneHearing", "parent": "BaseNumericGene", "label": "gene.hearing",
|
||||||
|
"default": 1.0, "min": 0.2, "max": 3.0, "tags": ["animal", "senses"],
|
||||||
|
"effects": { "hearing": "value" } },
|
||||||
|
{ "defName": "GeneSmell", "parent": "BaseNumericGene", "label": "gene.smell",
|
||||||
|
"default": 1.0, "min": 0.2, "max": 3.0, "tags": ["animal", "senses"],
|
||||||
|
"effects": { "smell": "value" } },
|
||||||
|
{ "defName": "GeneTouch", "parent": "BaseNumericGene", "label": "gene.touch",
|
||||||
|
"default": 1.0, "min": 0.2, "max": 3.0, "tags": ["animal", "senses"],
|
||||||
|
"effects": { "touch": "value" } },
|
||||||
|
// Заметность цели: камуфляж снижает обнаружение зрением (1−camouflage), запах повышает обонянием,
|
||||||
|
// шум при движении повышает обнаружение слухом. См. формулу радиуса в системе восприятия.
|
||||||
|
{ "defName": "GeneCamouflage", "parent": "BaseNumericGene", "label": "gene.camouflage",
|
||||||
|
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["animal", "stealth"],
|
||||||
|
"effects": { "camouflage": "value" } },
|
||||||
|
{ "defName": "GeneScent", "parent": "BaseNumericGene", "label": "gene.scent",
|
||||||
|
"default": 0.5, "min": 0.0, "max": 1.0, "tags": ["animal", "stealth"],
|
||||||
|
"effects": { "scent": "value" } },
|
||||||
|
{ "defName": "GeneNoise", "parent": "BaseNumericGene", "label": "gene.noise",
|
||||||
|
"default": 0.5, "min": 0.0, "max": 1.0, "tags": ["animal", "stealth"],
|
||||||
|
"effects": { "noise": "value" } },
|
||||||
{ "defName": "GeneBrainSize", "parent": "BaseNumericGene", "label": "gene.brainSize",
|
{ "defName": "GeneBrainSize", "parent": "BaseNumericGene", "label": "gene.brainSize",
|
||||||
"default": 0.45, "min": 0.0, "max": 1.0, "spread": 0.03, "tags": ["animal", "brain"],
|
"default": 0.45, "min": 0.0, "max": 1.0, "spread": 0.03, "tags": ["animal", "brain"],
|
||||||
"effects": { "brainSize": "value" } },
|
"effects": { "brainSize": "value" } },
|
||||||
|
|||||||
@@ -70,6 +70,8 @@
|
|||||||
"cap.moving": "moving",
|
"cap.moving": "moving",
|
||||||
"cap.sight": "sight",
|
"cap.sight": "sight",
|
||||||
"cap.hearing": "hearing",
|
"cap.hearing": "hearing",
|
||||||
|
"cap.smell": "smell",
|
||||||
|
"cap.touch": "touch",
|
||||||
"cap.talking": "talking",
|
"cap.talking": "talking",
|
||||||
"cap.eating": "eating",
|
"cap.eating": "eating",
|
||||||
"cap.breathing": "breathing",
|
"cap.breathing": "breathing",
|
||||||
|
|||||||
@@ -70,6 +70,8 @@
|
|||||||
"cap.moving": "движение",
|
"cap.moving": "движение",
|
||||||
"cap.sight": "зрение",
|
"cap.sight": "зрение",
|
||||||
"cap.hearing": "слух",
|
"cap.hearing": "слух",
|
||||||
|
"cap.smell": "обоняние",
|
||||||
|
"cap.touch": "осязание",
|
||||||
"cap.talking": "речь",
|
"cap.talking": "речь",
|
||||||
"cap.eating": "питание",
|
"cap.eating": "питание",
|
||||||
"cap.breathing": "дыхание",
|
"cap.breathing": "дыхание",
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ public sealed class WorldScene : Scene
|
|||||||
var thoughts = new ThoughtSet(content);
|
var thoughts = new ThoughtSet(content);
|
||||||
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs, _animals));
|
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs, _animals));
|
||||||
UpdateSystems.Add(new AnimalSkillSystem(Context.Clock, SecondsPerDay, _skills));
|
UpdateSystems.Add(new AnimalSkillSystem(Context.Clock, SecondsPerDay, _skills));
|
||||||
|
UpdateSystems.Add(new AnimalMemorySystem(Context.Clock, SecondsPerDay));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
|
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,9 +83,21 @@ public static class AnimalFactory
|
|||||||
Passions = skills.RollPassions(new Random(SkillSeed(genome) ^ species)),
|
Passions = skills.RollPassions(new Random(SkillSeed(genome) ^ species)),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Память: объём ∝ размеру мозга (примитивные ~0 — ничего не помнят).
|
||||||
|
entity.AddComponent(
|
||||||
|
new Memory
|
||||||
|
{
|
||||||
|
Entries = [],
|
||||||
|
Capacity = (int)MathF.Round(MathF.Max(0f, traits.BrainSize) * MemoryPerBrain),
|
||||||
|
}
|
||||||
|
);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Сколько записей памяти даёт единица размера мозга (объём памяти = brainSize × это).</summary>
|
||||||
|
public const float MemoryPerBrain = 16f;
|
||||||
|
|
||||||
// Стабильный сид из генома (сумма аллелей — порядконезависима) для детерминированной страсти к навыкам.
|
// Стабильный сид из генома (сумма аллелей — порядконезависима) для детерминированной страсти к навыкам.
|
||||||
private static int SkillSeed(Genome genome)
|
private static int SkillSeed(Genome genome)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Friflo.Engine.ECS;
|
using Friflo.Engine.ECS;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Genetics;
|
using MrGameEng.Genetics;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
@@ -27,6 +28,24 @@ public struct AnimalPhenotype
|
|||||||
/// <summary>Дальность зрения (множитель радиуса поиска).</summary>
|
/// <summary>Дальность зрения (множитель радиуса поиска).</summary>
|
||||||
public float Vision;
|
public float Vision;
|
||||||
|
|
||||||
|
/// <summary>Острота слуха (множитель радиуса).</summary>
|
||||||
|
public float Hearing;
|
||||||
|
|
||||||
|
/// <summary>Острота обоняния (множитель радиуса).</summary>
|
||||||
|
public float Smell;
|
||||||
|
|
||||||
|
/// <summary>Острота осязания (множитель радиуса; чувство ближнего контакта).</summary>
|
||||||
|
public float Touch;
|
||||||
|
|
||||||
|
/// <summary>Камуфляж [0..1] — снижает обнаружение этой особи зрением (заметность = 1−camouflage).</summary>
|
||||||
|
public float Camouflage;
|
||||||
|
|
||||||
|
/// <summary>Запах [0..1] — повышает обнаружение этой особи обонянием.</summary>
|
||||||
|
public float Scent;
|
||||||
|
|
||||||
|
/// <summary>Шумность [0..1] — повышает обнаружение этой особи слухом при движении.</summary>
|
||||||
|
public float Noise;
|
||||||
|
|
||||||
/// <summary>Размер мозга (0..1) — основа интеллекта.</summary>
|
/// <summary>Размер мозга (0..1) — основа интеллекта.</summary>
|
||||||
public float BrainSize;
|
public float BrainSize;
|
||||||
|
|
||||||
@@ -76,6 +95,8 @@ public struct AnimalPhenotype
|
|||||||
public static AnimalPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
public static AnimalPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
||||||
{
|
{
|
||||||
float T(string name) => traits.GetValueOrDefault(name);
|
float T(string name) => traits.GetValueOrDefault(name);
|
||||||
|
// Острота чувств по умолчанию 1 (вид без гена не «слепнет»); заметность — мягкие дефолты.
|
||||||
|
float Acuity(string name) => traits.TryGetValue(name, out var v) ? v : 1f;
|
||||||
return new AnimalPhenotype
|
return new AnimalPhenotype
|
||||||
{
|
{
|
||||||
BodySize = T("bodySize"),
|
BodySize = T("bodySize"),
|
||||||
@@ -83,6 +104,12 @@ public struct AnimalPhenotype
|
|||||||
MoveSpeed = T("moveSpeed"),
|
MoveSpeed = T("moveSpeed"),
|
||||||
BloodVolume = T("bloodVolume"),
|
BloodVolume = T("bloodVolume"),
|
||||||
Vision = T("vision"),
|
Vision = T("vision"),
|
||||||
|
Hearing = Acuity("hearing"),
|
||||||
|
Smell = Acuity("smell"),
|
||||||
|
Touch = Acuity("touch"),
|
||||||
|
Camouflage = T("camouflage"),
|
||||||
|
Scent = traits.GetValueOrDefault("scent", 0.5f),
|
||||||
|
Noise = traits.GetValueOrDefault("noise", 0.5f),
|
||||||
BrainSize = T("brainSize"),
|
BrainSize = T("brainSize"),
|
||||||
Insulation = T("insulation"),
|
Insulation = T("insulation"),
|
||||||
FurHue = T("furHue"),
|
FurHue = T("furHue"),
|
||||||
@@ -483,6 +510,89 @@ public struct Egg : IComponent
|
|||||||
public float IncubateDays;
|
public float IncubateDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Вид запомненного: угроза (избегать), корм/вода/партнёр (идти) — расширяемо.</summary>
|
||||||
|
public enum MemoryKind : byte
|
||||||
|
{
|
||||||
|
Threat,
|
||||||
|
Food,
|
||||||
|
Water,
|
||||||
|
Mate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Запись памяти: что, где (воспринятая позиция — грубая, если по запаху), насколько уверенно
|
||||||
|
/// (падает со временем — забывание), сколько дней назад.</summary>
|
||||||
|
public struct MemoryEntry
|
||||||
|
{
|
||||||
|
public MemoryKind Kind;
|
||||||
|
public Vector2 Pos;
|
||||||
|
public float Confidence;
|
||||||
|
public float AgeDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Память особи (объём ∝ мозгу): ограниченный набор записей о замеченном (сейчас — угрозы). Чувства
|
||||||
|
/// пишут сюда воспринятое; со временем уверенность падает и запись забывается; ИИ использует память,
|
||||||
|
/// когда цель не воспринимается сейчас (избегает зоны недавнего хищника). Список — managed-ссылка.
|
||||||
|
/// </summary>
|
||||||
|
public struct Memory : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Активные записи (упорядочены по добавлению; слабейшая вытесняется при переполнении).</summary>
|
||||||
|
public List<MemoryEntry> Entries;
|
||||||
|
|
||||||
|
/// <summary>Сколько записей вмещает (∝ размеру мозга; 0 — память отсутствует).</summary>
|
||||||
|
public int Capacity;
|
||||||
|
|
||||||
|
/// <summary>Запоминает/освежает наблюдение: близкое того же вида — обновляет, иначе добавляет (с
|
||||||
|
/// вытеснением слабейшего при переполнении). Вне ёмкости (Capacity 0) — ничего не помнит.</summary>
|
||||||
|
public readonly void Remember(MemoryKind kind, Vector2 pos, float mergeDist)
|
||||||
|
{
|
||||||
|
if (Entries is null || Capacity <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergeSq = mergeDist * mergeDist;
|
||||||
|
for (var i = 0; i < Entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (Entries[i].Kind == kind && Vector2.DistanceSquared(Entries[i].Pos, pos) <= mergeSq)
|
||||||
|
{
|
||||||
|
Entries[i] = new MemoryEntry
|
||||||
|
{
|
||||||
|
Kind = kind,
|
||||||
|
Pos = pos,
|
||||||
|
Confidence = 1f,
|
||||||
|
AgeDays = 0f,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Entries.Count >= Capacity)
|
||||||
|
{
|
||||||
|
var weakest = 0;
|
||||||
|
for (var i = 1; i < Entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (Entries[i].Confidence < Entries[weakest].Confidence)
|
||||||
|
{
|
||||||
|
weakest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entries.RemoveAt(weakest);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entries.Add(
|
||||||
|
new MemoryEntry
|
||||||
|
{
|
||||||
|
Kind = kind,
|
||||||
|
Pos = pos,
|
||||||
|
Confidence = 1f,
|
||||||
|
AgeDays = 0f,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Id навыков-эффектов (на них ссылается код начисления XP и применения бонуса).</summary>
|
/// <summary>Id навыков-эффектов (на них ссылается код начисления XP и применения бонуса).</summary>
|
||||||
public static class AnimalSkills
|
public static class AnimalSkills
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -268,10 +268,14 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
// корма/угрозы/добычи/партнёра/стада берёт только ближние сущности вместо прохода по всем (было
|
// корма/угрозы/добычи/партнёра/стада берёт только ближние сущности вместо прохода по всем (было
|
||||||
// O(растений)+O(животных) на каждое решение). В ячейке — предпосчитанные признаки (без дорефетча).
|
// O(растений)+O(животных) на каждое решение). В ячейке — предпосчитанные признаки (без дорефетча).
|
||||||
private const int GridCells = 10; // размер ячейки сетки в клетках мира (~радиус восприятия)
|
private const int GridCells = 10; // размер ячейки сетки в клетках мира (~радиус восприятия)
|
||||||
|
private const float HearingRangeMult = 1.4f; // слух шире зрения
|
||||||
|
private const float SmellRangeMult = 1.8f; // запах — дальше всех (но грубый, см. Detect)
|
||||||
|
private const float RememberedThreatAvoidCells = 14f; // в этой зоне от памяти об угрозе зверь уходит прочь
|
||||||
private readonly SpatialGrid<ForageEntry> _plantGrid;
|
private readonly SpatialGrid<ForageEntry> _plantGrid;
|
||||||
private readonly List<ForageEntry> _plantBuf = [];
|
private readonly List<ForageEntry> _plantBuf = [];
|
||||||
private readonly SpatialGrid<NeighborEntry> _animalGrid;
|
private readonly SpatialGrid<NeighborEntry> _animalGrid;
|
||||||
private readonly List<NeighborEntry> _animalBuf = [];
|
private readonly List<NeighborEntry> _animalBuf = [];
|
||||||
|
private readonly float _smellSnap; // размер «обонятельной» ячейки (грубая локализация по запаху)
|
||||||
|
|
||||||
public AnimalDecisionSystem(
|
public AnimalDecisionSystem(
|
||||||
EntityStore store,
|
EntityStore store,
|
||||||
@@ -292,6 +296,7 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
_rng = new Random(seed);
|
_rng = new Random(seed);
|
||||||
_plantGrid = new SpatialGrid<ForageEntry>(cellSize * GridCells);
|
_plantGrid = new SpatialGrid<ForageEntry>(cellSize * GridCells);
|
||||||
_animalGrid = new SpatialGrid<NeighborEntry>(cellSize * GridCells);
|
_animalGrid = new SpatialGrid<NeighborEntry>(cellSize * GridCells);
|
||||||
|
_smellSnap = cellSize * GridCells;
|
||||||
_animals = store.Query<
|
_animals = store.Query<
|
||||||
AnimalNeeds,
|
AnimalNeeds,
|
||||||
AnimalBrain,
|
AnimalBrain,
|
||||||
@@ -361,13 +366,20 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
// Эффективный интеллект (мозг × сознание) гейтит доступные нужды/действия (фаза A7):
|
// Эффективный интеллект (мозг × сознание) гейтит доступные нужды/действия (фаза A7):
|
||||||
// больной/раненый зверь с упавшим сознанием теряет высшие нужды (сон/секс) → рефлексы.
|
// больной/раненый зверь с упавшим сознанием теряет высшие нужды (сон/секс) → рефлексы.
|
||||||
var intelligence = AnimalFactory.Intelligence(o[i].Traits, health);
|
var intelligence = AnimalFactory.Intelligence(o[i].Traits, health);
|
||||||
// Радиус восприятия по способностям: зрение ищет корм/партнёра/добычу, угрозу замечаем
|
// Радиусы восприятия по чувствам: зрение (корм/партнёр), плюс слух и обоняние (угроза/добыча).
|
||||||
// зрением ИЛИ слухом (повреждённые глаза/уши сужают восприятие).
|
// Острота — гены чувств × способность органа (раны/хедифы их режут). Зрение узкое, слух шире,
|
||||||
|
// запах дальше всех, но грубый (см. Detect). Радиусы детерминированы.
|
||||||
var baseRadius = VisionRadius(o[i].Species, o[i].Traits.Vision);
|
var baseRadius = VisionRadius(o[i].Species, o[i].Traits.Vision);
|
||||||
var sight = health?.Capacity(AnimalCapacities.Sight) ?? 1f;
|
var sight = health?.Capacity(AnimalCapacities.Sight) ?? 1f;
|
||||||
var hearing = health?.Capacity(AnimalCapacities.Hearing) ?? 1f;
|
var hearing = health?.Capacity(AnimalCapacities.Hearing) ?? 1f;
|
||||||
|
var smell = health?.Capacity(AnimalCapacities.Smell) ?? 1f;
|
||||||
var seeRadius = baseRadius * MathF.Max(0.2f, sight);
|
var seeRadius = baseRadius * MathF.Max(0.2f, sight);
|
||||||
var senseRadius = baseRadius * MathF.Max(0.2f, MathF.Max(sight, hearing));
|
var senseBase = _animalSet[o[i].Species].Def.VisionCells * _cellSize;
|
||||||
|
var ranges = new SenseRanges(
|
||||||
|
seeRadius,
|
||||||
|
senseBase * o[i].Traits.Hearing * MathF.Max(0.2f, hearing) * HearingRangeMult,
|
||||||
|
senseBase * o[i].Traits.Smell * MathF.Max(0.2f, smell) * SmellRangeMult
|
||||||
|
);
|
||||||
// Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию.
|
// Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию.
|
||||||
var mood = self.GetComponent<Mood>();
|
var mood = self.GetComponent<Mood>();
|
||||||
|
|
||||||
@@ -381,19 +393,24 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
HerdScan(pos, seeRadius, o[i].Species, self.Id, out herdCenter, out herdCount);
|
HerdScan(pos, seeRadius, o[i].Species, self.Id, out herdCenter, out herdCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var threatRadius =
|
var safety =
|
||||||
herdCount > 0
|
herdCount > 0
|
||||||
? senseRadius
|
? 1f - SafetyInNumbers * MathF.Min(1f, herdCount / (float)HerdFullCount)
|
||||||
* (
|
: 1f;
|
||||||
1f
|
|
||||||
- SafetyInNumbers * MathF.Min(1f, herdCount / (float)HerdFullCount)
|
|
||||||
)
|
|
||||||
: senseRadius;
|
|
||||||
|
|
||||||
// Бегство (предатор-кластер): жертва, заметившая рядом хищника, спасается — это важнее
|
// Бегство (предатор-кластер): жертва, заметившая рядом хищника, спасается — это важнее
|
||||||
// любых нужд (инстинкт выживания). Сам хищник от добычи не бежит (см. IsThreatTo).
|
// любых нужд (инстинкт выживания). Сам хищник от добычи не бежит (см. IsThreatTo).
|
||||||
if (TryFindThreat(pos, threatRadius, o[i].Species, o[i].Traits, out var threatPos))
|
if (
|
||||||
|
TryFindThreat(
|
||||||
|
pos,
|
||||||
|
ranges.Scaled(safety),
|
||||||
|
o[i].Species,
|
||||||
|
o[i].Traits,
|
||||||
|
out var threatPos
|
||||||
|
)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
self.GetComponent<Memory>().Remember(MemoryKind.Threat, threatPos, _smellSnap);
|
||||||
brain.Action = AnimalActions.Flee;
|
brain.Action = AnimalActions.Flee;
|
||||||
brain.TargetPlant = -1;
|
brain.TargetPlant = -1;
|
||||||
brain.TargetPrey = -1;
|
brain.TargetPrey = -1;
|
||||||
@@ -401,7 +418,7 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
var len = away.Length();
|
var len = away.Length();
|
||||||
brain.Target =
|
brain.Target =
|
||||||
len > 0.001f
|
len > 0.001f
|
||||||
? pos + away / len * (senseRadius + _cellSize)
|
? pos + away / len * (ranges.Max() + _cellSize)
|
||||||
: pos + new Vector2(_cellSize, 0f);
|
: pos + new Vector2(_cellSize, 0f);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -421,7 +438,7 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
when eatsMeat
|
when eatsMeat
|
||||||
&& TryFindKill(
|
&& TryFindKill(
|
||||||
pos,
|
pos,
|
||||||
seeRadius,
|
ranges,
|
||||||
o[i].Species,
|
o[i].Species,
|
||||||
o[i].Traits,
|
o[i].Traits,
|
||||||
self.Id,
|
self.Id,
|
||||||
@@ -476,11 +493,21 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
default:
|
default:
|
||||||
brain.Action = AnimalActions.Wander;
|
brain.Action = AnimalActions.Wander;
|
||||||
brain.TargetPlant = -1;
|
brain.TargetPlant = -1;
|
||||||
// Стадность: социальный зверь вдали от группы бредёт к её центру (когезия), рядом —
|
|
||||||
// мельтешит случайно (с расхождением). Несоциальный — обычное случайное блуждание.
|
|
||||||
var toCenter = herdCenter - pos;
|
var toCenter = herdCenter - pos;
|
||||||
var centerDist = toCenter.Length();
|
var centerDist = toCenter.Length();
|
||||||
if (
|
if (TryRememberedThreat(self, pos, out var dangerPos))
|
||||||
|
{
|
||||||
|
// Память: вблизи места недавнего хищника зверь уходит прочь (опасная зона).
|
||||||
|
var flee = pos - dangerPos;
|
||||||
|
var d = flee.Length();
|
||||||
|
brain.Target =
|
||||||
|
d > 0.001f
|
||||||
|
? pos + flee / d * (_cellSize * (4f + _rng.NextSingle() * 4f))
|
||||||
|
: pos + new Vector2(_cellSize, 0f);
|
||||||
|
}
|
||||||
|
// Стадность: социальный зверь вдали от группы бредёт к её центру (когезия), рядом —
|
||||||
|
// мельтешит случайно (с расхождением). Несоциальный — обычное случайное блуждание.
|
||||||
|
else if (
|
||||||
sociability >= HerdMinSociability
|
sociability >= HerdMinSociability
|
||||||
&& herdCount > 0
|
&& herdCount > 0
|
||||||
&& centerDist > HerdSeparation * _cellSize
|
&& centerDist > HerdSeparation * _cellSize
|
||||||
@@ -511,6 +538,37 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
private float VisionRadius(int species, float vision) =>
|
private float VisionRadius(int species, float vision) =>
|
||||||
MathF.Max(1f, _animalSet[species].Def.VisionCells) * MathF.Max(0.2f, vision) * _cellSize;
|
MathF.Max(1f, _animalSet[species].Def.VisionCells) * MathF.Max(0.2f, vision) * _cellSize;
|
||||||
|
|
||||||
|
// Ближайшая запомненная угроза в зоне избегания (для блуждания — уйти от опасного места).
|
||||||
|
private bool TryRememberedThreat(Entity self, Vector2 pos, out Vector2 threatPos)
|
||||||
|
{
|
||||||
|
threatPos = default;
|
||||||
|
if (!self.TryGetComponent<Memory>(out var mem) || mem.Entries is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bestSq = RememberedThreatAvoidCells * _cellSize;
|
||||||
|
bestSq *= bestSq;
|
||||||
|
var found = false;
|
||||||
|
foreach (var e in mem.Entries)
|
||||||
|
{
|
||||||
|
if (e.Kind != MemoryKind.Threat)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sq = Vector2.DistanceSquared(pos, e.Pos);
|
||||||
|
if (sq < bestSq)
|
||||||
|
{
|
||||||
|
bestSq = sq;
|
||||||
|
threatPos = e.Pos;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
// Центр и размер ближайшей группы соплеменников в радиусе (для когезии/безопасности). O(n) на особь,
|
// Центр и размер ближайшей группы соплеменников в радиусе (для когезии/безопасности). O(n) на особь,
|
||||||
// как и прочие сканы. Сам исключается по id; центр — средняя позиция соседей того же вида.
|
// как и прочие сканы. Сам исключается по id; центр — средняя позиция соседей того же вида.
|
||||||
private void HerdScan(
|
private void HerdScan(
|
||||||
@@ -547,18 +605,18 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
// ест мясо и достаточно крупный (см. IsThreatTo). O(n) на особь — как поиск корма.
|
// ест мясо и достаточно крупный (см. IsThreatTo). O(n) на особь — как поиск корма.
|
||||||
private bool TryFindThreat(
|
private bool TryFindThreat(
|
||||||
Vector2 from,
|
Vector2 from,
|
||||||
float radius,
|
in SenseRanges ranges,
|
||||||
int selfSpecies,
|
int selfSpecies,
|
||||||
in AnimalPhenotype selfTraits,
|
in AnimalPhenotype selfTraits,
|
||||||
out Vector2 threatPos
|
out Vector2 threatPos
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var bestSq = radius * radius;
|
var bestSq = float.MaxValue;
|
||||||
threatPos = default;
|
threatPos = default;
|
||||||
var threatId = -1;
|
var threatId = -1;
|
||||||
var (_, selfEatsMeat) = AnimalFactory.Diet(selfTraits, _animalSet[selfSpecies].Def);
|
var (_, selfEatsMeat) = AnimalFactory.Diet(selfTraits, _animalSet[selfSpecies].Def);
|
||||||
var selfBody = selfTraits.BodySize;
|
var selfBody = selfTraits.BodySize;
|
||||||
_animalGrid.Collect(from, radius, _animalBuf);
|
_animalGrid.Collect(from, ranges.Max(), _animalBuf);
|
||||||
foreach (var e in _animalBuf)
|
foreach (var e in _animalBuf)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
@@ -570,10 +628,16 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sq = Vector2.DistanceSquared(from, e.Pos);
|
var sq = Vector2.DistanceSquared(from, e.Pos);
|
||||||
|
// Обнаружение по чувствам: зрение (минус камуфляж) / слух (× шум) / запах (× запах, грубая зона).
|
||||||
|
if (!Detect(sq, e.Pos, ranges, e.Camouflage, e.Scent, e.Noise, out var perceived))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (sq < bestSq || (sq == bestSq && e.Id < threatId))
|
if (sq < bestSq || (sq == bestSq && e.Id < threatId))
|
||||||
{
|
{
|
||||||
bestSq = sq;
|
bestSq = sq;
|
||||||
threatPos = e.Pos;
|
threatPos = perceived;
|
||||||
threatId = e.Id;
|
threatId = e.Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -598,11 +662,62 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
return selfEatsMeat ? otherBody > selfBody * 1.1f : otherBody >= selfBody * 0.9f;
|
return selfEatsMeat ? otherBody > selfBody * 1.1f : otherBody >= selfBody * 0.9f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Радиусы восприятия особи по чувствам (уже с остротой генов и способностью органов).</summary>
|
||||||
|
private readonly record struct SenseRanges(float Sight, float Hearing, float Smell)
|
||||||
|
{
|
||||||
|
public float Max() => MathF.Max(Sight, MathF.Max(Hearing, Smell));
|
||||||
|
|
||||||
|
public SenseRanges Scaled(float f) => new(Sight * f, Hearing * f, Smell * f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обнаружена ли цель и где она «видится» наблюдателю. Зрение/слух дают точную позицию (зрение глушит
|
||||||
|
// камуфляж цели, слух усиливает её шум); запах добивает дальше всех, но даёт лишь ГРУБУЮ зону —
|
||||||
|
// позиция округляется до ячейки запаха (зверь идёт «примерно туда», пока ближе не сработает зрение).
|
||||||
|
private bool Detect(
|
||||||
|
float dist2,
|
||||||
|
Vector2 truePos,
|
||||||
|
in SenseRanges r,
|
||||||
|
float camouflage,
|
||||||
|
float scent,
|
||||||
|
float noise,
|
||||||
|
out Vector2 perceived
|
||||||
|
)
|
||||||
|
{
|
||||||
|
perceived = truePos;
|
||||||
|
var sight = r.Sight * (1f - Math.Clamp(camouflage, 0f, 1f));
|
||||||
|
if (sight > 0f && dist2 <= sight * sight)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hear = r.Hearing * Math.Clamp(noise, 0f, 1f);
|
||||||
|
if (hear > 0f && dist2 <= hear * hear)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var smell = r.Smell * Math.Clamp(scent, 0f, 1f);
|
||||||
|
if (smell > 0f && dist2 <= smell * smell)
|
||||||
|
{
|
||||||
|
perceived = SnapToSmellCell(truePos); // грубая зона запаха
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Округляет позицию до центра «обонятельной» ячейки — детерминированная грубая локализация по запаху.
|
||||||
|
private Vector2 SnapToSmellCell(Vector2 p) =>
|
||||||
|
new(
|
||||||
|
MathF.Floor(p.X / _smellSnap) * _smellSnap + _smellSnap * 0.5f,
|
||||||
|
MathF.Floor(p.Y / _smellSnap) * _smellSnap + _smellSnap * 0.5f
|
||||||
|
);
|
||||||
|
|
||||||
// Ближайшая «мясная» цель: труп (падаль, приоритет — даровая еда без риска) или живая добыча.
|
// Ближайшая «мясная» цель: труп (падаль, приоритет — даровая еда без риска) или живая добыча.
|
||||||
// Возвращает id, позицию и флаг трупа. Добыча — другой вид не крупнее охотника (см. IsPreyFor).
|
// Возвращает id, позицию и флаг трупа. Добыча — другой вид не крупнее охотника (см. IsPreyFor).
|
||||||
private bool TryFindKill(
|
private bool TryFindKill(
|
||||||
Vector2 from,
|
Vector2 from,
|
||||||
float radius,
|
in SenseRanges ranges,
|
||||||
int selfSpecies,
|
int selfSpecies,
|
||||||
in AnimalPhenotype selfTraits,
|
in AnimalPhenotype selfTraits,
|
||||||
int selfId,
|
int selfId,
|
||||||
@@ -614,9 +729,10 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
targetId = -1;
|
targetId = -1;
|
||||||
position = default;
|
position = default;
|
||||||
isCorpse = false;
|
isCorpse = false;
|
||||||
|
var reach = ranges.Max();
|
||||||
|
|
||||||
// 1) Падаль с остатком мяса.
|
// 1) Падаль с остатком мяса — находится в пределах любого чувства (запах хорошо ведёт к падали).
|
||||||
var bestCorpseSq = radius * radius;
|
var bestCorpseSq = reach * reach;
|
||||||
var corpseId = -1;
|
var corpseId = -1;
|
||||||
var corpsePos = default(Vector2);
|
var corpsePos = default(Vector2);
|
||||||
foreach (var (transforms, corpses, entities) in _corpses.Chunks)
|
foreach (var (transforms, corpses, entities) in _corpses.Chunks)
|
||||||
@@ -641,12 +757,12 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Живая добыча (из сетки животных).
|
// 2) Живая добыча (из сетки животных, по чувствам).
|
||||||
var bestPreySq = radius * radius;
|
var bestPreySq = float.MaxValue;
|
||||||
var preyId = -1;
|
var preyId = -1;
|
||||||
var preyPos = default(Vector2);
|
var preyPos = default(Vector2);
|
||||||
var selfBody = selfTraits.BodySize;
|
var selfBody = selfTraits.BodySize;
|
||||||
_animalGrid.Collect(from, radius, _animalBuf);
|
_animalGrid.Collect(from, reach, _animalBuf);
|
||||||
foreach (var e in _animalBuf)
|
foreach (var e in _animalBuf)
|
||||||
{
|
{
|
||||||
if (e.Species == selfSpecies || e.Id == selfId || !IsPreyFor(selfBody, e.BodySize))
|
if (e.Species == selfSpecies || e.Id == selfId || !IsPreyFor(selfBody, e.BodySize))
|
||||||
@@ -655,11 +771,16 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sq = Vector2.DistanceSquared(from, e.Pos);
|
var sq = Vector2.DistanceSquared(from, e.Pos);
|
||||||
|
if (!Detect(sq, e.Pos, ranges, e.Camouflage, e.Scent, e.Noise, out var perceived))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (sq < bestPreySq || (sq == bestPreySq && e.Id < preyId))
|
if (sq < bestPreySq || (sq == bestPreySq && e.Id < preyId))
|
||||||
{
|
{
|
||||||
bestPreySq = sq;
|
bestPreySq = sq;
|
||||||
preyId = e.Id;
|
preyId = e.Id;
|
||||||
preyPos = e.Pos;
|
preyPos = perceived;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,7 +874,10 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
o[i].Traits.BodySize,
|
o[i].Traits.BodySize,
|
||||||
eatsMeat,
|
eatsMeat,
|
||||||
o[i].IsMale,
|
o[i].IsMale,
|
||||||
AnimalFactory.IsAdult(sp, g[i].Stage)
|
AnimalFactory.IsAdult(sp, g[i].Stage),
|
||||||
|
o[i].Traits.Camouflage,
|
||||||
|
o[i].Traits.Scent,
|
||||||
|
o[i].Traits.Noise
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2504,3 +2628,50 @@ public sealed class AnimalSkillSystem(GameClock clock, float secondsPerDay, Skil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Забывание (память): уверенность каждой записи падает со временем, состарившиеся стираются. Свежие
|
||||||
|
/// наблюдения записывает/освежает <see cref="AnimalDecisionSystem"/>; эта система только «выветривает»
|
||||||
|
/// память, давая ей конечный срок.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AnimalMemorySystem(GameClock clock, float secondsPerDay) : QuerySystem<Memory>
|
||||||
|
{
|
||||||
|
private const float ForgetPerDay = 0.5f; // уверенность падает (память живёт ~2 дня без подтверждения)
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
var days = clock.DeltaTime / secondsPerDay;
|
||||||
|
if (days <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (memories, _) in Query.Chunks)
|
||||||
|
{
|
||||||
|
var m = memories.Span;
|
||||||
|
for (var i = 0; i < m.Length; i++)
|
||||||
|
{
|
||||||
|
var entries = m[i].Entries;
|
||||||
|
if (entries is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = entries.Count - 1; k >= 0; k--)
|
||||||
|
{
|
||||||
|
var e = entries[k];
|
||||||
|
e.Confidence -= ForgetPerDay * days;
|
||||||
|
e.AgeDays += days;
|
||||||
|
if (e.Confidence <= 0f)
|
||||||
|
{
|
||||||
|
entries.RemoveAt(k);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entries[k] = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public static class AnimalCapacities
|
|||||||
public const string BloodFiltration = "BloodFiltration";
|
public const string BloodFiltration = "BloodFiltration";
|
||||||
public const string Sight = "Sight";
|
public const string Sight = "Sight";
|
||||||
public const string Hearing = "Hearing";
|
public const string Hearing = "Hearing";
|
||||||
|
public const string Smell = "Smell";
|
||||||
|
public const string Touch = "Touch";
|
||||||
public const string Talking = "Talking";
|
public const string Talking = "Talking";
|
||||||
public const string Eating = "Eating";
|
public const string Eating = "Eating";
|
||||||
public const string Digestion = "Digestion";
|
public const string Digestion = "Digestion";
|
||||||
@@ -23,6 +25,8 @@ public static class AnimalCapacities
|
|||||||
Moving,
|
Moving,
|
||||||
Sight,
|
Sight,
|
||||||
Hearing,
|
Hearing,
|
||||||
|
Smell,
|
||||||
|
Touch,
|
||||||
Talking,
|
Talking,
|
||||||
Eating,
|
Eating,
|
||||||
Breathing,
|
Breathing,
|
||||||
@@ -99,6 +103,8 @@ public static class CapacityCalc
|
|||||||
[AnimalCapacities.Moving] = Raw(AnimalCapacities.Moving) * cons * pump,
|
[AnimalCapacities.Moving] = Raw(AnimalCapacities.Moving) * cons * pump,
|
||||||
[AnimalCapacities.Sight] = Raw(AnimalCapacities.Sight) * cons,
|
[AnimalCapacities.Sight] = Raw(AnimalCapacities.Sight) * cons,
|
||||||
[AnimalCapacities.Hearing] = Raw(AnimalCapacities.Hearing) * cons,
|
[AnimalCapacities.Hearing] = Raw(AnimalCapacities.Hearing) * cons,
|
||||||
|
[AnimalCapacities.Smell] = Raw(AnimalCapacities.Smell) * cons,
|
||||||
|
[AnimalCapacities.Touch] = Raw(AnimalCapacities.Touch) * cons,
|
||||||
[AnimalCapacities.Talking] = Raw(AnimalCapacities.Talking) * cons,
|
[AnimalCapacities.Talking] = Raw(AnimalCapacities.Talking) * cons,
|
||||||
[AnimalCapacities.Eating] = Raw(AnimalCapacities.Eating) * cons,
|
[AnimalCapacities.Eating] = Raw(AnimalCapacities.Eating) * cons,
|
||||||
[AnimalCapacities.Digestion] = Raw(AnimalCapacities.Digestion) * pump,
|
[AnimalCapacities.Digestion] = Raw(AnimalCapacities.Digestion) * pump,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public interface ISpatialEntry
|
|||||||
public readonly record struct ForageEntry(int Id, Vector2 Pos, float Toxicity, float Palatability)
|
public readonly record struct ForageEntry(int Id, Vector2 Pos, float Toxicity, float Palatability)
|
||||||
: ISpatialEntry;
|
: ISpatialEntry;
|
||||||
|
|
||||||
/// <summary>Сосед-животное для ИИ: позиция/id + предпосчитанные признаки (вид, размер, мясоед, пол, взрослость),
|
/// <summary>Сосед-животное для ИИ: позиция/id + предпосчитанные признаки (вид, размер, мясоед, пол, взрослость)
|
||||||
/// чтобы запросы угрозы/добычи/партнёра/стада не дорефетчили компоненты.</summary>
|
/// и заметность (камуфляж/запах/шум) — чтобы запросы угрозы/добычи/партнёра/стада не дорефетчили компоненты.</summary>
|
||||||
public readonly record struct NeighborEntry(
|
public readonly record struct NeighborEntry(
|
||||||
int Id,
|
int Id,
|
||||||
Vector2 Pos,
|
Vector2 Pos,
|
||||||
@@ -23,7 +23,10 @@ public readonly record struct NeighborEntry(
|
|||||||
float BodySize,
|
float BodySize,
|
||||||
bool EatsMeat,
|
bool EatsMeat,
|
||||||
bool IsMale,
|
bool IsMale,
|
||||||
bool IsAdult
|
bool IsAdult,
|
||||||
|
float Camouflage,
|
||||||
|
float Scent,
|
||||||
|
float Noise
|
||||||
) : ISpatialEntry;
|
) : ISpatialEntry;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user