Nimbin[12]?Games / Entropie / entropie/src/people/condition.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/people/condition.c 10.1 KB · 205 lines raw
#include "world/tent.h"
#include "people/condition.h"
#include "world/object.h"
#include "world/grid.h"

#define SOBER_PER_HOUR   0.4f   /* drunkenness wearing off per game hour */
#define SLEEP_SOBERING   5.0f   /* ... this many times faster while asleep */
#define DRUNK_UNSTEADY   1.1f   /* drunkenness -> unsteadiness of the body */
#define PASS_OUT         1.0f
#define WAKE_UP          0.7f
#define BURP_FROM        0.2f   /* drunk enough for burps */
#define BURP_AFTER_SIP   0.35f  /* chance per sip (plus drunkenness) */
#define BURP_PER_SECOND  0.015f /* random burps when quite drunk (times drunkenness) */
#define COMPANY_DISTANCE 3.0f   /* someone this close changes how you hold yourself */
#define SICK_FROM        0.8f   /* drunk enough to be sick */
#define SICK_AFTER_SIP   0.45f
#define SICK_PER_SECOND  0.25f  /* times how far past SICK_FROM */

/* Needs, per game hour */
#define HUNGER_PER_HOUR   0.12f
#define THIRST_PER_HOUR   0.15f
#define BLADDER_PER_HOUR  0.04f
#define AWAKE_PER_HOUR    0.065f  /* sleepiness while awake: about 16 hours a day */
#define SLEEP_PER_HOUR    0.16f   /* ... taken off while asleep: a good night's sleep */
#define TENT_SLEEP        1.15f   /* ... in a tent, quicker */
#define TENT_BED_SLEEP    1.4f    /* ... and in a sleeping bag in it, quicker still */
#define WORK_THIRST       2.0f    /* hard work makes you this much thirstier / hungrier */
/* Stamina, per real second */
#define TIRE_RUNNING      0.018f
#define TIRE_WORKING      0.040f  /* tool strokes: a few minutes of chopping and you need a break */
#define TIRE_PUSHING      0.016f
#define TIRE_CARRYING     0.024f
#define TIRE_HOLDING      0.12f   /* ... carrying something in the arms, by how heavy it is for them (see Condition_load) */
#define HOLD_EASY         0.08f   /* ... light (a bottle, a bag): nothing */
#define TIRED_CAP         0.55f  /* tired out: the most stamina this much lower ... */
#define TIRED_EFFORT      0.6f   /* ... effort this much harder ... */
#define TIRED_REST        0.5f   /* ... resting this much slower */
#define REST_STANDING     0.030f
#define REST_WALKING      0.008f
#define REST_SLEEPING     0.050f
/* Worn out over the days (see Object.worn) */
#define WORN_PER_EFFORT   0.012f  /* per stamina used up (an afternoon of hard work: about 0.7) */
#define WORN_SLEEP_HOUR   0.075f  /* taken off per game hour asleep (a night: about 0.6) */

static unsigned rng = 0x2545F491u;

static float random01(void) {
    rng = rng * 1664525u + 1013904223u;
    return (float)(rng >> 8) / 16777216.0f;
}

static void dropEverything(Object *self) {
    if (Hands_isGrabbing(self)) Hands_release(self);
    for (int hand = 0; hand < 2; hand++)
        if (self->hands.state[hand] != HAND_FREE) Hands_drop(self, hand);
}

static void drink(Object *self, float dt, float game_dt) {
    Body *b = &self->body;
    float sobering = SOBER_PER_HOUR * game_dt / 3600.0f * (b->asleep ? SLEEP_SOBERING : 1.0f);
    bool sipped = self->drunk > self->last_drunk + 1e-4f;
    self->drunk = fmaxf(0.0f, self->drunk - sobering);
    self->last_drunk = self->drunk;
    b->unsteadiness = clampf(self->drunk * DRUNK_UNSTEADY, 0.0f, 1.0f);

    /* Too much: lie down and sleep it off */
    bool settled = b->pose == POSE_UP || b->pose == POSE_DOWN; /* not halfway up or down */
    if (self->drunk >= PASS_OUT && !b->asleep && settled) {
        Body_lieDown(b);
        self->passed_out = true;
    }

    /* Way too much: being sick, right after a sip or out of the blue */
    if (self->drunk > SICK_FROM && b->vomit <= 0.0f && !Hands_isDrinking(self)) {
        float over = self->drunk - SICK_FROM;
        if ((sipped && random01() < SICK_AFTER_SIP) || random01() < SICK_PER_SECOND * over * dt)
            Body_vomit(b);
    }

    /* Burps: soon after a sip, or just now and then */
    if (self->drunk > BURP_FROM && self->burp_timer <= 0.0f) {
        if (sipped && random01() < BURP_AFTER_SIP + self->drunk * 0.4f)
            self->burp_timer = 0.8f + 1.5f * random01();
        else if (self->drunk > 0.35f && random01() < BURP_PER_SECOND * self->drunk * dt)
            self->burp_timer = 0.01f;
    }
    if (self->burp_timer > 0.0f && (self->burp_timer -= dt) <= 0.0f && !Hands_isDrinking(self))
        Body_burp(b);
}

/* How much a need above `from` hurts (0..1). */
static float excess(float value, float from) {
    return clampf((value - from) / (1.0f - from), 0.0f, 1.0f);
}

float Condition_load(const Object *self) {
    float held = 0.0f;
    for (int h = 0; h < 2; h++) {
        const Object *it = self->hands.item[h];
        if (!it || self->hands.state[h] != HAND_HOLDING || (h == 1 && it == self->hands.item[0])) continue;
        held += it->weight;
    }
    if (held <= 0.0f || self->weight <= 0.0f) return 0.0f;
    return held / (self->weight * (self->strength / 100.0f) * (0.7f + 0.6f * self->fitness));
}

/* Hunger, thirst, bladder, sleepiness over the day; stamina from work and rest. */
static void needs(Object *self, float dt, float game_dt) {
    Needs *n = &self->needs;
    Body *b = &self->body;
    const float hours = game_dt / 3600.0f;

    bool asleep = b->asleep && b->pose != POSE_UP;
    bool working = Tools_isWorking(self);
    bool carrying = self->hands.grabbed && self->hands.grabbed->lifted;
    bool pushing = self->hands.grabbed && !carrying && self->ground_speed > 0.2f;
    float effort = working ? TIRE_WORKING : carrying ? TIRE_CARRYING : pushing ? TIRE_PUSHING
                 : self->ground_speed > 4.0f ? TIRE_RUNNING : 0.0f;
    float load = Condition_load(self); /* (something heavy in the arms: a crate - tiring for the weaker) */
    if (load > HOLD_EASY) effort = fmaxf(effort, TIRE_HOLDING * (load - HOLD_EASY));
    float rest = asleep ? REST_SLEEPING : self->sitting ? REST_STANDING * 2.0f
               : self->ground_speed < 0.3f ? REST_STANDING : REST_WALKING;
    /* Tired (late in the day, drunk, sluggish): less to give - a lower ceiling,
       used up quicker, back slower */
    float tired = Object_tiredness(self);
    float cap = clampf(1.0f - 0.6f * excess(n->hunger, 0.6f) - 0.6f * excess(n->thirst, 0.6f)
                       - TIRED_CAP * tired, 0.15f, 1.0f);
    effort *= 1.0f + TIRED_EFFORT * tired;
    if (!asleep) rest *= 1.0f - TIRED_REST * tired;
    n->stamina = clampf(n->stamina + (effort > 0.0f ? -effort : rest) * dt, 0.0f, fmaxf(cap, 0.0f));
    if (effort > 0.0f) self->worn = fminf(1.0f, self->worn + WORN_PER_EFFORT * effort * dt * (1.15f - 0.3f * self->fitness));
    if (asleep) self->worn = fmaxf(0.0f, self->worn - WORN_SLEEP_HOUR * hours * (1.0f + 0.25f * self->temperament.energy));
    if (n->stamina > cap) n->stamina = cap;

    float hard = effort > 0.0f ? WORK_THIRST : 1.0f;
    float slow = asleep ? 0.5f : 1.0f;
    n->hunger     = fminf(1.0f, n->hunger + HUNGER_PER_HOUR * hours * hard * slow);
    n->thirst     = fminf(1.0f, n->thirst + THIRST_PER_HOUR * hours * hard * slow);
    n->bladder    = fminf(1.0f, n->bladder + BLADDER_PER_HOUR * hours / Object_bladder(self));
    float bed = asleep && self->in_tent ? (Tent_hasBed(self->in_tent, self) ? TENT_BED_SLEEP : TENT_SLEEP) : 1.0f; /* (a tent, a sleeping bag: better sleep) */
    n->sleepiness = clampf(n->sleepiness + (asleep ? -SLEEP_PER_HOUR * bed : AWAKE_PER_HOUR) * hours, 0.0f, 1.0f);

    /* Can't keep the eyes open any more: sleep right here */
    bool settled = b->pose == POSE_UP || b->pose == POSE_DOWN;
    if (n->sleepiness >= 1.0f && !b->asleep && settled) Body_lieDown(b);
    /* Slept enough (and sober enough): get up. Passed out drunk: up once sober enough. */
    float rested = self->passed_out ? 0.7f : 0.05f;
    if (b->asleep && n->sleepiness < rested && self->drunk < WAKE_UP && !self->rain_nap) Body_getUp(b);
    if (!b->asleep) self->passed_out = false;
    /* Can't hold it any more */
    if (n->bladder >= 1.0f && b->pose == POSE_UP && b->pee <= 0.0f) Body_pee(b, Object_peeTime(self));

    b->exertion = 1.0f - n->stamina;
}

/* Temperament, nudged by drink and company. */
static struct Grid *g_grid;
void Condition_useGrid(struct Grid *grid) { g_grid = grid; }

static void showMood(Object *self, Object *const *objects, int count) {
    Mood m = self->temperament;
    float d = self->drunk;

    /* A drink or two: bolder and looser. More: sagging. */
    m.pride  += 0.35f * fminf(d * 2.5f, 1.0f) - 0.6f * fmaxf(d - 0.5f, 0.0f);
    m.energy -= 0.8f * fmaxf(d - 0.35f, 0.0f);

    /* Worn out, hungry, thirsty or sleepy: everything droops */
    const Needs *n = &self->needs;
    m.energy -= 0.9f * fmaxf(0.5f - n->stamina, 0.0f) * 2.0f + 0.5f * excess(n->sleepiness, 0.6f)
              + 0.3f * excess(n->hunger, 0.6f) + 0.3f * excess(n->thirst, 0.6f);

    /* Somebody close: the shy shrink, the proud stand taller, everyone is a bit more alert */
    float nearest = 1e9f;
    Object *near[256];
    int found = g_grid ? Grid_nearObjects(g_grid, objects, count, self->position.x, self->position.z, COMPANY_DISTANCE, near, 256) : -1;
    if (found >= 0) { objects = near; count = found; } /* (only who's around, from the cell grid) */
    else objects = Object_kind(KIND_CHARACTERS, objects, count, &count);
    for (int i = 0; i < count; i++) {
        const Object *o = objects[i];
        if (o == self || !o->is_character) continue;
        Vec3 to = vec3_sub(o->position, self->position);
        nearest = fminf(nearest, sqrtf(to.x * to.x + to.z * to.z));
    }
    if (nearest < COMPANY_DISTANCE) {
        if (self->temperament.pride < 0.0f) m.pride -= 0.25f;
        if (self->temperament.pride > 0.0f) m.pride += 0.15f;
        m.nerves += 0.15f;
    }
    /* Dying for a smoke: restless (nicotine) - a cigarette calms them down */
    if (self->smoker > 0.0f && !Body_isSmoking(&self->body))
        m.nerves += 0.5f * smoothstepf(0.8f, 1.0f, self->craving) * self->smoker;
    self->body.mood = (Mood){
        .pride  = clampf(m.pride, -1.0f, 1.0f),
        .energy = clampf(m.energy, -1.0f, 1.0f),
        .nerves = clampf(m.nerves, 0.0f, 1.0f),
    };
}

void Condition_update(Object *self, Object *const *objects, int count, float dt, float game_dt) {
    needs(self, dt, game_dt);
    drink(self, dt, game_dt);
    if (Body_isDown(&self->body)) dropEverything(self); /* nobody holds on while falling */
    showMood(self, objects, count);
}