Entropie git · main
Entropie festival game; first steps
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.gzentropie/src/people/condition.c 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);
}