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.13.0: wind
- Wind with calm and windy spells and gusts (windier with rain and storms) - Rain slants and drifts with it; fires now smoke, and smoke and flames go with the wind - Tents not pegged down (under half the pegs) blow about in strong wind until they snag or someone works on them; the hint line warns you - Dev menu: wind (on its own, calm, a breeze, a storm)
commit_message.txt | 13 +++++----- entropie/Makefile | 2 +- entropie/README.md | 6 ++++- entropie/STATE.md | 11 ++++++--- entropie/src/game/game.c | 8 +++++++ entropie/src/game/game.h | 1 + entropie/src/game/game_dev.c | 8 ++++++- entropie/src/render/shapes.h | 2 +- entropie/src/render/shapes_props.c | 4 ++-- entropie/src/world/chips.c | 35 ++++++++++++++++++++------- entropie/src/world/chips.h | 5 +++- entropie/src/world/object.h | 1 + entropie/src/world/tent.c | 30 +++++++++++++++++++++++ entropie/src/world/weather.c | 49 ++++++++++++++++++++++++++++++++++++-- entropie/src/world/weather.h | 6 +++++ entropie/src/world/world.c | 22 ++++++++++++----- entropie/src/world/world.h | 8 +++++++ 17 files changed, 179 insertions(+), 32 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index 07a6e3e..fdf2961 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,7 +1,8 @@ -entropie 2.12.0: wet clothes, thunderstorms, bedtime at work +entropie 2.13.0: wind -- 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 +- Wind with calm and windy spells and gusts (windier with rain and storms) +- Rain slants and drifts with it; fires now smoke, and smoke and flames + go with the wind +- Tents not pegged down (under half the pegs) blow about in strong wind + until they snag or someone works on them; the hint line warns you +- Dev menu: wind (on its own, calm, a breeze, a storm) diff --git a/entropie/Makefile b/entropie/Makefile index 2faa756..796f03f 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.12.0 +VERSION := 2.13.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index f1896f7..f839f4c 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -56,6 +56,10 @@ Painted in the rotoscope style of *Waking Life* (2001): 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 +- Wind: calm or windy spells, gusts on top (windier with rain). The rain + slants, the fire's smoke and flames go with it +- A tent not pegged down (under half its pegs) blows about in strong wind + until it snags on something or somebody works on it ## Tents @@ -701,7 +705,7 @@ say no more often and don't come to you for a bit. | V | Third / first person | | I | Show all keys in game | | Tab | Your stats (Left / Right: other page) | -| F1 | Dev menu: pick anyone, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard; time of day, game speed, weather, skip the start | +| F1 | Dev menu: pick anyone, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard; time of day, game speed, weather, wind, skip the start | | Esc | Menu: resume, keys, quit (the game waits meanwhile) | The game starts in third person. Close to a wall the view moves in, never through it. diff --git a/entropie/STATE.md b/entropie/STATE.md index b4cbc09..3bd2853 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -231,6 +231,11 @@ Round 2.12: - 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). +Round 2.13: +- Wind (`weather.c` blow): `wind_goal` 0..1 (x WIND_MAX 14 m/s) changes every 20-80 game min (0.04 + 0.35 d^2 + 0.45 x rain coming), a thunderstorm at least 0.55; `wind_level` eases to it (WIND_EASE 240 game s); `wind_angle` turns a bit each change. Gusts: a slow beat of two sines (GUST_RATE 0.45) on top (up to 0.6 x WIND_MAX x level). `world.wind` the vector now; `Weather_windSpeed`. Dev menu: on its own / calm / a breeze / a storm. +- Rain drops drift with it and slant (`Shapes_rain` lean: the top upwind). Smoke and sawdust slow to the wind's speed (`Chips_update` wind). Fires smoke now (`Chips_smoke`, a big soft grey puff every 0.22 s / flame size, rising 0.9, spreading 0.22 m/s). Flames sheared downwind (`drawFlames`, 0.07 x wind, at most 0.8). +- Tents in the wind (`tent.c` blowAbout): not up and under half the pegs in, nobody working on it for BLOW_HELD 1.5 s: on its poles from 4 m/s, flat from 6.5 m/s, it slides 0.13 m/s per m/s over that (flat half), turning a little, on the ground; a ray along the wind (0.3 m up) hitting something static: snagged, it stays. `world.tent_blowing` -> the hint line: "Volkram's tent is blowing about in the wind - peg it down!" (not again for 60 s). + 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). @@ -377,8 +382,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; NPCs taking theirs down on their own (leaving) -- Rain, still to do: wind; a sound for the thunder (no sound in the game yet) +- Tents, still to do: NPCs taking theirs down on their own (leaving); owners running after theirs when it blows away +- Weather, still to do: a sound for thunder, wind and rain (no sound in the game yet); trees and bushes swaying; loose light things (paper, a pullover) blown about - Main building: "the left building's roof peak is too far left" - to be clarified **Tools for us** @@ -388,4 +393,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.12.0 (build afa32be)` +State of `entropie 2.13.0 (build f0f0b53)` diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 6fe77c6..9882dd9 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -336,6 +336,14 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { break; } } + game->blow_told = fmaxf(0.0f, game->blow_told - dt); + if (game->world.tent_blowing && game->blow_told <= 0.0f) { /* (a tent not pegged down, the wind pushing it about) */ + const Object *t = game->world.tent_blowing, *o = t->owner; + static char line[96]; + snprintf(line, sizeof(line), "%s%s tent is blowing about in the wind - peg it down!", !o ? "A" : o == game->player ? "Your" : o->name, o && o != game->player ? "'s" : ""); + say(game, line); + game->blow_told = 60.0f; + } 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"); diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 004a25c..d2729ce 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -78,6 +78,7 @@ typedef struct { float dt; /* this frame's step */ bool rain_told; /* told it's raining (this shower) */ bool wet_told; /* "wet through" said (till dry again) */ + float blow_told; /* "your tent is blowing about" said: not again for a while */ } Game; /* A line of speech above someone's head, in screen pixels (y up from the bottom). */ diff --git a/entropie/src/game/game_dev.c b/entropie/src/game/game_dev.c index ddcc009..0b8e8b2 100644 --- a/entropie/src/game/game_dev.c +++ b/entropie/src/game/game_dev.c @@ -10,7 +10,7 @@ #include "world/weather.h" typedef enum { - DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_START, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY, + DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_WIND, DEV_START, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY, DEV_BLADDER, DEV_HUNGER, DEV_THIRST, DEV_SLEEPY, DEV_STRENGTH, DEV_ANNOYED, DEV_PRIDE, DEV_LIVELY, DEV_NERVES, DEV_CLUMSY, DEV_WITS, DEV_HEIGHTS, DEV_WILLING, DEV_LIKES, /* likes for each job ... */ @@ -109,6 +109,11 @@ void game_dev(Game *game, const Input *input) { if (game->world.weather == WEATHER_AUTO) game->world.rain_left = 0.0f; /* (on its own again: from now) */ return; } + if (game->dev_row == DEV_WIND) { + game->world.wind_kind = (game->world.wind_kind + dir + WIND_KINDS) % WIND_KINDS; + if (game->world.wind_kind == WIND_AUTO) game->world.wind_left = 0.0f; /* (on its own again: from now) */ + return; + } if (game->dev_row == DEV_START) { if (dir > 0) game_skipArrival(game); return; @@ -174,6 +179,7 @@ static void devLine(const Game *game, const Object *o, int row, char *out) { case DEV_SPEED: snprintf(out, 96, "Time runs: %.0fx", game->daycycle.time_scale); break; case DEV_WEATHER: snprintf(out, 96, "Weather: %s (raining %.0f%%, wet %.0f%%)", Weather_name((WeatherKind)game->world.weather), game->world.rain * 100.0f, game->world.wet * 100.0f); break; + case DEV_WIND: snprintf(out, 96, "Wind: %s (%.0f m/s)", Weather_windName((WindKind)game->world.wind_kind), Weather_windSpeed(&game->world)); break; case DEV_START: snprintf(out, 96, "The start: %s", game->arrival.task == TASK_NONE && game->arrival.stage != ARRIVAL_DRIVING ? "done" : "skip it ->"); break; case DEV_FEMALE: snprintf(out, 96, "Female: %s", o->female ? "yes" : "no"); break; case DEV_URINELLA: snprintf(out, 96, "Urinella: %s (%d left)", o->urinella ? "yes" : "no", o->urinellas); break; diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index 8539427..065bb34 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -134,7 +134,7 @@ Mesh Shapes_car(Vec3 color, bool boot_open); /* ... seen from the driver's seat: the bonnet, the dashboard, the wheel, the pillars, the roof. */ Mesh Shapes_carInside(Vec3 color); /* Rain: a thin light streak at each drop (world space), len long. */ -Mesh Shapes_rain(const Vec3 *drops, int n, float len); +Mesh Shapes_rain(const Vec3 *drops, int n, float len, Vec3 lean); /* A crate of bottles (centred): variant 0 beer, 1 water, 2 soda (cola and orange), 3 mate; this many bottles in it. */ #define CRATE_SIZE_X 0.40f #define CRATE_SIZE_Y 0.30f diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 086ea63..405d145 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -954,12 +954,12 @@ Mesh Shapes_carInside(Vec3 color) { return Mesh_fromBuilder(&b); } -Mesh Shapes_rain(const Vec3 *drops, int n, float len) { +Mesh Shapes_rain(const Vec3 *drops, int n, float len, Vec3 lean) { MeshBuilder b; MeshBuilder_init(&b); const Vec3 color = { 0.78f, 0.82f, 0.88f }; for (int k = 0; k < n; k++) { - Vec3 p = drops[k], d = vec3(0.07f * len, len, 0.0f); /* (leaning a little with the wind) */ + Vec3 p = drops[k], d = vec3(lean.x * len, len, lean.z * len); /* (leaning with the wind: the top upwind) */ Vec3 side = vec3(0.0f, 0.0f, 0.014f), across = vec3(0.014f, 0.0f, 0.0f); GLuint i0 = MeshBuilder_vertex(&b, vec3_sub(p, side), vec3(0, 0, 1), color); GLuint i1 = MeshBuilder_vertex(&b, vec3_add(p, side), vec3(0, 0, 1), color); diff --git a/entropie/src/world/chips.c b/entropie/src/world/chips.c index 6053a54..19b87f4 100644 --- a/entropie/src/world/chips.c +++ b/entropie/src/world/chips.c @@ -68,22 +68,41 @@ void Chips_puff(Chips *c, Vec3 at, Vec3 dir, bool small) { }; } -void Chips_update(Chips *c, const Terrain *terrain, float dt) { +void Chips_smoke(Chips *c, Vec3 at) { + Chip *ch = &c->chips[c->next]; + c->next = (c->next + 1) % CHIP_MAX; + Vec3 jitter = vec3(rnd(c) - 0.5f, 0.0f, rnd(c) - 0.5f); + float grey = 0.78f + 0.12f * rnd(c); + *ch = (Chip){ + .p = vec3_add(at, vec3_scale(jitter, 0.4f)), + .v = vec3_add(vec3_scale(jitter, 0.2f), vec3(0.0f, 0.7f + 0.3f * rnd(c), 0.0f)), + .life = 3.5f + 1.5f * rnd(c), + .size = 0.25f + 0.12f * rnd(c), + .angle = 6.28f * rnd(c), + .spin = (rnd(c) - 0.5f) * 0.8f, + .color = vec3(grey, grey, grey * 1.04f), + .smoke = true, + }; +} + +void Chips_update(Chips *c, const Terrain *terrain, Vec3 wind, float dt) { for (int i = 0; i < CHIP_MAX; i++) { Chip *ch = &c->chips[i]; if (ch->life <= 0.0f) continue; ch->life -= dt; - if (ch->smoke) { /* slows down, rises, spreads */ - ch->v = vec3_scale(ch->v, expf(-2.5f * dt)); - ch->v.y += 0.35f * dt; + if (ch->smoke) { /* slows down to the wind's speed, rises, spreads */ + float k = expf(-2.5f * dt), up = ch->v.y * k; + ch->v = vec3_add(vec3_scale(ch->v, k), vec3_scale(wind, 0.8f * (1.0f - k))); + ch->v.y = up + (ch->size > 0.05f ? 0.9f : 0.35f) * dt; /* (a fire's smoke: warm, rising more) */ ch->p = vec3_add(ch->p, vec3_scale(ch->v, dt)); - ch->size += 0.03f * dt; + ch->size += (ch->size > 0.05f ? 0.22f : 0.03f) * dt; /* (a fire's smoke spreads more) */ ch->angle += ch->spin * dt; continue; } if (ch->down) continue; - float drag = ch->dust ? 3.5f : 0.6f; - ch->v = vec3_scale(ch->v, expf(-drag * dt)); + float drag = ch->dust ? 3.5f : 0.6f, k = expf(-drag * dt), fall = ch->v.y * k; + ch->v = vec3_add(vec3_scale(ch->v, k), ch->dust ? vec3_scale(wind, 0.7f * (1.0f - k)) : vec3_zero()); + ch->v.y = fall; ch->v.y -= GRAVITY_C * (ch->dust ? 0.25f : 1.0f) * dt; ch->p = vec3_add(ch->p, vec3_scale(ch->v, dt)); ch->angle += ch->spin * dt; @@ -102,7 +121,7 @@ void Chips_draw(const Chips *c, Renderer *renderer) { const Chip *ch = &c->chips[i]; if (ch->life <= 0.0f) continue; float shrink = fminf(1.0f, ch->life / (ch->smoke ? 0.5f : ch->dust ? 0.3f : 1.5f)); /* (fading away at the end) */ - float s = ch->size * shrink; + float s = ch->size * (ch->smoke && ch->size > 0.05f ? 1.0f : shrink); /* (a fire's smoke: thinning out instead) */ Mat4 m = mat4_mul(mat4_translate(ch->p), mat4_mul(mat4_rotate_yxz(vec3(ch->down ? 0.0f : ch->angle, ch->angle * 0.7f, 0.0f)), mat4_scale(vec3(s, s * (ch->dust || ch->smoke ? 1.0f : 0.35f), s * (ch->dust || ch->smoke ? 1.0f : 0.7f))))); if (ch->smoke) Renderer_drawSmoke(renderer, &c->mesh, m, ch->color); diff --git a/entropie/src/world/chips.h b/entropie/src/world/chips.h index 333e546..1800e7c 100644 --- a/entropie/src/world/chips.h +++ b/entropie/src/world/chips.h @@ -40,7 +40,10 @@ void Chips_destroy(Chips *c); void Chips_spray(Chips *c, Vec3 at, Vec3 dir, int n, bool dust); /* A little puff of smoke at at (breathed out along dir, or off a cigarette's tip: small). */ void Chips_puff(Chips *c, Vec3 at, Vec3 dir, bool small); -void Chips_update(Chips *c, const Terrain *terrain, float dt); +/* Smoke off a fire (a big soft puff, rising). */ +void Chips_smoke(Chips *c, Vec3 at); +/* wind: m/s the air moves (smoke and dust go with it). */ +void Chips_update(Chips *c, const Terrain *terrain, Vec3 wind, float dt); void Chips_draw(const Chips *c, Renderer *renderer); #endif /* WORLD_CHIPS_H */ diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 144e876..493b6d0 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -171,6 +171,7 @@ typedef struct Object { int tent_stage; /* tents: TentStage ... */ float tent_work; /* ... how far the stage is (0..1) ... */ float tent_idle; /* ... seconds since anybody worked on it */ + float smoke_t; /* fireplaces: till the next puff of smoke */ int tent_beds; /* ... sleeping bags (bit 0 owner, 2 owner2) and mats (1, 3) put in */ bool tent_down; /* ... being taken down: the stages backwards, then packed into its bag */ struct Object *in_tent; /* characters: sleeping in this tent (no collisions with it) */ diff --git a/entropie/src/world/tent.c b/entropie/src/world/tent.c index 513d025..6e13496 100644 --- a/entropie/src/world/tent.c +++ b/entropie/src/world/tent.c @@ -2,6 +2,7 @@ #include "world/car.h" #include "people/hands.h" #include "render/shapes.h" +#include "world/weather.h" #include <stdio.h> #include <string.h> @@ -267,14 +268,43 @@ static void pack(World *world, Object *tent) { Object_useUp(tent); } +/* The wind and a tent not pegged down (pegs under half in): it slides and + turns along the ground, more standing on its poles than lying flat, till + it snags on something or somebody gets hold of it (working on it). */ +#define BLOW_STANDING 4.0f /* m/s: on its poles, it goes from this wind ... */ +#define BLOW_FLAT 6.5f /* ... lying flat from this */ +#define BLOW_PUSH 0.13f /* m/s it slides for each m/s over that */ +#define BLOW_HELD 1.5f /* s: worked on this recently, it's held */ + +static bool notIt(const Object *o, const void *tent) { return o == tent || o->is_character || !o->is_static; } + +static void blowAbout(World *world, Object *tent, float dt) { + if (tent->tent_stage >= TENT_UP || (tent->tent_stage == TENT_PEGS && tent->stored * 2 >= TENT_PEGS(tent->variant)) || + tent->tent_idle < BLOW_HELD) return; + float speed = Weather_windSpeed(world), from = tent->tent_stage == TENT_LAID ? BLOW_FLAT : BLOW_STANDING; + if (speed <= from) return; + Vec3 dir = vec3_scale(world->wind, 1.0f / speed); + float push = (speed - from) * BLOW_PUSH * (tent->tent_stage == TENT_LAID ? 0.5f : 1.0f); + float reach = 0.5f * fmaxf(TENT_W(tent->variant), TENT_L(tent->variant)) + 0.15f; + Vec3 from_p = vec3_add(tent->position, vec3(0.0f, 0.3f, 0.0f)); + if (World_rayCast(world, from_p, dir, reach, notIt, tent, NULL, NULL) >= 0.0f) return; /* (snagged on something) */ + tent->position = vec3_add(tent->position, vec3_scale(dir, push * dt)); + tent->position.y = World_groundHeight(world, tent->position.x, tent->position.z, tent->position.y + 0.5f); + Object_setRotation(tent, vec3(0.0f, tent->rotation.y + 0.06f * push * sinf(tent->position.x * 1.7f + tent->position.z) * dt, 0.0f)); + Object_syncColliders(tent); + world->tent_blowing = tent; +} + #define RAIN_NAP_END 0.15f /* rain: under this, out of the tent again */ void Tent_update(World *world, float dt) { + world->tent_blowing = NULL; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type == OBJ_TENT) { o->tent_idle += dt; if (o->tent_down && !o->broken && o->tent_stage == TENT_LAID && o->tent_work >= 1.0f) pack(world, o); + else if (!o->broken) blowAbout(world, o, dt); continue; } if (o->rain_nap && (!o->body.asleep || world->rain < RAIN_NAP_END)) { /* in out of the rain: up when it stops */ diff --git a/entropie/src/world/weather.c b/entropie/src/world/weather.c index 8ec6206..5114316 100644 --- a/entropie/src/world/weather.c +++ b/entropie/src/world/weather.c @@ -22,6 +22,10 @@ #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) */ +#define WIND_EASE 240.0f /* game s: the wind picks up / dies down over about this */ +#define WIND_SPELL (20.0f * 60.0f) /* game s: it changes after this ... */ +#define WIND_SPELL_MORE (60.0f * 60.0f) /* ... up to this much later */ +#define GUST_RATE 0.45f /* gusts come and go (real s, a slow beat of two sines) */ static float dice(unsigned *rng) { *rng = *rng * 1103515245u + 12345u; @@ -33,12 +37,50 @@ void Weather_init(World *world) { world->rain_left = FIRST_DRY; world->wet = 0.0f; world->weather = WEATHER_AUTO; + world->wind_kind = WIND_AUTO; + world->wind = vec3_zero(); + world->wind_goal = world->wind_level = 0.08f; + world->wind_angle = 0.6f; + world->wind_left = WIND_SPELL; + world->gust = world->gust_t = 0.0f; world->flash = world->thunder_in = 0.0f; world->thunder = false; world->drops = calloc(1, sizeof(*world->drops)); if (world->drops) world->drops->rng = 0x5eed1234u; } +const char *Weather_windName(WindKind kind) { + static const char *const NAME[WIND_KINDS] = { "on its own", "calm", "a breeze", "a storm" }; + return NAME[(unsigned)kind < WIND_KINDS ? kind : 0]; +} + +float Weather_windSpeed(const World *world) { + return hypotf(world->wind.x, world->wind.z); +} + +/* The wind: calm spells and windy ones (windier with rain coming), turning now + and then; gusts on top of it. */ +static void blow(World *world, float dt, float game_dt) { + unsigned *rng = world->drops ? &world->drops->rng : &world->rng; + if (world->wind_kind == WIND_CALM) world->wind_goal = 0.03f; + else if (world->wind_kind == WIND_BREEZE) world->wind_goal = 0.3f; + else if (world->wind_kind == WIND_STORM) world->wind_goal = 0.85f; + else if ((world->wind_left -= game_dt) <= 0.0f) { + world->wind_left = WIND_SPELL + WIND_SPELL_MORE * dice(rng); + float d = dice(rng); + world->wind_goal = 0.04f + 0.35f * d * d + 0.45f * world->rain_goal * dice(rng); + world->wind_angle += (dice(rng) - 0.5f) * 1.6f; /* (turning a bit) */ + } + if (world->wind_kind == WIND_AUTO && world->rain > STORM_RAIN) world->wind_goal = fmaxf(world->wind_goal, 0.55f); /* (a thunderstorm: windy) */ + world->wind_level += (world->wind_goal - world->wind_level) * fminf(1.0f, game_dt / WIND_EASE); + world->gust_t += dt * GUST_RATE; + float beat = 0.5f + 0.5f * sinf(world->gust_t * 2.3f) * sinf(world->gust_t * 0.71f + 1.3f); + world->gust = clampf(beat * beat * 1.6f - 0.25f, 0.0f, 1.0f) * world->wind_level; + float speed = WIND_MAX * world->wind_level * (0.7f + 0.3f * sinf(world->gust_t * 3.1f)) + 0.6f * WIND_MAX * world->gust; + float a = world->wind_angle + 0.15f * sinf(world->gust_t * 1.7f); + world->wind = vec3(cosf(a) * speed, 0.0f, sinf(a) * speed); +} + const char *Weather_name(WeatherKind kind) { static const char *const NAME[WEATHER_KINDS] = { "on its own", "dry", "a shower", "pouring" }; return NAME[(unsigned)kind < WEATHER_KINDS ? kind : 0]; @@ -77,6 +119,7 @@ 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); + blow(world, dt, game_dt); /* a thunderstorm: lightning, the thunder a few seconds after it */ world->thunder = false; world->flash = fmaxf(0.0f, world->flash - FLASH_FADE * dt); @@ -108,7 +151,8 @@ void Weather_update(World *world, float dt, float game_dt) { float dx = p->x - world->eye.x, dz = p->z - world->eye.z; if (p->y == 0.0f && p->x == 0.0f) { respawn(world, k, true); continue; } p->y -= DROP_SPEED * dt; - p->x += 0.6f * dt; /* (a little wind) */ + p->x += world->wind.x * dt; /* (blown along) */ + p->z += world->wind.z * dt; float ground = world->has_terrain ? Terrain_height(&world->terrain, p->x, p->z) : 0.0f; if (p->y < ground || dx * dx + dz * dz > DROP_RADIUS * DROP_RADIUS * 1.3f) respawn(world, k, p->y >= ground); } @@ -124,7 +168,8 @@ void Weather_draw(World *world, Renderer *renderer) { 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); + Vec3 lean = vec3(-world->wind.x / DROP_SPEED, 0.0f, -world->wind.z / DROP_SPEED); /* (slanting into where it comes from) */ + Mesh mesh = Shapes_rain(shown, n, DROP_LEN, lean); Renderer_drawSmoke(renderer, &mesh, mat4_identity(), vec3(1.0f, 1.0f, 1.0f)); Mesh_destroy(&mesh); } diff --git a/entropie/src/world/weather.h b/entropie/src/world/weather.h index a29601c..c01ad9e 100644 --- a/entropie/src/world/weather.h +++ b/entropie/src/world/weather.h @@ -12,6 +12,9 @@ #include "world/world.h" typedef enum { WEATHER_AUTO, WEATHER_DRY, WEATHER_SHOWER, WEATHER_RAIN, WEATHER_KINDS } WeatherKind; +typedef enum { WIND_AUTO, WIND_CALM, WIND_BREEZE, WIND_STORM, WIND_KINDS } WindKind; + +#define WIND_MAX 14.0f /* m/s: the windiest it gets (gusts on top) */ #define RAIN_DROPS 1400 @@ -28,5 +31,8 @@ void Weather_draw(World *world, Renderer *renderer); /* Is there a roof (anything) over this spot? */ bool Weather_covered(const World *world, Vec3 p); const char *Weather_name(WeatherKind kind); +const char *Weather_windName(WindKind kind); +/* m/s the wind blows now (gusts and all). */ +float Weather_windSpeed(const World *world); #endif /* WORLD_WEATHER_H */ diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 6f0b529..658a95b 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -1334,7 +1334,14 @@ void World_update(World *world, float dt, float game_dt) { Chips_spray(&world->chips, w->point, vec3_add(out, vec3(0.0f, -0.4f, 0.0f)), (int)want, true); } } - Chips_update(&world->chips, &world->terrain, dt); + Chips_update(&world->chips, &world->terrain, world->wind, dt); + for (int i = 0; i < world->count; i++) { /* smoke off the fires (with the wind) */ + Object *f = world->objects[i]; + if (f->type != OBJ_FIREPLACE || !f->burning) continue; + if ((f->smoke_t -= dt) > 0.0f) continue; + f->smoke_t = 0.22f / fmaxf(0.4f, Camp_flameSize(f)); + Chips_smoke(&world->chips, vec3_add(f->position, vec3(0.0f, 0.6f + 0.4f * Camp_flameSize(f), 0.0f))); + } { /* woodpiles count what really lies on them; what fell off isn't on one */ Object *piles[16]; int np = 0; @@ -1407,11 +1414,14 @@ static Mat4 scaled(Vec3 s) { return m; } -/* Flames over a fire: they flicker, and grow with the wood in it. */ -static void drawFlames(Renderer *renderer, const Mesh *flames, Vec3 at, float width, float height, float spin) { +/* Flames over a fire: they flicker, and grow with the wood in it; the wind bends them over. */ +static void drawFlames(Renderer *renderer, const Mesh *flames, Vec3 at, float width, float height, float spin, Vec3 wind) { float t = renderer->boil_time; float flicker = 0.85f + 0.3f * (0.5f + 0.5f * sinf(t * 23.0f + spin) * sinf(t * 7.3f + 1.0f)); - Mat4 model = mat4_mul(mat4_translate(at), mat4_mul(mat4_rotate_y(t * 2.1f + spin), scaled(vec3(width, height * flicker, width)))); + Mat4 lean = mat4_identity(); /* (sheared downwind: the tips go furthest) */ + lean.m[4] = clampf(wind.x * 0.07f, -0.8f, 0.8f); + lean.m[6] = clampf(wind.z * 0.07f, -0.8f, 0.8f); + Mat4 model = mat4_mul(mat4_translate(at), mat4_mul(lean, mat4_mul(mat4_rotate_y(t * 2.1f + spin), scaled(vec3(width, height * flicker, width))))); Renderer_drawMesh(renderer, flames, model); } @@ -1495,7 +1505,7 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { if (obj->on_fire > 0.0f) { bool down = Body_isDown(&obj->body); drawFlames(renderer, &world->flames, vec3_add(obj->position, vec3(0.0f, down ? 0.0f : 0.3f, 0.0f)), - down ? 1.3f : 0.8f, down ? 0.9f : 2.0f, (float)i); + down ? 1.3f : 0.8f, down ? 0.9f : 2.0f, (float)i, world->wind); } } else if (obj->type == OBJ_CAR && !draw_player && playerIn(world, obj)) { /* (from the driver's seat: inside it) */ Renderer_drawMesh(renderer, &obj->inside, Object_modelMatrix(obj)); @@ -1526,7 +1536,7 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { if (obj->burning) { /* more wood: bigger, and taller still */ float size = Camp_flameSize(obj), width = size * obj->size.x / 1.4f; drawFlames(renderer, &obj->flames, vec3_add(obj->position, vec3(0.0f, 0.05f, 0.0f)), width, - width * (0.85f + 0.2f * size), 0.0f); + width * (0.85f + 0.2f * size), 0.0f, world->wind); } } } diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index aab3899..276a059 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -36,6 +36,14 @@ typedef struct World { 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 wind; /* m/s the air moves now (gusts and all; flat) ... */ + float wind_goal; /* ... 0..1 how windy it's getting (x WIND_MAX m/s) ... */ + float wind_level; /* ... how windy it is (eases to the goal) ... */ + float wind_angle; /* ... where it blows to (wanders) ... */ + float wind_left; /* ... game s till it changes ... */ + float gust, gust_t; /* ... a gust now (0..1), and its clock */ + int wind_kind; /* WindKind: on its own, or set (dev menu) */ + struct Object *tent_blowing; /* a tent the wind is pushing about just now (or NULL) */ Vec3 eye; /* where the camera is (set by the game: the rain falls round it) */ struct RainDrops *drops; } World;