From 1b128318174a1a363bbfe38b069be2a7ecf2be71 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sun, 14 Jun 2026 06:13:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=96=D0=B8=D0=B2=D0=BE=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20A8=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=201:=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=E2=80=94=20=D1=8F=D0=B4=D1=80=D0=BE=20(ThoughtDef=20+=20Mood?= =?UTF-8?q?=20+=20MoodSystem)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тип дефа ThoughtDef + thoughts.json (роды/спаривание/жажда — сила и длительность данными). Компонент Mood (значение 0..1, активность по тиру мозга, список затухающих мыслей) на всех животных; AnimalMoodSystem (тир 4+: настроение из непрерывного фона — средняя удовлетворённость убывающих нужд минус боль — плюс сумма мыслей). Реестр ThoughtSet + id-константы AnimalThoughts; триггеры: роды (PregnancySystem), спаривание и утоление жажды (ActionSystem). Низкое настроение подавляет спаривание (DecisionSystem). Команда mood [species]. Локализация thought.* ru/en. Сборка + --check-content чистые (11 типов дефов). Осталось A8.2 — вкладки инспектора. Co-Authored-By: Claude Opus 4.8 (1M context) --- Mods/Core/Defs/thoughts.json | 13 +++ Mods/Core/Languages/en/ui.json | 3 + Mods/Core/Languages/ru/ui.json | 3 + docs/животные.md | 14 ++- src/LittleSim/Content/GameContent.cs | 1 + src/LittleSim/Content/GameDefs.cs | 15 +++ src/LittleSim/Content/ThoughtSet.cs | 22 ++++ src/LittleSim/Scenes/WorldScene.cs | 83 +++++++++++++++ src/LittleSim/Sim/AnimalFactory.cs | 9 +- src/LittleSim/Sim/AnimalOrganism.cs | 54 ++++++++++ src/LittleSim/Sim/AnimalSystems.cs | 147 ++++++++++++++++++++++++++- 11 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 Mods/Core/Defs/thoughts.json create mode 100644 src/LittleSim/Content/ThoughtSet.cs diff --git a/Mods/Core/Defs/thoughts.json b/Mods/Core/Defs/thoughts.json new file mode 100644 index 0000000..02a7bf0 --- /dev/null +++ b/Mods/Core/Defs/thoughts.json @@ -0,0 +1,13 @@ +{ + "type": "Thought", + // Мысли настроения (фаза A8): событие вешает на зверя временный сдвиг настроения, затухающий за + // durationDays. Триггер события — код (реестр AnimalThoughts), сила/длительность/метка — данные. + // Настроение есть только у тира 4+ (мозг ≥ 0.40); непрерывный фон (голод/жажда/усталость/боль) + // считается отдельно, мысли — поверх него. Негативные событийные мысли (потеря детёныша) придут с + // родительской опекой (горизонт) — пока горе идёт через непрерывный фон. + "defs": [ + { "defName": "GaveBirth", "label": "thought.gaveBirth", "moodOffset": 0.25, "durationDays": 3 }, + { "defName": "Mated", "label": "thought.mated", "moodOffset": 0.15, "durationDays": 1.5 }, + { "defName": "QuenchedThirst", "label": "thought.quenchedThirst", "moodOffset": 0.1, "durationDays": 0.5 } + ] +} diff --git a/Mods/Core/Languages/en/ui.json b/Mods/Core/Languages/en/ui.json index 5c4464f..9ace6f9 100644 --- a/Mods/Core/Languages/en/ui.json +++ b/Mods/Core/Languages/en/ui.json @@ -123,6 +123,9 @@ "pawn.wolf": "wolf", "pawn.muffalo": "muffalo", "pawn.squirrel": "squirrel", + "thought.gaveBirth": "gave birth", + "thought.mated": "mated", + "thought.quenchedThirst": "drank its fill", "net.hud": "Multiplayer: {0} | beings: {1} | Esc — back to menu", "net.connecting": "connecting…", "net.connected": "connected", diff --git a/Mods/Core/Languages/ru/ui.json b/Mods/Core/Languages/ru/ui.json index f576f95..9f4f785 100644 --- a/Mods/Core/Languages/ru/ui.json +++ b/Mods/Core/Languages/ru/ui.json @@ -123,6 +123,9 @@ "pawn.wolf": "волк", "pawn.muffalo": "муффало", "pawn.squirrel": "белка", + "thought.gaveBirth": "родила потомство", + "thought.mated": "спарилась", + "thought.quenchedThirst": "напилась вволю", "net.hud": "Мультиплеер: {0} | жителей: {1} | Esc — в меню", "net.connecting": "подключение…", "net.connected": "подключено", diff --git a/docs/животные.md b/docs/животные.md index 0aa753d..be941d5 100644 --- a/docs/животные.md +++ b/docs/животные.md @@ -1,7 +1,17 @@ # Животные (зоология) -> Статус: **A1–A7 + рефактор расширяемости реализованы**, остальное (A8) по плану. Это живой -> план — правится по мере реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md). +> Статус: **A1–A7 + A8 часть 1 (ядро настроения) реализованы**, осталось A8 часть 2 (инспектор). Это +> живой план — правится по мере реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md). +> +> **A8 часть 1 (готово): настроение — ядро.** Тип дефа `ThoughtDef` + `thoughts.json` (мысли: роды/ +> спаривание/утоление жажды — сила и длительность данными); компонент `Mood` (значение 0..1, активность по +> тиру мозга, список затухающих мыслей) на всех животных; `AnimalMoodSystem` (тир 4+: настроение из +> непрерывного фона — средняя удовлетворённость убывающих нужд минус боль из здоровья — плюс сумма мыслей; +> мысли угасают). Реестр `ThoughtSet` + id-константы `AnimalThoughts`; событийные триггеры: роды +> (`AnimalPregnancySystem`), спаривание и утоление сильной жажды (`AnimalActionSystem`). **Эффект на +> поведение**: низкое настроение (стресс/истощение) подавляет спаривание в `AnimalDecisionSystem`. Команда +> `mood [species]`. Сборка + `--check-content` чистые (11 типов дефов). Осталось A8.2: вкладки инспектора +> (needs/health/mood) — сейчас животные в инспекторе не осматриваются (он только растительный). > > **A7 (готово): мозг → интеллект → гейт нужд/действий.** Эффективный интеллект = `brainSize` (ген) × > `Consciousness` (capacity из здоровья) — `AnimalFactory.Intelligence`. `AnimalContext` гейтит выбор ИИ: diff --git a/src/LittleSim/Content/GameContent.cs b/src/LittleSim/Content/GameContent.cs index ee594b3..90803d7 100644 --- a/src/LittleSim/Content/GameContent.cs +++ b/src/LittleSim/Content/GameContent.cs @@ -66,6 +66,7 @@ public sealed class GameContent defs.RegisterType("Animal"); defs.RegisterType("Hediff"); defs.RegisterType("Need"); + defs.RegisterType("Thought"); defs.RegisterType("Body"); defs.RegisterType("WorldPreset"); // Валидация имён при загрузке (фаза G5): гены и продукты обязаны иметь префикс типа. diff --git a/src/LittleSim/Content/GameDefs.cs b/src/LittleSim/Content/GameDefs.cs index 951a922..772f554 100644 --- a/src/LittleSim/Content/GameDefs.cs +++ b/src/LittleSim/Content/GameDefs.cs @@ -320,6 +320,21 @@ public sealed class NeedDef : Def public float MinBrain { get; init; } } +/// +/// Мысль настроения (Defs/thoughts.json) как ДАННЫЕ (фаза A8, модель thoughts RimWorld): событие +/// (роды/спаривание/утоление жажды/…) вешает на особь временный сдвиг настроения, затухающий за +/// . Триггеры событий — код (реестр AnimalThoughts), а величины/метка — +/// данные: модер правит силу и длительность мысли одной записью JSON, без пересборки. +/// +public sealed class ThoughtDef : Def +{ + /// Сдвиг настроения [-1,1] (положительный — радость, отрицательный — горе). + public float MoodOffset { get; init; } + + /// Сколько игровых дней мысль живёт, прежде чем угаснуть. + public float DurationDays { get; init; } = 1f; +} + /// /// Часть тела/орган (вложенный объект ) как ДАННЫЕ: иерархия (родитель), /// вес попадания, максимум HP (масштабируется размером тела), флаг жизненной важности и вклады в diff --git a/src/LittleSim/Content/ThoughtSet.cs b/src/LittleSim/Content/ThoughtSet.cs new file mode 100644 index 0000000..0021b3d --- /dev/null +++ b/src/LittleSim/Content/ThoughtSet.cs @@ -0,0 +1,22 @@ +namespace LittleSim.Content; + +/// +/// Реестр мыслей настроения () с доступом по id (фаза A8): системы-источники +/// событий (роды/спаривание/жажда) берут нужную мысль по строковому id (см. AnimalThoughts) и +/// вешают её на особь. Метка/сила/длительность мысли — данные; модер правит их в thoughts.json. Зеркало +/// по духу. Удалил модер мысль из дефов — вернёт null (no-op). +/// +public sealed class ThoughtSet +{ + private readonly Dictionary _byId; + + /// Строит реестр из всех мода. + public ThoughtSet(GameContent content) => + _byId = content.Defs.All().ToDictionary(t => t.DefName, StringComparer.Ordinal); + + /// Число мыслей. + public int Count => _byId.Count; + + /// Деф мысли по id или null, если такой нет (мод убрал). + public ThoughtDef? Get(string id) => _byId.GetValueOrDefault(id); +} diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index 19939f7..e172ce0 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -214,6 +214,7 @@ public sealed class WorldScene : Scene // Животные (фаза A2): нужды падают, ИИ выбирает действие, исполнение ведёт к цели и утоляет // нужду (еда выедает растения — контур ёмкости среды). Внешний вид тускнеет с острой нуждой. var shore = ComputeShore(); + var thoughts = new ThoughtSet(content); UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs)); UpdateSystems.Add( new AnimalRutSystem(_animals, climate, content.Defs.Get("Rut")) @@ -229,6 +230,8 @@ public sealed class WorldScene : Scene _config.Seed + 0x3EAD ) ); + // Настроение (A8) до выбора решения: стресс (низкое настроение) подавляет спаривание. + UpdateSystems.Add(new AnimalMoodSystem(Context.Clock, SecondsPerDay, _needs)); UpdateSystems.Add( new AnimalDecisionSystem( Store, @@ -246,6 +249,7 @@ public sealed class WorldScene : Scene _plants, _animals, _needs, + thoughts, Context.Clock, SecondsPerDay, _bounds @@ -260,6 +264,7 @@ public sealed class WorldScene : Scene Store, _animals, _needs, + thoughts, Context.Clock, SecondsPerDay, CellSize, @@ -738,6 +743,11 @@ public sealed class WorldScene : Scene "intel [consciousness] — effective intelligence (brain x consciousness) and which needs it gates", (c, args) => RunIntel(c, content, args) ); + console.Register( + "mood", + "mood [species] — live population mood (avg/min/max) and content/stressed counts", + (c, args) => RunMood(c, args) + ); console.Register( "menu", "menu — return to the main menu", @@ -1071,6 +1081,79 @@ public sealed class WorldScene : Scene console.WriteLine($" gated: {(gated.Count > 0 ? string.Join(", ", gated) : "—")}"); } + // Наблюдаемость настроения (фаза A8): среднее/мин/макс настроения живой популяции по виду и счётчики + // довольных (>0.7) / угнетённых (<0.35). Считает только особей с активным настроением (тир 4+). + private void RunMood(DevConsole console, string[] args) + { + if (_animals.Count == 0) + { + console.WriteLine("no animal species"); + return; + } + + var count = new int[_animals.Count]; + var sum = new double[_animals.Count]; + var min = new float[_animals.Count]; + var max = new float[_animals.Count]; + var content_ = new int[_animals.Count]; + var stressed = new int[_animals.Count]; + Array.Fill(min, 1f); + + Store + .Query() + .ForEachEntity( + (ref AnimalOrganism o, ref Mood m, Entity _) => + { + if (!m.Active) + { + return; // тир ниже зверя — настроения нет + } + + var s = o.Species; + count[s]++; + sum[s] += m.Value; + min[s] = MathF.Min(min[s], m.Value); + max[s] = MathF.Max(max[s], m.Value); + if (m.Value > 0.7f) + { + content_[s]++; + } + else if (m.Value < 0.35f) + { + stressed[s]++; + } + } + ); + + var filter = args.Length > 0 ? args[0] : null; + var any = false; + for (var s = 0; s < _animals.Count; s++) + { + var name = _animals[s].Def.DefName; + if ( + count[s] == 0 + || ( + filter is not null + && !string.Equals(name, filter, StringComparison.OrdinalIgnoreCase) + ) + ) + { + continue; + } + + any = true; + console.WriteLine( + $"{name}: n={count[s]}, mood {sum[s] / count[s]:0.##} (min {min[s]:0.##}, max {max[s]:0.##}), " + + $"content {content_[s]}, stressed {stressed[s]}" + ); + } + + if (!any) + { + console.WriteLine(filter is null ? "no animals with mood" : $"no '{filter}' with mood"); + } + } + private static string ProductLabel(GameContent content, string productDefName) => content.Defs.TryGet(productDefName, out var product) ? content.Languages.Get(product.Label) diff --git a/src/LittleSim/Sim/AnimalFactory.cs b/src/LittleSim/Sim/AnimalFactory.cs index 4c97e0d..47d581a 100644 --- a/src/LittleSim/Sim/AnimalFactory.cs +++ b/src/LittleSim/Sim/AnimalFactory.cs @@ -64,7 +64,14 @@ public static class AnimalFactory TargetPlant = -1, TargetMate = -1, }, - new Health { State = HealthState.Create(sp.Body, traits.BodySize) } + new Health { State = HealthState.Create(sp.Body, traits.BodySize) }, + // Настроение (фаза A8): нейтральное на старте, активность по тиру мозга считает MoodSystem. + new Mood + { + Value = 0.5f, + Active = false, + Thoughts = [], + } ); } diff --git a/src/LittleSim/Sim/AnimalOrganism.cs b/src/LittleSim/Sim/AnimalOrganism.cs index 53523d2..8f61071 100644 --- a/src/LittleSim/Sim/AnimalOrganism.cs +++ b/src/LittleSim/Sim/AnimalOrganism.cs @@ -326,6 +326,60 @@ public struct Corpse : IComponent public float Meat; } +/// Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания. +public struct ThoughtInstance +{ + /// Деф мысли (сдвиг настроения и длительность). + public Content.ThoughtDef Def; + + /// Сколько игровых дней мысль ещё действует. + public float RemainingDays; +} + +/// +/// Настроение особи (фаза A8): значение [0,1] и активные событийные мысли. Считается +/// из непрерывного фона (нужды/боль) и суммы мыслей. Есть только у +/// тира 4+ ( по размеру мозга) — у примитивных настроения нет. Низкое настроение +/// влияет на поведение (стресс не даёт спариваться) и видно в инспекторе. Список мыслей — managed-ссылка +/// (как AnimalNeeds.Values): события добавляют мысли через . +/// +public struct Mood : IComponent +{ + /// Текущее настроение [0,1]; у неактивного (тир < 4) держится нейтральным. + public float Value; + + /// Есть ли у особи настроение (гейт тиром мозга) — иначе фон/мысли не считаются. + public bool Active; + + /// Активные событийные мысли (затухают со временем). + public List Thoughts; + + /// + /// Вешает событийную мысль (или обновляет её длительность, если уже висит — без накопления + /// дубликатов). Вызывается из систем-источников событий (роды, спаривание, утоление жажды). + /// + public readonly void Add(Content.ThoughtDef? def) + { + if (Thoughts is null || def is null) + { + return; + } + + for (var i = 0; i < Thoughts.Count; i++) + { + if (Thoughts[i].Def == def) + { + var refreshed = Thoughts[i]; + refreshed.RemainingDays = def.DurationDays; + Thoughts[i] = refreshed; + return; + } + } + + Thoughts.Add(new ThoughtInstance { Def = def, RemainingDays = def.DurationDays }); + } +} + /// /// Беременность самки (фаза A4): managed-ссылка на геном отца («слепок» на момент зачатия), остаток /// срока вынашивания и размер помёта. По истечении срока diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index 9bdc71d..1056f91 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -24,6 +24,18 @@ public static class AnimalActions public const string Mate = "Mate"; } +/// +/// Id событийных мыслей настроения (фаза A8). Системы-источники события вешают мысль с этим id (через +/// ); её сила/длительность/метка — данные (thoughts.json). Новая мысль на +/// новое событие = код-триггер + деф; правка чисел существующей — только JSON. +/// +public static class AnimalThoughts +{ + public const string GaveBirth = "GaveBirth"; + public const string Mated = "Mated"; + public const string QuenchedThirst = "QuenchedThirst"; +} + /// /// Нужды зверя как ДАННЫЕ (рефактор расширяемости): значения по индексам из /// (managed-массив — добавление нужды не меняет структуру) + счётчик истощения. Падают/растут в @@ -197,6 +209,7 @@ public sealed class AnimalRutSystem(AnimalSet animals, Climate climate, HediffDe public sealed class AnimalDecisionSystem : BaseSystem { private const float Interval = 0.6f; + private const float MateMoodFloor = 0.35f; // ниже — стресс/истощение, зверь не спаривается (A8) private readonly UtilityAi _brain; private readonly GameClock _clock; @@ -294,12 +307,15 @@ public sealed class AnimalDecisionSystem : BaseSystem 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 self = entities.EntityAt(i); // Эффективный интеллект (мозг × сознание) гейтит доступные нужды/действия (фаза A7): // больной/раненый зверь с упавшим сознанием теряет высшие нужды (сон/секс) → рефлексы. var intelligence = AnimalFactory.Intelligence( o[i].Traits, - entities.EntityAt(i).GetComponent().State + self.GetComponent().State ); + // Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию. + var mood = self.GetComponent(); var name = _brain.Select(new AnimalContext(n[i].Values, _needs, intelligence))?.Name ?? AnimalActions.Wander; @@ -324,6 +340,7 @@ public sealed class AnimalDecisionSystem : BaseSystem break; case AnimalActions.Mate when adult + && (!mood.Active || mood.Value >= MateMoodFloor) && TryFindMate( pos, o[i].IsMale, @@ -478,11 +495,13 @@ public sealed class AnimalDecisionSystem : BaseSystem public sealed class AnimalActionSystem : BaseSystem { private const float GrazeKillAge = 1f; // трава с возрастом ниже этого после выедания исчезает + private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась» private readonly EntityStore _store; private readonly PlantSet _plants; private readonly AnimalSet _animalSet; private readonly NeedSet _needs; + private readonly ThoughtSet _thoughts; private readonly GameClock _clock; private readonly float _secondsPerDay; private readonly RectF _bounds; @@ -501,6 +520,7 @@ public sealed class AnimalActionSystem : BaseSystem PlantSet plants, AnimalSet animals, NeedSet needs, + ThoughtSet thoughts, GameClock clock, float secondsPerDay, RectF bounds @@ -510,6 +530,7 @@ public sealed class AnimalActionSystem : BaseSystem _plants = plants; _animalSet = animals; _needs = needs; + _thoughts = thoughts; _clock = clock; _secondsPerDay = secondsPerDay; _bounds = bounds; @@ -565,7 +586,21 @@ public sealed class AnimalActionSystem : BaseSystem case AnimalActions.Drink: if (MoveTo(ref pos, brain.Target, speed * seconds)) { + // Утоление сильной жажды — событийная мысль настроения (A8); уровень берём + // ДО восполнения, чтобы радость была от облегчения, а не от каждого глотка. + var thirst = _needs.NeedForAction(AnimalActions.Drink); + var parched = + thirst >= 0 + && thirst < values.Length + && values[thirst] < ThirstReliefBelow; Refill(values, AnimalActions.Drink, days); + if (parched) + { + entities + .EntityAt(i) + .GetComponent() + .Add(_thoughts.Get(AnimalThoughts.QuenchedThirst)); + } } break; @@ -695,6 +730,10 @@ public sealed class AnimalActionSystem : BaseSystem ResetMating(a, mateNeed); ResetMating(b, mateNeed); + // Спаривание — положительная событийная мысль обоим партнёрам (A8). + var mated = _thoughts.Get(AnimalThoughts.Mated); + a.GetComponent().Add(mated); + b.GetComponent().Add(mated); } } @@ -713,6 +752,103 @@ public sealed class AnimalActionSystem : BaseSystem } } +/// +/// Настроение (фаза A8): для особей тира 4+ (мозг ≥ порога) считает настроение из непрерывного фона +/// (удовлетворённость убывающих нужд минус боль из здоровья) и суммы затухающих событийных мыслей. +/// Мысли угасают со временем. У примитивных (тир < 4) настроения нет (Active=false, мысли сбрасываются). +/// Гейт интеллектом — тем же порогом тира 4, что и высшие нужды (сон/секс). Низкое настроение влияет на +/// поведение (стресс не даёт спариваться — см. ) и видно в консоли/инспекторе. +/// +public sealed class AnimalMoodSystem(GameClock clock, float secondsPerDay, NeedSet needs) + : QuerySystem +{ + /// Порог размера мозга (тир 4), с которого у особи появляется настроение. + public const float MoodMinBrain = 0.40f; + private const float NeutralBase = 0.35f; // настроение при полном истощении нужд (без боли/мыслей) + private const float NeedsWeight = 0.55f; // вклад средней удовлетворённости убывающих нужд + private const float PainWeight = 0.4f; // боль из здоровья тянет настроение вниз + + protected override void OnUpdate() + { + var days = clock.DeltaTime / secondsPerDay; + if (days <= 0f) + { + return; + } + + foreach (var (moods, needsChunk, organisms, healths, _) in Query.Chunks) + { + var m = moods.Span; + var n = needsChunk.Span; + var o = organisms.Span; + var h = healths.Span; + for (var i = 0; i < m.Length; i++) + { + ref var mood = ref m[i]; + if (o[i].Traits.BrainSize < MoodMinBrain) + { + mood.Active = false; // тир ниже зверя — настроения нет + mood.Value = 0.5f; + mood.Thoughts?.Clear(); + continue; + } + + mood.Active = true; + // 1) Затухание событийных мыслей + сумма их сдвигов. + var thoughtSum = 0f; + var list = mood.Thoughts; + if (list is not null) + { + for (var k = list.Count - 1; k >= 0; k--) + { + var t = list[k]; + t.RemainingDays -= days; + if (t.RemainingDays <= 0f) + { + list.RemoveAt(k); + continue; + } + + list[k] = t; + thoughtSum += t.Def.MoodOffset; + } + } + + // 2) Непрерывный фон: средняя удовлетворённость убывающих нужд минус боль из здоровья. + var wellbeing = AvgDeplete(n[i].Values); + var pain = h[i].State?.Pain ?? 0f; + mood.Value = Math.Clamp( + NeutralBase + NeedsWeight * wellbeing - PainWeight * pain + thoughtSum, + 0f, + 1f + ); + } + } + } + + // Средняя удовлетворённость убывающих нужд (голод/жажда/усталость) [0,1]; нет нужд → 1 (нейтрально). + private float AvgDeplete(float[] values) + { + if (values is null) + { + return 1f; + } + + var sum = 0f; + var count = 0; + for (var k = 0; k < needs.Count && k < values.Length; k++) + { + if (needs.IsDeplete(k)) + { + sum += values[k]; + count++; + } + } + + return count > 0 ? sum / count : 1f; + } +} + /// /// Презентация состояния нужд: голодный/уставший зверь тускнеет (яркость падает с самой острой /// убывающей нуждой), сохраняя оттенок меха из генов. Читает симуляцию, пишет только @@ -957,6 +1093,7 @@ public sealed class AnimalPregnancySystem : BaseSystem private readonly EntityStore _store; private readonly AnimalSet _animals; private readonly NeedSet _needs; + private readonly ThoughtSet _thoughts; private readonly GameClock _clock; private readonly float _secondsPerDay; private readonly int _cellSize; @@ -970,6 +1107,7 @@ public sealed class AnimalPregnancySystem : BaseSystem EntityStore store, AnimalSet animals, NeedSet needs, + ThoughtSet thoughts, GameClock clock, float secondsPerDay, int cellSize, @@ -980,6 +1118,7 @@ public sealed class AnimalPregnancySystem : BaseSystem _store = store; _animals = animals; _needs = needs; + _thoughts = thoughts; _clock = clock; _secondsPerDay = secondsPerDay; _cellSize = cellSize; @@ -1035,6 +1174,12 @@ public sealed class AnimalPregnancySystem : BaseSystem birth.Mother.RemoveComponent(); } + // Роды — сильная положительная мысль матери (A8). + if (birth.Mother.HasComponent()) + { + birth.Mother.GetComponent().Add(_thoughts.Get(AnimalThoughts.GaveBirth)); + } + for (var k = 0; k < birth.Litter && count < _maxAnimals; k++) { var child = Genome.Breed(