ObzLib game AI
obz::emotional_state
A small mood model using pleasantness and energy coordinates with named emotional states.
A Mood Layer For Behaviour
The interesting use case is not just naming moods. It is giving a behaviour system another layer of memory. An NPC can still have ordinary states like patrol, trade, flee, or attack, while its emotional state quietly changes how likely those transitions become.
If the player helps the NPC, pleasantness might rise and energy might settle. If the player betrays them, pleasantness can fall and energy can spike.
struct npc_memory {
obz::emotional_state emotion{1.0, -1.0};
};
void remember_player_help(npc_memory& npc) {
npc.emotion.adjust(1.5, -0.5);
}
void remember_player_attack(npc_memory& npc) {
npc.emotion.adjust(-2.0, 2.0);
}
State Transitions With A Mood Bias
A layered state machine can ask for the current quadrant before choosing the next behaviour. The world state still matters, but the same event can feel different depending on the character's emotional position.
behaviour choose_reaction(const npc_memory& npc) {
switch (npc.emotion.current_quadrant()) {
case obz::mood_quadrant::high_energy_low_pleasantness:
return behaviour::confront;
case obz::mood_quadrant::low_energy_low_pleasantness:
return behaviour::withdraw;
case obz::mood_quadrant::high_energy_high_pleasantness:
return behaviour::approach;
case obz::mood_quadrant::low_energy_high_pleasantness:
return behaviour::trust;
}
}
That is where emergent behaviour starts to become possible. The NPC does not need a bespoke branch for every past interaction. The emotional layer carries some of that history forward.
Remember The Last Emotional Step
The wrapper now keeps the previous finite mood and quadrant as part of the state. That is useful when behaviour depends not only on where the NPC is emotionally, but on what just changed.
npc.emotion.adjust(-2.0, 2.0);
const auto from = npc.emotion.previous_mood();
const auto to = npc.emotion.current_mood();
const auto quadrant_from = npc.emotion.previous_quadrant();
const auto quadrant_to = npc.emotion.current_quadrant();
record_emotional_memory(from, to, quadrant_from, quadrant_to);
That makes the transition inspectable after the update has happened. A save file, debug panel, or behaviour layer can ask, "what mood did this NPC just move from, and where did they land?"
Emotion Can Trigger Behaviour Too
The relationship can also run the other way. A mood or quadrant change can request a behaviour transition when the emotional state crosses a meaningful boundary.
npc.emotion.on_quadrant_changed([&](obz::quadrant_change change) {
if (change.current == obz::mood_quadrant::high_energy_low_pleasantness) {
request_transition(behaviour::hostile);
}
});
The callback is for immediate reactions. The previous/current accessors are for ordinary state inspection. Together, they let the state machine feel less like a fixed script: an NPC that remembers mistreatment between runs can become quicker to distrust the player, while an NPC that has been treated well can become easier to calm down later.
Continuous Feeling, Finite Vocabulary
The wrapper stores continuous double coordinates, clamped from -5.0 to
5.0. It also caches the current and previous snapped finite axes internally, so asking
for a mood or quadrant is asking for a stable interpretation of the continuous position.
obz::emotional_state state(3.2, 1.8);
const auto mood = state.current_mood();
const auto name = obz::name_of(mood); // "happy"
That gives game code two useful views of the same idea: smooth movement for simulation, and stable current and previous names or quadrants for decisions, debugging, saving, and designer-facing tools.