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 2.12.0: wet clothes, thunderstorms, bedtime at work
- Clothes get wet in the rain (drawn darker, Tab: Wet), make you cold, and dry by a fire quickest; wet NPCs go and sit by a burning fire - Thunderstorms in hard rain: lightning flashes, thunder a few seconds later - Working NPCs put the work down and go to bed once really sleepy - Rain drops right in front of the camera are no longer drawn
commit_message.txt | 12 ++++++------ entropie/Makefile | 2 +- entropie/README.md | 5 ++++- entropie/STATE.md | 13 ++++++++++--- entropie/assets/shaders/figure.frag | 3 +++ entropie/src/game/game.c | 19 ++++++++++++++++++ entropie/src/game/game.h | 1 + entropie/src/people/ai.c | 39 ++++++++++++++++++++++++++++++++++--- entropie/src/people/ai.h | 1 + entropie/src/render/figure.h | 1 + entropie/src/render/renderer.c | 2 ++ entropie/src/render/renderer.h | 2 +- entropie/src/world/weather.c | 18 ++++++++++++++++- entropie/src/world/world.c | 21 +++++++++++++++++++- entropie/src/world/world.h | 4 ++++ 15 files changed, 126 insertions(+), 17 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index e487023..07a6e3e 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,7 +1,7 @@ -entropie 2.11.0: taking tents down +entropie 2.12.0: wet clothes, thunderstorms, bedtime at work -- Hold left click right at a standing tent: after a moment it comes down - (pegs out, cover off, poles down, folded into its bag) -- Sleeping bags and mats that were inside come out at the door -- The owner and people nearby help; a tent packed on purpose stays packed -- New Gear_create (car.c) for camping things +- Clothes get wet in the rain (drawn darker, Tab: Wet), make you cold, + and dry by a fire quickest; wet NPCs go and sit by a burning fire +- Thunderstorms in hard rain: lightning flashes, thunder a few seconds later +- Working NPCs put the work down and go to bed once really sleepy +- Rain drops right in front of the camera are no longer drawn diff --git a/entropie/Makefile b/entropie/Makefile index 4185ade..2faa756 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.11.0 +VERSION := 2.12.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 9764bd5..f1896f7 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -52,7 +52,10 @@ Painted in the rotoscope style of *Waking Life* (2001): - People get out of it: Kurti and his sons into the house, the others into their tent (at night, or when it's nearer) or your garages' kitchen; out again when it stops -- Out in the rain you get cold sooner (pullover) +- Out in the rain your clothes get wet (darker; Tab: Wet) and you get cold. + They dry by a fire quickest, in the day slower, at night slowest. Wet + through, the others go and sit by a burning fire +- Hard rain brings thunderstorms: lightning, then the thunder ## Tents diff --git a/entropie/STATE.md b/entropie/STATE.md index 4007350..b4cbc09 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -224,6 +224,13 @@ Round 2.11: - Taking a tent down (`tent.c` Tent_takeDown, `tent_down`): anybody in it comes out (woken); then Tent_work runs the stages backwards (workDown: each DOWN_QUICKER 0.6 of the time up, the pegs out one by one from the last, the cover off, the poles down, FOLD_TIME 7 s folding) and returns TENT_DOWN + the stage (TENT_DOWN_PACKED folded). Tent_update packs it (`pack`): its bag (`Gear_create`, from car.c) where it lay, `keep` (its owner doesn't put it up again on their own: `ai.c` pitchTents), the sleeping bags and mats that were in it at the door; the tent used up (no owner: Tent_of skips it). Drawn the other way round (`shownWork`). - You: hold left click right at a standing tent (0.6 m, so a click near it still takes things): TAKE_DOWN_HOLD 1 s, then it comes down while you hold (`game_actions.c` workTent). The owner and anybody near help (helpers drop a packed job). Hints: "hold left click: take it down". +Round 2.12: +- Wet clothes (`FigureLook.wet`, `world.c` clothesWet, game seconds via `world.game_dt`): out in the rain wet through in SOAK_THROUGH 6 min x rain; dry by a burning fire (4 m) in 8 min, in the day outside 40 min, else 2 h. Wet (over 0.2, not by a fire): cold like the rain (0.8 x wet). Drawn darker (`figure.frag` uniform `soaked`: clothes, hair, shoes x (1 - 0.34 wet)). Tab: "Wet"; "You're wet through" once over 0.6. +- Drying off (`ai.c` dryOff, idle / wander): over 0.45 wet, the rain under 0.1, a burning fire within 60 m: over to it (2.4 m), sitting there till under 0.1. +- Thunderstorms (`weather.c`, rain over STORM_RAIN 0.7): a flash about every 75 game s (`world.flash`, fading in a quarter second: `game.c` rainLight lights everything white), the thunder 1-6 s after (`world.thunder`: said on the hint line, somebody near says something). +- Bedtime at work (`ai.c` goToBed): working, they go once sleepiness is over WORK_BEDTIME 0.9 (idle 0.8), putting the work down first. +- Rain drops within 1.5 m of the eye aren't drawn (fat bars right in front of the camera). + Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves. Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built). @@ -370,8 +377,8 @@ Most important first. - Smoking, still to do: paying for a pack (no money in the game yet) - "No shirt no service" policy (decision open) - Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers); the pullover over a railing NPCs never do on their own -- Tents, still to do: wind blowing an unpegged one about; people busy (work) when sleepy go to bed late; NPCs taking theirs down on their own (leaving) -- Rain, still to do: thunder; wet clothes +- Tents, still to do: wind blowing an unpegged one about; NPCs taking theirs down on their own (leaving) +- Rain, still to do: wind; a sound for the thunder (no sound in the game yet) - Main building: "the left building's roof peak is too far left" - to be clarified **Tools for us** @@ -381,4 +388,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 2.11.0 (build 7d62268)` +State of `entropie 2.12.0 (build afa32be)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index e1813f6..e23923d 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -41,6 +41,7 @@ uniform vec3 stream[4]; // pee or sick: an arc from the body to the ground ( uniform int streamKind; // 0 none, 1 pee, 2 sick, 3 water uniform float streamWidth; uniform int garment; // 0 trousers, 1 shorts, 2 skirt, 3 dress +uniform float soaked; // 0..1 wet from the rain: clothes and hair darker uniform float undress; // to pee: squatting trousers down / skirt up (0..1); standing fly open / skirt lifted in front (-1..0) uniform vec3 cigA, cigTip; // a cigarette (figure space): the end at the hand / lips, the burning end uniform float cigGlow; @@ -468,6 +469,8 @@ void main() { } else if (mat == M_HAIR) base = hairDetail(base, p); else if (mat < M_PEE) base = clothDetail(mat, base, p); + if (species == 0 && (mat == M_SHIRT || mat == M_PANTS || mat == M_JACKET || mat == M_HAIR || mat == M_SHOES)) + base *= 1.0 - 0.34 * soaked; // (wet cloth: darker) if (mat == M_JACKET) { // the zip down the front, a darker collar vec3 rel = p - joints[J_CHEST]; vec3 side = cross(spineDir, chestForward); diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 344a407..6fe77c6 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -182,6 +182,7 @@ int Game_stats(const Game *game, StatBar *bars, int max, const char **title, cha { "Worn (days)", p->worn, true }, { "Drunk", fminf(p->drunk, 1.0f), true }, { "Dirty", p->dirt, true }, + { "Wet", p->look.wet, true }, { "Sporty", p->fitness, false }, }; const StatBar skills[] = { /* grow with practice */ @@ -283,6 +284,10 @@ static void rainLight(Game *game) { l->sky_top = vec3_lerp(l->sky_top, vec3_scale(grey, 0.8f * bright), fminf(1.0f, 1.3f * r)); l->sky_horizon = vec3_lerp(l->sky_horizon, vec3_scale(grey, bright), fminf(1.0f, 1.2f * r)); l->shadow_strength *= 1.0f - 0.85f * r; + float f = game->world.flash; /* (lightning: everything lit up white for a moment) */ + l->light_color = vec3_add(l->light_color, vec3_scale(vec3(1.4f, 1.45f, 1.6f), f)); + l->sky_top = vec3_lerp(l->sky_top, vec3(0.9f, 0.92f, 1.0f), 0.7f * f); + l->sky_horizon = vec3_lerp(l->sky_horizon, vec3(0.95f, 0.95f, 1.0f), 0.7f * f); l->wet = game->world.wet; } @@ -321,6 +326,20 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game->rain_told = true; say(game, "It's starting to rain - under a roof (the house, your garages) or into your tent"); } else if (game->world.rain < 0.05f) game->rain_told = false; + if (game->world.thunder) { /* (the thunder: said, and somebody near reacts) */ + static const char *const ROLL[] = { "*KRRRAKK* - thunder, close by", "*rumble* - thunder rolls over the farm", "*BOOM* - that one was close" }; + say(game, ROLL[(int)(game->daycycle.seconds) % 3]); + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->type != OBJ_HUMAN || o->body.asleep || o->indoors > 0.0f || o->ai.speech_time > 0.0f || ((int)(game->daycycle.seconds) + i) % 4) continue; + Ai_say(o, (i % 2) ? "Whoa! That was close." : "Did you see that one?"); + break; + } + } + if (game->player->look.wet > 0.6f && !game->wet_told) { + game->wet_told = true; + say(game, "You're wet through - by a fire you dry quickest"); + } else if (game->player->look.wet < 0.2f) game->wet_told = false; game_arrival(game, dt); Ai_setSettled(game->arrival.task == TASK_NONE); tooHeavy(game); diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index ce199bb..004a25c 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -77,6 +77,7 @@ typedef struct { float tent_hold; /* ... held this long at a standing one (a moment: you start taking it down) */ float dt; /* this frame's step */ bool rain_told; /* told it's raining (this shower) */ + bool wet_told; /* "wet through" said (till dry again) */ } Game; /* A line of speech above someone's head, in screen pixels (y up from the bottom). */ diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 4ca5f8a..bda7612 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1960,6 +1960,33 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) { return true; } +/* Wet through after the rain: over to a burning fire, sitting by it till dry. */ +#define DRY_START 0.45f /* wet this much ... */ +#define DRY_DONE 0.1f /* ... dry enough */ +#define DRY_WALK 60.0f /* m: a burning fire this near */ + +static bool dryOff(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (!g_world || self->body.asleep || self->in_tent || self->indoors > 0.0f || self->riding) return false; + Object *fire = Camp_fireNear(objects, count, self->position, DRY_WALK, true); + if (!ai->drying) { + if (self->look.wet < DRY_START || g_world->rain > 0.1f || !fire || random01(ai) > 0.3f * dt) return false; + ai->drying = true; + if (random01(ai) < 0.6f) Ai_say(self, random01(ai) < 0.5f ? "Soaked. I'm going to dry off by the fire." : "Wet through - fire, here I come."); + } + if (self->look.wet < DRY_DONE || !fire || g_world->rain > 0.3f) { ai->drying = false; return false; } + float d = flatDistance(fire->position, self->position); + if (self->sitting && d < 4.0f) { rest(ai, 5.0f, 10.0f); return true; } + Vec3 to = vec3_sub(self->position, fire->position); + to.y = 0.0f; + to = d > 0.1f ? vec3_scale(to, 1.0f / d) : vec3(1.0f, 0.0f, 0.0f); + if (d > 3.4f && !goToPoint(self, vec3_add(fire->position, vec3_scale(to, 2.4f)), 1.0f, objects, count, dt)) return true; + stand(self); + Object_sit(self, Object_seatNear(objects, count, self->position, 1.5f), fire->position); + rest(ai, 5.0f, 10.0f); + return true; +} + /* Just arrived: over to the fire to say hello to Kurti and his sons, and sit down with them till the work starts (game_arrival.c). */ static bool greetHosts(Object *self, Object *const *objects, int count, float dt) { @@ -1981,14 +2008,17 @@ static bool greetHosts(Object *self, Object *const *objects, int count, float dt } /* Sleepy, their tent up: over to it and in. */ +#define WORK_BEDTIME 0.9f /* sleepiness: working, they keep going till this (idle: 0.8) */ + static bool goToBed(Object *self, Object *const *objects, int count, float dt) { if (self->needs.sleepiness < 0.8f || self->body.asleep || self->in_tent || self->body.pose != POSE_UP) return false; Object *tent = Tent_of(objects, count, self); if (!tent || tent->tent_stage < TENT_UP || flatDistance(tent->position, self->position) > TENT_WALK) return false; if (self->sitting) self->sitting = false; + if (self->ai.mode == AI_WORK) freeHands(self, NULL); /* (the work put down where it is) */ if (!goToPoint(self, Tent_door(tent), 0.4f, objects, count, dt)) return true; freeHands(self, NULL); - if (random01(&self->ai) < 0.5f) Ai_say(self, "Night, everybody."); + if (random01(&self->ai) < 0.5f) Ai_say(self, self->ai.mode == AI_WORK ? "That's it, I'm done in. Night." : "Night, everybody."); Tent_crawlIn(tent, self); rest(&self->ai, 2.0f, 3.0f); return true; @@ -2131,14 +2161,17 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { /* Rain: inside */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) && shelter(self, objects, count, dt)) return; + /* Wet through: by the fire to dry */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && dryOff(self, objects, count, dt)) return; /* Unloading the car */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && unloadCar(self, objects, count, dt)) return; /* Just arrived: hello at the fire */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && greetHosts(self, objects, count, dt)) return; /* Kurti and his sons: into the house for a while (not while you're being welcomed) */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && goInside(self, objects, count, dt)) return; - /* Sleepy: to bed in their tent */ - if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && goToBed(self, objects, count, dt)) return; + /* Sleepy: to bed in their tent (busy working: later, once really done in) */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || (ai->mode == AI_WORK && self->needs.sleepiness > WORK_BEDTIME)) && + goToBed(self, objects, count, dt)) return; /* Tents: their own up, a hand with others', the bed things in */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && pitchTents(self, objects, count, dt)) return; /* A finished cigarette in the fingers: to the tin, a bin, the fire */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index e68f421..1735dd8 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -179,6 +179,7 @@ typedef struct { struct Object *take_from; /* a bottle from this crate, or the next thing out of this car's boot: please (see car.c Car_serve) */ bool sheltering; /* in out of the rain (ai.c shelter) */ bool greet; /* just arrived: over to the fire, saying hello, sitting there till the work starts */ + bool drying; /* wet through after the rain: sitting by the fire till dry */ bool going_in; /* a resident on the way into the house (ai.c goInside) */ struct Object *tent_job; /* lending a hand with this tent (ai.c pitchTents) ... */ float tent_time; /* ... this long */ diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index 6e6c098..cb012d3 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -32,6 +32,7 @@ typedef struct { bool jacket_tied; /* the jacket off and along with them: tied round the waist by its sleeves */ float belly; /* 0 .. 1: a belly (Kurti's) */ bool keeps_shirt; /* never takes the shirt off, sun or no sun */ + float wet; /* 0 .. 1: clothes and hair wet from the rain (darker) */ } FigureLook; enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET }; diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index 5b13af6..2b25bc9 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -105,6 +105,7 @@ static bool loadShaders(Renderer *r) { f->top = glGetUniformLocation(f->program, "top"); f->jacket_tied = glGetUniformLocation(f->program, "jacketTied"); f->belly = glGetUniformLocation(f->program, "belly"); + f->wet = glGetUniformLocation(f->program, "soaked"); f->cig_a = glGetUniformLocation(f->program, "cigA"); f->uri_a = glGetUniformLocation(f->program, "uriA"); f->uri_b = glGetUniformLocation(f->program, "uriB"); @@ -548,6 +549,7 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { glUniform1i(f->top, pose->look.top); glUniform1i(f->jacket_tied, pose->look.jacket_tied && pose->look.top != TOP_JACKET ? 1 : 0); glUniform1f(f->belly, pose->look.belly); + glUniform1f(f->wet, pose->look.wet); setVec3(f->cig_a, sk->cig_a); setVec3(f->uri_a, sk->uri_a); setVec3(f->uri_b, sk->uri_b); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index 734bcb9..fb7cdb5 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -70,7 +70,7 @@ typedef struct { GLint skin, hair, shirt, pants, shoes, hair_style, long_sleeves; GLint head_forward, head_up, chest_forward, waist_bend, face, expression; GLint stream, stream_kind, stream_width; - GLint garment, undress, top, jacket_tied, belly; + GLint garment, undress, top, jacket_tied, belly, wet; GLint cig_a, cig_tip, cig_glow, jacket, uri_a, uri_b, uri; GLint species, tail_a, tail_b; GLint view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon; diff --git a/entropie/src/world/weather.c b/entropie/src/world/weather.c index a546f6c..8ec6206 100644 --- a/entropie/src/world/weather.c +++ b/entropie/src/world/weather.c @@ -19,6 +19,9 @@ #define DROP_SPEED 8.5f #define DROP_LEN 0.6f #define FIRE_DOUSE 0.004f /* kg of a fire's wood a game second, in hard rain */ +#define STORM_RAIN 0.7f /* rain this hard: a thunderstorm ... */ +#define FLASH_EVERY 75.0f /* ... a flash about every this many game seconds */ +#define FLASH_FADE 4.0f /* a flash gone in a quarter second (real) */ static float dice(unsigned *rng) { *rng = *rng * 1103515245u + 12345u; @@ -30,6 +33,8 @@ void Weather_init(World *world) { world->rain_left = FIRST_DRY; world->wet = 0.0f; world->weather = WEATHER_AUTO; + world->flash = world->thunder_in = 0.0f; + world->thunder = false; world->drops = calloc(1, sizeof(*world->drops)); if (world->drops) world->drops->rng = 0x5eed1234u; } @@ -72,6 +77,14 @@ void Weather_update(World *world, float dt, float game_dt) { if (world->rain < 0.002f && world->rain_goal <= 0.0f) world->rain = 0.0f; bool day = world->hour >= 7.0f && world->hour < 20.0f; world->wet = clampf(world->wet + world->rain * game_dt / SOAK_TIME - (1.0f - world->rain) * game_dt / (DRY_TIME * (day ? 1.0f : 3.0f)), 0.0f, 1.0f); + /* a thunderstorm: lightning, the thunder a few seconds after it */ + world->thunder = false; + world->flash = fmaxf(0.0f, world->flash - FLASH_FADE * dt); + if (world->thunder_in > 0.0f && (world->thunder_in -= dt) <= 0.0f) { world->thunder_in = 0.0f; world->thunder = true; } + if (world->rain > STORM_RAIN && world->drops && world->thunder_in <= 0.0f && dice(&world->drops->rng) < game_dt / FLASH_EVERY) { + world->flash = 1.0f; + world->thunder_in = 1.0f + 5.0f * dice(&world->drops->rng); /* (near: right after; further: later) */ + } if (world->rain <= 0.0f || !world->drops) return; /* the ground near you gets the water: puddles, mud (not under a roof) */ @@ -106,7 +119,10 @@ void Weather_draw(World *world, Renderer *renderer) { Vec3 shown[RAIN_DROPS]; int n = 0, live = (int)(RAIN_DROPS * world->rain); for (int k = 0; k < live; k++) - if (!world->drops->hidden[k]) shown[n++] = world->drops->p[k]; + if (!world->drops->hidden[k]) { /* (not right in front of the eye: a fat bar) */ + Vec3 p = world->drops->p[k], e = vec3_sub(p, world->eye); + if (e.x * e.x + e.z * e.z > 1.5f * 1.5f || fabsf(e.y) > 1.5f) shown[n++] = p; + } if (n == 0) return; Mesh mesh = Shapes_rain(shown, n, DROP_LEN); Renderer_drawSmoke(renderer, &mesh, mat4_identity(), vec3(1.0f, 1.0f, 1.0f)); diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 9e9bee6..6f0b529 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -726,6 +726,22 @@ static void smoke(World *world, Object *o, Vec3 facing, float dt) { } } +/* Wet clothes: out in the rain they soak through; they dry by a fire quickest, + in the day slower, at night or inside slowest (game seconds). */ +#define SOAK_THROUGH (6.0f * 60.0f) /* hard rain: wet through */ +#define DRY_BY_FIRE (8.0f * 60.0f) +#define DRY_IN_DAY (40.0f * 60.0f) +#define DRY_ELSE (120.0f * 60.0f) + +static void clothesWet(World *world, Object *o, bool by_fire, bool night) { + float gdt = world->game_dt; + if (o->rain_out) o->look.wet = fminf(1.0f, o->look.wet + world->rain * gdt / SOAK_THROUGH); + else if (o->look.wet > 0.0f) { + float dry = by_fire ? DRY_BY_FIRE : !night && o->indoors <= 0.0f && !o->in_tent ? DRY_IN_DAY : DRY_ELSE; + o->look.wet = fmaxf(0.0f, o->look.wet - gdt / dry); + } +} + /* Cold nights: away from a burning fire a chill builds up and the pullover goes on; by the fire, in the day (or warm with drink) it comes off again (round the waist). Put down somewhere: people go and get it, you are told where it @@ -738,7 +754,9 @@ static void dressForNight(World *world, Object *o, float dt) { if (world->rain > 0.2f && !o->in_tent && o->indoors <= 0.0f && !o->riding && ((int)(world->hour * 3600.0f) + (int)(o->position.x * 7.0f)) % 30 == 0) o->rain_out = !Weather_covered(world, vec3_add(o->position, vec3(0.0f, o->look.height, 0.0f))); /* (out in it: now and then looked up) */ if (world->rain <= 0.2f || o->in_tent || o->indoors > 0.0f) o->rain_out = false; - if (o->rain_out) cold = fmaxf(cold, 0.0f) + 1.2f * world->rain; /* (wet through: cold, even in the day) */ + clothesWet(world, o, by_fire, night); + if (o->rain_out) cold = fmaxf(cold, 0.0f) + 1.2f * world->rain; /* (out in it: cold, even in the day) */ + else if (o->look.wet > 0.2f && !by_fire) cold = fmaxf(cold, 0.0f) + 0.8f * o->look.wet; /* (in wet clothes: cold too) */ o->chill = clampf(o->chill + cold * dt / 60.0f, 0.0f, 1.0f); /* (about a minute to feel it) */ bool player = o->type == OBJ_PLAYER; if (o->look.top != TOP_JACKET && o->chill > 0.6f && !(player && o->jacket_told)) { @@ -1238,6 +1256,7 @@ static void emptyTray(World *world, Object *o) { void World_update(World *world, float dt, float game_dt) { Object_indexKinds(world->objects, world->count); world->dt_props = dt; + world->game_dt = game_dt; Clothes_update(world); Weather_update(world, dt, game_dt); Car_serve(world); diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index deb62ca..aab3899 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -15,6 +15,7 @@ typedef struct World { float dt_props; /* (this frame's step, for the props that move: gates) */ + float game_dt; /* (this frame's step in game seconds) */ Object **objects; int count; int capacity; @@ -32,6 +33,9 @@ typedef struct World { float rain_goal, rain_left; /* ... where it's going, for how much longer (game s) */ int weather; /* WeatherKind: on its own, or set (dev menu) */ float wet; /* 0..1 everything wet from the rain (dries over hours) */ + float flash; /* 0..1 lightning just now (fades in a moment) ... */ + float thunder_in; /* ... the thunder after it, in this many seconds (0: none coming) ... */ + bool thunder; /* ... rolling just now (this frame: the game says so) */ Vec3 eye; /* where the camera is (set by the game: the rain falls round it) */ struct RainDrops *drops; } World;