diff --git a/Mods/Core/Defs/Animals/Animals.json b/Mods/Core/Defs/Animals/Animals.json index ed10555..4a235e6 100644 --- a/Mods/Core/Defs/Animals/Animals.json +++ b/Mods/Core/Defs/Animals/Animals.json @@ -31,7 +31,8 @@ "GeneGestationDays": 30, "GeneLitterSize": 1, "GeneHerbivory": 1.0, - "GeneToxinTolerance": 0.1 + "GeneToxinTolerance": 0.1, + "GeneSociability": 0.8 } }, @@ -64,7 +65,8 @@ "GeneBreedingSeason": 3, "GeneGestationDays": 35, "GeneLitterSize": 4, - "GeneCarnivory": 1.0 + "GeneCarnivory": 1.0, + "GeneSociability": 0.6 } }, @@ -99,7 +101,8 @@ "GeneHerbivory": 0.4, "GeneCarnivory": 0.2, "GeneOmnivory": 0.85, - "GeneToxinTolerance": 0.25 + "GeneToxinTolerance": 0.25, + "GeneSociability": 0.3 } }, @@ -129,7 +132,8 @@ "GeneLitterSize": 6, "GeneHerbivory": 0.8, "GeneToxinTolerance": 0.15, - "GeneEggLaying": 1.0 + "GeneEggLaying": 1.0, + "GeneSociability": 0.55 } } ] diff --git a/Mods/Core/Defs/Genes/Animal.json b/Mods/Core/Defs/Genes/Animal.json index cc54db5..31dc673 100644 --- a/Mods/Core/Defs/Genes/Animal.json +++ b/Mods/Core/Defs/Genes/Animal.json @@ -59,6 +59,12 @@ { "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex", "variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] }, + // Социальность [0..1]: стадные/стайные виды держатся вместе (когезия при блуждании) и спокойнее в + // группе («безопасность в числе» снижает дальность реакции на хищника). См. AnimalDecisionSystem. + { "defName": "GeneSociability", "parent": "BaseNumericGene", "label": "gene.sociability", + "default": 0.3, "min": 0.0, "max": 1.0, "spread": 0.05, "tags": ["animal", "behaviour"], + "effects": { "sociability": "value" } }, + // Тип рождения (цикл зачатия): живородящие (eggLaying < 0.5) вынашивают и рожают живых детёнышей; // яйцекладущие (≥ 0.5) после вынашивания ОТКЛАДЫВАЮТ яйца-сущности, которые инкубируются и // вылупляются (см. AnimalPregnancySystem/EggSystem). Признак фиксирован по виду (spread/mutation 0). diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index 92954ad..15310e8 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -1208,6 +1208,9 @@ public sealed class WorldScene : Scene $" reproduction: {(traits.Oviparous ? "oviparous (lays eggs)" : "viviparous (live birth)")}, " + $"gestation {traits.GestationDays:0} d, litter {traits.LitterSize:0.#}" ); + console.WriteLine( + $" behaviour: insulation {traits.Insulation:0.##}, sociability {traits.Sociability:0.##}" + ); } // Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory, diff --git a/src/LittleSim/Sim/AnimalOrganism.cs b/src/LittleSim/Sim/AnimalOrganism.cs index 8f93ae5..4bca504 100644 --- a/src/LittleSim/Sim/AnimalOrganism.cs +++ b/src/LittleSim/Sim/AnimalOrganism.cs @@ -51,6 +51,9 @@ public struct AnimalPhenotype /// Устойчивость к растительным ядам [0..1] — снижает дозу отравления токсичным кормом. public float ToxinTolerance; + /// Социальность [0..1] — тяга держаться стаи/стада (когезия + спокойствие в группе). + public float Sociability; + /// Продолжительность жизни (игровых дней). public float Lifespan; @@ -88,6 +91,7 @@ public struct AnimalPhenotype Carnivory = T("carnivory"), Omnivory = T("omnivory"), ToxinTolerance = T("toxinTolerance"), + Sociability = T("sociability"), Lifespan = T("lifespan"), BreedingSeason = (int)MathF.Round(Math.Clamp(T("breedingSeason"), 0f, 3f)), GestationDays = T("gestationDays"), diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index c16c642..c6dcbb8 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -232,6 +232,10 @@ public sealed class AnimalDecisionSystem : BaseSystem { private const float Interval = 0.6f; private const float MateMoodFloor = 0.35f; // ниже — стресс/истощение, зверь не спаривается (A8) + private const float HerdMinSociability = 0.3f; // ниже — вид не стадный (когезия/безопасность выкл.) + private const float SafetyInNumbers = 0.5f; // макс. снижение радиуса реакции на угрозу в группе + private const int HerdFullCount = 6; // размер группы, при котором эффект «безопасность в числе» полон + private const float HerdSeparation = 1.5f; // ближе этого (в клетках) к центру — не сходимся плотнее private readonly UtilityAi _brain; private readonly GameClock _clock; @@ -345,9 +349,28 @@ public sealed class AnimalDecisionSystem : BaseSystem // Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию. var mood = self.GetComponent(); + // Стадность (boids): социальные виды считают центр/размер ближайшей группы соплеменников — + // для когезии при блуждании и «безопасности в числе» (спокойнее на угрозу в большой группе). + var sociability = o[i].Traits.Sociability; + var herdCount = 0; + var herdCenter = pos; + if (sociability >= HerdMinSociability) + { + HerdScan(pos, seeRadius, o[i].Species, self.Id, out herdCenter, out herdCount); + } + + var threatRadius = + herdCount > 0 + ? senseRadius + * ( + 1f + - SafetyInNumbers * MathF.Min(1f, herdCount / (float)HerdFullCount) + ) + : senseRadius; + // Бегство (предатор-кластер): жертва, заметившая рядом хищника, спасается — это важнее // любых нужд (инстинкт выживания). Сам хищник от добычи не бежит (см. IsThreatTo). - if (TryFindThreat(pos, senseRadius, o[i].Species, o[i].Traits, out var threatPos)) + if (TryFindThreat(pos, threatRadius, o[i].Species, o[i].Traits, out var threatPos)) { brain.Action = AnimalActions.Flee; brain.TargetPlant = -1; @@ -431,11 +454,31 @@ public sealed class AnimalDecisionSystem : BaseSystem default: brain.Action = AnimalActions.Wander; brain.TargetPlant = -1; - var angle = _rng.NextSingle() * MathF.Tau; - brain.Target = - pos - + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) - * (_cellSize * (4f + _rng.NextSingle() * 6f)); + // Стадность: социальный зверь вдали от группы бредёт к её центру (когезия), рядом — + // мельтешит случайно (с расхождением). Несоциальный — обычное случайное блуждание. + var toCenter = herdCenter - pos; + var centerDist = toCenter.Length(); + if ( + sociability >= HerdMinSociability + && herdCount > 0 + && centerDist > HerdSeparation * _cellSize + ) + { + brain.Target = + pos + + toCenter + / centerDist + * (_cellSize * (3f + _rng.NextSingle() * 3f)); + } + else + { + var angle = _rng.NextSingle() * MathF.Tau; + brain.Target = + pos + + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + * (_cellSize * (4f + _rng.NextSingle() * 6f)); + } + break; } } @@ -446,6 +489,42 @@ 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; + // Центр и размер ближайшей группы соплеменников в радиусе (для когезии/безопасности). O(n) на особь, + // как и прочие сканы. Сам исключается по id; центр — средняя позиция соседей того же вида. + private void HerdScan( + Vector2 from, + float radius, + int species, + int selfId, + out Vector2 center, + out int count + ) + { + var sum = Vector2.Zero; + count = 0; + var radiusSq = radius * radius; + foreach (var (_, _, organisms, _, transforms, entities) in _animals.Chunks) + { + var oo = organisms.Span; + var tt = transforms.Span; + for (var i = 0; i < tt.Length; i++) + { + if (oo[i].Species != species || entities.EntityAt(i).Id == selfId) + { + continue; + } + + if (Vector2.DistanceSquared(from, tt[i].Position) <= radiusSq) + { + sum += tt[i].Position; + count++; + } + } + } + + center = count > 0 ? sum / count : from; + } + // Ближайший хищник-угроза в радиусе восприятия (для бегства жертвы). Угроза = другой вид, который // ест мясо и достаточно крупный (см. IsThreatTo). O(n) на особь — как поиск корма. private bool TryFindThreat(