#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); }