Животные A8 часть 1: настроение — ядро (ThoughtDef + Mood + MoodSystem)
Тип дефа 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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
26bf83db64
commit
1b12831817
@@ -24,6 +24,18 @@ public static class AnimalActions
|
||||
public const string Mate = "Mate";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id событийных мыслей настроения (фаза A8). Системы-источники события вешают мысль с этим id (через
|
||||
/// <see cref="Content.ThoughtSet"/>); её сила/длительность/метка — данные (thoughts.json). Новая мысль на
|
||||
/// новое событие = код-триггер + деф; правка чисел существующей — только JSON.
|
||||
/// </summary>
|
||||
public static class AnimalThoughts
|
||||
{
|
||||
public const string GaveBirth = "GaveBirth";
|
||||
public const string Mated = "Mated";
|
||||
public const string QuenchedThirst = "QuenchedThirst";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Нужды зверя как ДАННЫЕ (рефактор расширяемости): значения по индексам из <see cref="NeedSet"/>
|
||||
/// (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<AnimalContext> _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<Health>().State
|
||||
self.GetComponent<Health>().State
|
||||
);
|
||||
// Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию.
|
||||
var mood = self.GetComponent<Mood>();
|
||||
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<Mood>()
|
||||
.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<Mood>().Add(mated);
|
||||
b.GetComponent<Mood>().Add(mated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,6 +752,103 @@ public sealed class AnimalActionSystem : BaseSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настроение (фаза A8): для особей тира 4+ (мозг ≥ порога) считает настроение из непрерывного фона
|
||||
/// (удовлетворённость убывающих нужд минус боль из здоровья) и суммы затухающих событийных мыслей.
|
||||
/// Мысли угасают со временем. У примитивных (тир < 4) настроения нет (Active=false, мысли сбрасываются).
|
||||
/// Гейт интеллектом — тем же порогом тира 4, что и высшие нужды (сон/секс). Низкое настроение влияет на
|
||||
/// поведение (стресс не даёт спариваться — см. <see cref="AnimalDecisionSystem"/>) и видно в консоли/инспекторе.
|
||||
/// </summary>
|
||||
public sealed class AnimalMoodSystem(GameClock clock, float secondsPerDay, NeedSet needs)
|
||||
: QuerySystem<Mood, AnimalNeeds, AnimalOrganism, Health>
|
||||
{
|
||||
/// <summary>Порог размера мозга (тир 4), с которого у особи появляется настроение.</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Презентация состояния нужд: голодный/уставший зверь тускнеет (яркость падает с самой острой
|
||||
/// убывающей нуждой), сохраняя оттенок меха из генов. Читает симуляцию, пишет только
|
||||
@@ -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<Pregnant>();
|
||||
}
|
||||
|
||||
// Роды — сильная положительная мысль матери (A8).
|
||||
if (birth.Mother.HasComponent<Mood>())
|
||||
{
|
||||
birth.Mother.GetComponent<Mood>().Add(_thoughts.Get(AnimalThoughts.GaveBirth));
|
||||
}
|
||||
|
||||
for (var k = 0; k < birth.Litter && count < _maxAnimals; k++)
|
||||
{
|
||||
var child = Genome.Breed(
|
||||
|
||||
Reference in New Issue
Block a user