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.9.0: rain, soda and Mate crates, dev menu time/weather
- Weather: dry spells and showers; drops round the camera, grey sky, wetter look, real water on the ground, fires burn down faster - People shelter: Kurti and sons into the house, the others into the garage kitchen; rain makes you colder - Two more crates in the boot: soda (cola and orange) and Mate (wakes you up) - No job offers until the tents stand and everything is unloaded - Dev menu: time of day, game speed, weather, skip the start
build.sh | 2 +- commit_message.txt | 9 +++ entropie/ARCHITECTURE.md | 2 +- entropie/Makefile | 2 +- entropie/README.md | 17 +++++- entropie/STATE.md | 11 +++- entropie/assets/shaders/scene.frag | 4 ++ entropie/src/game/game.c | 20 +++++++ entropie/src/game/game.h | 1 + entropie/src/game/game_arrival.c | 62 +++++++++++++++++--- entropie/src/game/game_dev.c | 38 ++++++++++++- entropie/src/game/game_internal.h | 1 + entropie/src/people/ai.c | 40 ++++++++++++- entropie/src/people/ai.h | 3 + entropie/src/people/hands.c | 1 + entropie/src/render/renderer.c | 2 + entropie/src/render/renderer.h | 2 + entropie/src/render/shapes.h | 6 +- entropie/src/render/shapes_props.c | 45 ++++++++++++--- entropie/src/world/car.c | 23 ++++++-- entropie/src/world/car.h | 9 ++- entropie/src/world/object.c | 30 +++++++--- entropie/src/world/object.h | 4 ++ entropie/src/world/weather.c | 114 +++++++++++++++++++++++++++++++++++++ entropie/src/world/weather.h | 32 +++++++++++ entropie/src/world/world.c | 12 +++- entropie/src/world/world.h | 6 ++ 27 files changed, 452 insertions(+), 46 deletions(-) diff --git a/build.sh b/build.sh index 914c409..c062845 100644 --- a/build.sh +++ b/build.sh @@ -1 +1 @@ -cd .. && rm -rf entropie; tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat entropie/commit_message.txt)")" && cd entropie && make clean && make && cat commit_message.txt +cd .. && rm -rf entropie; mv $SHARED_CLAUDE/entropie/commit_message.txt . && tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat commit_message.txt)")" && cd entropie && make clean && make && cat commit_message.txt diff --git a/commit_message.txt b/commit_message.txt new file mode 100644 index 0000000..cc6fd6e --- /dev/null +++ b/commit_message.txt @@ -0,0 +1,9 @@ +entropie 2.9.0: rain, soda and Mate crates, dev menu time/weather + +- Weather: dry spells and showers; drops round the camera, grey sky, + wetter look, real water on the ground, fires burn down faster +- People shelter: Kurti and sons into the house, the others into the + garage kitchen; rain makes you colder +- Two more crates in the boot: soda (cola and orange) and Mate (wakes you up) +- No job offers until the tents stand and everything is unloaded +- Dev menu: time of day, game speed, weather, skip the start diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index e4ab575..f196f39 100644 --- a/entropie/ARCHITECTURE.md +++ b/entropie/ARCHITECTURE.md @@ -10,7 +10,7 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL | `src/core/` | window, GL loader (`gl.h`: every GL call is listed there), input | core | | `src/math/` | vectors, matrices, frustum, yaw conventions (`linalg.h`) | math | | `src/render/` | renderer and its passes, shaders' uniforms, meshes, procedural shapes (`shapes*.c`), figures, camera, sky, UI | core, math, render | -| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, car, water, grass, grid, day | everything but game | +| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, car, water, weather, grass, grid, day | everything but game | | `src/people/` | bodies (animation), hands, AI, talk, condition, dog, climbing | everything but game | | `src/game/` | the frame: input to actions, player, camera follow, talk and dev UI, the arrival by car at the start | all | diff --git a/entropie/Makefile b/entropie/Makefile index 0fd9a8b..93ca4b0 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.8.0 +VERSION := 2.9.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index b6bcb35..67bb8ef 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -33,14 +33,25 @@ Painted in the rotoscope style of *Waking Life* (2001): - Then the work: the boot opens, the others unload. Crates go into the kitchen in your garages, the camping things up to the meadow behind them. Help: left click at the boot takes out the next thing (crates: both hands) -- In the boot: 2 crates of beer, 2 of water, everybody's sleeping bag and - mat, the big tent (Volkram and Jana) and two small ones (Toni, you) +- In the boot: 2 crates of beer, 2 of water, one of soda (cola and orange), + one of Mate, everybody's sleeping bag and mat, the big tent (Volkram and Jana) and two small ones (Toni, you) - A crate is heavy: carrying it tires you (the weaker sooner). Worn out, you put it down for a moment; or two carry it (F to hold on, E to lift together). The others put theirs down for a breather too - Crates: left click takes a bottle out (the others help themselves too), E / Q carries the whole crate - The hint line says what's to do next +- Nobody asks you for other jobs (wood and so on) until the tents stand and + everything is unloaded + +## Weather + +- Dry spells of hours, then showers now and then (the first few hours stay dry) +- Rain falls round you, the sky greys over, everything looks wetter; the + ground gets real water (puddles, mud), a fire burns down faster +- People get out of it: Kurti and his sons into the house, the others into + your garages' kitchen; out again when it stops +- Out in the rain you get cold sooner (pullover) ## Tents @@ -682,7 +693,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 | +| 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 | | 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 fe921b5..774138f 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -208,6 +208,13 @@ Round 2.8: - On a break (`ai_work.c` putToolsDown): the orderly (stack care over 0.5) lay their tools down side by side, square, a step back; the others drop them any old way. An axe or shears put down or come to rest lies on its side (`Hands_drop`; `physics.c` settleOnGround tips a blade-up one over). - Default things thinned out: no bottles or vodka lying about, 4 pieces of litter in the yard (was 26), 2 in each open garage (was 12). +Round 2.9: +- Settled first (`Ai_setSettled`, from `game.c`): nobody comes up asking for jobs (`maybeApproach`) until the arrival's work is done (boot empty, tents up). Kurti and his sons never ask. +- Two more crates (`car.c` CARGO_SODA / CARGO_MATE; crate variant from weight: 0 beer, 1 water, 2 soda, 3 mate): soda 12 x 1.2 kg (cola and orange taking turns, `BOTTLE_COLA` / `BOTTLE_ORANGE`), Mate 20 x 0.85 kg (`BOTTLE_MATE`). `Object_setDrink`: per style quench / refresh / nourish / `wake` (Mate 0.04, cola 0.01 off sleepiness a sip, `hands.c`). Boot: Mate, soda, water x2, beer x2 under the tents, mats, bags. +- Dev menu (`game_dev.c`): time of day (Left / Right: an hour), game speed (1, 3, 10, 30, 60, 120x), weather (on its own, dry, a shower, pouring), skip the start (`game_skipArrival`: parked, out, welcome and chat done, everything unloaded, the tents up). +- Weather (`world/weather.c`): `world.rain` eases (RAIN_EASE 150 s) to `rain_goal`: dry FIRST_DRY 3 h, then dry spells 2-8 h and showers 12-52 min (0.25..1). `world.wet` soaks in 25 min of hard rain, dries in 3 h (3x at night). 7 mm an hour poured onto the ground within 16 m (`Wetness_pour`, 8 splashes a frame, not under a roof: `Weather_covered`, a ray up 14 m). Fires lose 0.004 kg wood a second x rain. Drops (1400 x rain) within 9 m of the camera, 8.5 m/s, a little wind; `Shapes_rain` two crossed two-sided streaks each; none under a roof. Light (`game.c` rainLight): sun x (1 - 0.45 rain), the sky to grey, shadows x (1 - 0.85 rain); `rainWet` in scene.frag: the ground damper, the rest darker. +- Shelter (`ai.c` shelter, from rain 0.3, out in it: `rain_out` in world.c): crates and gear down, Kurti and his sons into the house (`indoors`, kept while rain > 0.1), the others into your garages' kitchen (at `Site_drinksSpot`). Rain makes you colder outside; a hint the first time. + 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). @@ -356,7 +363,7 @@ Most important first. - "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: taking them down again; wind blowing an unpegged one about; people busy (work) when sleepy go to bed late -- Rain: showers; people go into their tents (the house is Kurti's), or under the porch / into your garages +- Rain, still to do: people sheltering in their tents; thunder; wet clothes - Main building: "the left building's roof peak is too far left" - to be clarified **Tools for us** @@ -366,4 +373,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.8.0 (build 56bae88)` +State of `entropie 2.9.0 (build 56bba45)` diff --git a/entropie/assets/shaders/scene.frag b/entropie/assets/shaders/scene.frag index 3ba5a6d..83ff6c9 100644 --- a/entropie/assets/shaders/scene.frag +++ b/entropie/assets/shaders/scene.frag @@ -21,6 +21,7 @@ uniform float boilTime; uniform float terrainSize; uniform float decalSize; uniform float grass; +uniform float rainWet; // 0..1 everything wet from rain: darker, the ground damp uniform sampler2D shadowMap; uniform sampler2D waterMap; uniform sampler2D surfaceMap; // asphalt, concrete, gravel, bare earth (Site_surface) @@ -108,6 +109,9 @@ void main() { vec4 some = step(vec4(0.004), wet); // dry ground stays exactly as it was float puddle = smoothstep(0.9, 1.4, depth + edge * 1.2 * some.g); float damp = smoothstep(0.06, 0.3, wet.r + edge * 0.15 * some.r) * (0.55 + 0.45 * wet.r); + bool ground = terrainSize > 0.0 || grass > 0.0 || (decalSize > 0.0 && n.y > 0.7); + damp = max(damp, rainWet * (ground ? 0.8 : 0.0)); + if (!ground) albedo *= 1.0 - 0.18 * rainWet; // (walls, roofs, people: a little darker wet) float mud = smoothstep(0.15, 0.35, wet.a + edge * 0.25 * some.a) * (0.6 + 0.4 * wet.a); float stain = smoothstep(0.1, 0.4, wet.b + edge * 0.2 * some.b); albedo *= mix(1.0, 0.58, damp); diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 0cff241..344a407 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -273,6 +273,19 @@ static void puttingUp(Game *game) { Hands_holdEdge(p, grip, across, 0.0f); } +/* Rain: the light duller, the sky grey, no sharp shadows; everything wet. */ +static void rainLight(Game *game) { + Lighting *l = &game->daycycle.light; + float r = game->world.rain; + const Vec3 grey = { 0.56f, 0.58f, 0.62f }; + float bright = 0.35f + 0.65f * l->day_factor; + l->light_color = vec3_scale(l->light_color, 1.0f - 0.45f * r); + 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; + l->wet = game->world.wet; +} + /* Worn out with something heavy in your arms (a crate): down it goes for a moment. */ static void tooHeavy(Game *game) { Object *p = game->player; @@ -287,6 +300,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game->fast_sleep = pb->asleep && pb->pose == POSE_DOWN && input->jump; const float speed = game->fast_sleep ? SLEEP_FAST_FORWARD : 1.0f; DayCycle_update(&game->daycycle, dt * speed); + rainLight(game); { /* worn out: shown until a bit rested again */ float stamina = game->player->needs.stamina; @@ -302,7 +316,13 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { puttingUp(game); game_climbLadder(game, input, dt); game_climb(game, input, dt); + game->world.eye = game->camera.position; + if (game->world.rain > 0.3f && !game->rain_told) { + 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; game_arrival(game, dt); + Ai_setSettled(game->arrival.task == TASK_NONE); tooHeavy(game); bool warm = game->player->look.top == TOP_JACKET; World_update(&game->world, dt, dt * game->daycycle.time_scale * speed); diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 8f0858b..0c133f6 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -75,6 +75,7 @@ typedef struct { Arrival arrival; /* the start: in by car */ struct Object *tent; /* the tent you're working on (left button held) */ float dt; /* this frame's step */ + bool rain_told; /* told it's raining (this shower) */ } Game; /* A line of speech above someone's head, in screen pixels (y up from the bottom). */ diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c index 5091f7d..9cf16b9 100644 --- a/entropie/src/game/game_arrival.c +++ b/entropie/src/game/game_arrival.c @@ -43,6 +43,7 @@ static Object *person(Game *game, const char *name) { static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" }; static void chat(Game *game, float dt); +static void settled(Game *game); /* While you're still to say hello and have a chat: Kurti and his sons stay at the fire. */ static void hostsStay(Game *game) { @@ -78,6 +79,8 @@ void game_arrivalStart(Game *game) { Object *friends[3]; for (int k = 0; k < 3; k++) friends[k] = person(game, FRIENDS[k]); /* the boot, loaded last first: the camping things on top, the crates under them */ + Car_load(car, CARGO_MATE, NULL); + Car_load(car, CARGO_SODA, NULL); Car_load(car, CARGO_WATER, NULL); Car_load(car, CARGO_WATER, NULL); Car_load(car, CARGO_BEER, NULL); @@ -150,6 +153,7 @@ void game_arrival(Game *game, float dt) { hostsStay(game); welcome(game, dt); chat(game, dt); + settled(game); if (a->stage == ARRIVAL_NONE || a->stage == ARRIVAL_DONE || !car) return; a->time += dt; if (a->stage == ARRIVAL_DRIVING) { @@ -240,16 +244,20 @@ static void chat(Game *game, float dt) { } /* What to do now (the hint line when nothing else is said there), or NULL. */ -const char *game_task(Game *game) { +/* The car empty and all the tents up: settled in. */ +static void settled(Game *game) { Arrival *a = &game->arrival; - if (a->task == TASK_UNLOAD) { /* done once the boot is empty and all the tents stand */ - bool done = a->car && a->car->load && a->car->load->n == 0; - for (int i = 0; i < game->world.count && done; i++) { - const Object *o = game->world.objects[i]; - if ((o->type == OBJ_TENT && o->tent_stage < TENT_UP) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken)) done = false; - } - if (done) a->task = TASK_NONE; + if (a->task != TASK_UNLOAD) return; + bool done = a->car && a->car->load && a->car->load->n == 0; + for (int i = 0; i < game->world.count && done; i++) { + const Object *o = game->world.objects[i]; + if ((o->type == OBJ_TENT && o->tent_stage < TENT_UP) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken)) done = false; } + if (done) a->task = TASK_NONE; +} + +const char *game_task(Game *game) { + Arrival *a = &game->arrival; switch (a->task) { case TASK_HELLO: return "To do: say hello to Kurti and his sons at the fire"; case TASK_CHAT: return "To do: sit down with them (C) and have a chat"; @@ -258,6 +266,44 @@ const char *game_task(Game *game) { } } +/* Dev: the start skipped - parked, everybody out, hello and chat said, the car unloaded and the tents up. */ +void game_skipArrival(Game *game) { + Arrival *a = &game->arrival; + if (!a->car) return; + Car_place(&game->world, a->car, a->path[ARRIVAL_SAMPLES - 1], a->park_yaw); + for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->riding) Car_getOut(game->world.objects[i]); + a->stage = ARRIVAL_DONE; + a->step = 4; + a->greeted = true; + a->welcome = 99; + a->chat = 99; + game->third_person = true; + Car_openBoot(a->car, true); + Object *drop[CAR_LOAD_MAX]; + int n = 0; + while (a->car->load && a->car->load->n > 0 && n < CAR_LOAD_MAX) { + Object *it = Car_takeOut(&game->world, a->car); + if (!it) break; + drop[n++] = it; + } + for (int k = 0; k < n; k++) { /* (each where it goes; the tents up there) */ + Object *it = drop[k]; + Vec3 at = Car_dropSpot(&game->world, it); + it->position = vec3(at.x, World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f) + 0.3f, at.z); + Object_syncColliders(it); + if (it->type == OBJ_GEAR && it->variant >= GEAR_TENT_SMALL) { + Object *tent = Tent_unpack(&game->world, it, Car_campDoor()); + while (tent && tent->tent_stage < TENT_UP) Tent_work(tent, game->player, 1.0f); + } + } + for (int k = 0; k < 3; k++) { + Object *o = person(game, FRIENDS[k]); + if (o) { o->ai.greet = false; o->ai.unload = false; o->sitting = false; } + } + a->task = TASK_NONE; + say(game, "Dev: the start skipped"); +} + bool game_arriving(const Game *game) { return game->arrival.stage == ARRIVAL_DRIVING || (game->arrival.stage == ARRIVAL_PARKED && game->player->riding); } diff --git a/entropie/src/game/game_dev.c b/entropie/src/game/game_dev.c index 0cb7d6d..ddcc009 100644 --- a/entropie/src/game/game_dev.c +++ b/entropie/src/game/game_dev.c @@ -7,9 +7,10 @@ #include <stdio.h> #include "game/game_internal.h" +#include "world/weather.h" typedef enum { - DEV_WHO, DEV_TELEPORT, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY, + DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, 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 ... */ @@ -90,6 +91,28 @@ void game_dev(Game *game, const Input *input) { teleport(game, dir); return; } + if (game->dev_row == DEV_TIME) { /* an hour on or back */ + float day = 24.0f * 3600.0f; + game->daycycle.seconds = fmodf(game->daycycle.seconds + (float)dir * 3600.0f + day, day); + return; + } + if (game->dev_row == DEV_SPEED) { + static const float SPEEDS[] = { 1.0f, 3.0f, 10.0f, 30.0f, 60.0f, 120.0f }; + int k = 0; + for (int i = 0; i < 6; i++) if (fabsf(game->daycycle.time_scale - SPEEDS[i]) < fabsf(game->daycycle.time_scale - SPEEDS[k])) k = i; + k = k + dir < 0 ? 0 : k + dir > 5 ? 5 : k + dir; + game->daycycle.time_scale = SPEEDS[k]; + return; + } + if (game->dev_row == DEV_WEATHER) { + game->world.weather = (game->world.weather + dir + WEATHER_KINDS) % WEATHER_KINDS; + if (game->world.weather == WEATHER_AUTO) game->world.rain_left = 0.0f; /* (on its own again: from now) */ + return; + } + if (game->dev_row == DEV_START) { + if (dir > 0) game_skipArrival(game); + return; + } Object *o = picked(game); if (!o) return; Needs *n = &o->needs; @@ -129,7 +152,7 @@ void game_dev(Game *game, const Input *input) { } /* One line of the menu. */ -static void devLine(const Object *o, int row, char *out) { +static void devLine(const Game *game, const Object *o, int row, char *out) { const Needs *n = &o->needs; const Character *c = &o->ai.character; if (row >= DEV_LIKES && row < DEV_SKILLS) { @@ -143,6 +166,15 @@ static void devLine(const Object *o, int row, char *out) { switch ((DevRow)row) { case DEV_WHO: snprintf(out, 96, "Who: %s%s", o->name, o->type == OBJ_PLAYER ? " (you)" : ""); break; case DEV_TELEPORT: snprintf(out, 96, "Teleport you: <- yard parkour ->"); break; + case DEV_TIME: { + int m = (int)(game->daycycle.seconds / 60.0f); + snprintf(out, 96, "Time: %02d:%02d (an hour back / on)", (m / 60) % 24, m % 60); + break; + } + 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_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; case DEV_SPORTY: snprintf(out, 96, "Sporty: %.2f", o->fitness); break; @@ -175,7 +207,7 @@ int Game_devLines(const Game *game, char lines[][96], int max, int *chosen) { snprintf(lines[0], 96, "Dev (F1 closes; Up / Down: line %d/%d, Left / Right: change)", game->dev_row + 1, DEV_ROWS); int n = 1; for (int row = first; row < DEV_ROWS && n < max; row++) { - devLine(o, row, lines[n]); + devLine(game, o, row, lines[n]); if (row == game->dev_row) *chosen = n; n++; } diff --git a/entropie/src/game/game_internal.h b/entropie/src/game/game_internal.h index d5b1825..be9bda5 100644 --- a/entropie/src/game/game_internal.h +++ b/entropie/src/game/game_internal.h @@ -27,6 +27,7 @@ void game_arrival(Game *game, float dt); bool game_arriving(const Game *game); bool game_unloadCar(Game *game); const char *game_task(Game *game); +void game_skipArrival(Game *game); void game_callHelp(Game *game); void game_callOver(Game *game); diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 47b4fac..27b06ca 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -497,7 +497,7 @@ static Object *findConsumable(const Object *self, Object *const *objects, int co bool crate = o->type == OBJ_CRATE; /* (a crate: a bottle out of it) */ bool fits = want == WANT_FOOD ? Object_isFood(o) : Object_isBottle(o); if (want == WANT_BEER && o->alcohol <= 0.0f) fits = false; - if (crate) fits = want != WANT_FOOD && o->stored > 0 && (want != WANT_BEER || o->variant == 0); + if (crate) fits = want != WANT_FOOD && o->stored > 0 && (want != WANT_BEER || o->variant == 0); /* (thirsty: water, soda or mate too) */ if (!fits || o->is_held || (!crate && o->sips <= 0) || o->broken) continue; if (ai_skipped(&self->ai, o)) continue; /* couldn't get at it */ bool taken = false; @@ -1134,8 +1134,12 @@ static void updateApproach(Object *self, Object *const *objects, int count, floa /* Now and then, depending on who they are: offer to take over the player's wood work, or suggest a job that needs doing and that they like. */ +static bool g_settled = true; /* (just arrived: no jobs offered till the car is empty and the tents stand) */ +void Ai_setSettled(bool settled) { g_settled = settled; } + static bool maybeApproach(Object *self, Object *const *objects, int count, const Object *player, float dt) { Ai *ai = &self->ai; + if (!g_settled || self->lives_here) return false; /* (settling in first; the owners don't do your jobs) */ if (!player || ai->talk_cooldown > 0.0f || Body_isBusy(&player->body) || ai->annoyed > 0.3f) return false; /* (annoyed: not now) */ const Mood *m = &self->temperament; const Character *c = &ai->character; @@ -1910,6 +1914,37 @@ static bool goInside(Object *self, Object *const *objects, int count, float dt) return true; } +/* Rain (out in it): into the house till it's over (people camp in tents, + but sit out a shower inside). */ +#define SHELTER_RAIN 0.3f + +static bool shelter(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (!g_world || g_world->rain < SHELTER_RAIN || self->indoors > 0.0f || self->in_tent || self->body.asleep || self->riding) { + if (!g_world || g_world->rain < SHELTER_RAIN * 0.5f) ai->sheltering = false; + return false; + } + if (!ai->sheltering) { + if (!self->rain_out) return false; + ai->sheltering = true; + if (random01(ai) < 0.6f) Ai_say(self, random01(ai) < 0.5f ? "It's pouring - inside, quick!" : self->lives_here ? "Rain! Into the house." : "Rain! Into the garages."); + for (int h = 0; h < 2; h++) { /* (crates and gear down where they are; tools along) */ + const Object *it = self->hands.item[h]; + if (it && (it->type == OBJ_CRATE || it->type == OBJ_GEAR)) Hands_drop(self, h); + } + } + if (self->sitting) self->sitting = false; + if (!self->lives_here) { /* (the house is Kurti's: you lot wait it out in your garages' kitchen) */ + Vec3 spot = vec3_add(Site_drinksSpot(), vec3(0.7f * ai->side, 0.0f, 0.7f * ai->side)); + if (goToPoint(self, spot, 0.7f, objects, count, dt)) rest(ai, 3.0f, 6.0f); + return true; + } + if (!goToPoint(self, Site_houseDoor(), 0.6f, objects, count, dt)) return true; + self->indoors = 30.0f; /* (kept while it rains: see world.c) */ + rest(ai, 1.0f, 2.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) { @@ -2078,6 +2113,9 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_HELP) && ai_holdLadder(self, objects, count, dt)) return; /* Done with a tool: careful people put it back where it belongs, the others leave it lying */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return; + /* 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; /* Unloading the car */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && unloadCar(self, objects, count, dt)) return; /* Just arrived: hello at the fire */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index e67c949..e68f421 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -177,6 +177,7 @@ typedef struct { float smokes_time; /* ... on the way this long */ bool asked_you; /* asked the player for one (nothing came of it: the machine next time) */ 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 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) ... */ @@ -286,6 +287,8 @@ struct World; void Ai_useGrid(struct Grid *grid); /* The world (for climbing: ladders, edges). */ void Ai_useWorld(struct World *world); +/* Settled in (the car empty, the tents up): from now on they come to you with jobs (firewood, trees...). */ +void Ai_setSettled(bool settled); /* Before physics: chooses what to do and sets movement and facing. */ void Ai_update(struct Object *self, struct Object *const *objects, int count, float dt); diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 1bff9f5..9396297 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -637,6 +637,7 @@ static void advanceDrink(Object *who, float dt) { n->thirst = fmaxf(0.0f, n->thirst - bottle->quench); n->hunger = fmaxf(0.0f, n->hunger - bottle->nourish); n->stamina = fminf(1.0f, n->stamina + bottle->refresh); + n->sleepiness = fmaxf(0.0f, n->sleepiness - bottle->wake); n->bladder = fminf(1.0f, n->bladder + (bottle->quench * 0.25f + bottle->alcohol * 0.3f) / Object_bladder(who)); } } diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index a12b05b..5b13af6 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -37,6 +37,7 @@ static bool loadShaders(Renderer *r) { s->fire_light = glGetUniformLocation(s->program, "fireLight"); s->tint = glGetUniformLocation(s->program, "tint"); s->grass = glGetUniformLocation(s->program, "grass"); + s->rain_wet = glGetUniformLocation(s->program, "rainWet"); s->shadow_map = glGetUniformLocation(s->program, "shadowMap"); s->terrain_size = glGetUniformLocation(s->program, "terrainSize"); s->grass_depth = glGetUniformLocation(s->program, "grassDepth"); @@ -340,6 +341,7 @@ void Renderer_beginScenePass(Renderer *r, const Camera *camera, const Lighting * setVec3(s->sky_top, light->sky_top); setVec3(s->sky_horizon, light->sky_horizon); glUniform1f(s->shadow_strength, light->shadow_strength); + glUniform1f(s->rain_wet, light->wet); glUniform4f(s->fire_light, light->fire_position.x, light->fire_position.y, light->fire_position.z, light->fire_strength); glUniform1f(s->terrain_size, 0.0f); /* 0 = regular object */ diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index 849b00b..734bcb9 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -37,6 +37,7 @@ typedef struct { float day_factor; /* 0 = night, 1 = day */ Vec3 fire_position; /* a campfire's light ... */ float fire_strength; /* ... and how bright it is (0 = none) */ + float wet; /* 0..1 everything wet from rain (darker, the ground damp) */ } Lighting; typedef struct { @@ -48,6 +49,7 @@ typedef struct { GLint decal_size; /* things lying on the ground: sink with it */ GLint tint; /* color multiplier and ember glow */ GLint grass; /* grass blades: terrain size while drawing them */ + GLint rain_wet; /* everything wet from rain (0..1) */ GLint holes, hole_size, hole_count; /* holes in the ground (basements): not drawn there */ } SceneShader; diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index dadc590..8539427 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -64,7 +64,7 @@ typedef struct { float door_x; } BuildingSize; -typedef enum { BOTTLE_BEER, BOTTLE_SPIRITS, BOTTLE_WATER } BottleStyle; +typedef enum { BOTTLE_BEER, BOTTLE_SPIRITS, BOTTLE_WATER, BOTTLE_COLA, BOTTLE_ORANGE, BOTTLE_MATE } BottleStyle; /* Bottle centered on the origin: glass, label, neck and cap. Brown beer bottle, clear with a red cap for spirits, light blue for water. */ @@ -133,7 +133,9 @@ Mesh Shapes_ivy(float width, float height, unsigned seed); 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); -/* A crate of bottles (centred): variant 0 beer, 1 water; this many bottles in it. */ +/* Rain: a thin light streak at each drop (world space), len long. */ +Mesh Shapes_rain(const Vec3 *drops, int n, float len); +/* 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 #define CRATE_SIZE_Z 0.30f diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 0ba242c..086ea63 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -27,13 +27,16 @@ Mesh Shapes_bottle(float diameter, float height, BottleStyle style) { const float base = -height * 0.5f; const float body_h = height * (spirits ? 0.66f : 0.58f), shoulder_h = height * (spirits ? 0.08f : 0.14f); const float neck_h = height * (spirits ? 0.20f : 0.24f); - const Vec3 glass = water ? COLOR_WATER : (spirits ? COLOR_CLEAR : COLOR_GLASS); - const Vec3 cap = water ? COLOR_BLUE : (spirits ? COLOR_RED : COLOR_CAP); + Vec3 glass = water ? COLOR_WATER : (spirits ? COLOR_CLEAR : COLOR_GLASS); + Vec3 cap = water ? COLOR_BLUE : (spirits ? COLOR_RED : COLOR_CAP), label = COLOR_LABEL; + if (style == BOTTLE_COLA) { glass = (Vec3){ 0.20f, 0.11f, 0.07f }; cap = COLOR_RED; label = (Vec3){ 0.78f, 0.12f, 0.10f }; } + if (style == BOTTLE_ORANGE) { glass = (Vec3){ 0.96f, 0.56f, 0.14f }; cap = COLOR_ORANGE; label = (Vec3){ 0.20f, 0.36f, 0.72f }; } + if (style == BOTTLE_MATE) { glass = (Vec3){ 0.55f, 0.33f, 0.10f }; cap = COLOR_BLUE; label = (Vec3){ 0.93f, 0.84f, 0.50f }; } MeshBuilder b; MeshBuilder_init(&b); shapes_addTube(&b, base, r, r, body_h * 0.25f, seg, glass, true); /* bottom */ - shapes_addTube(&b, base + body_h * 0.25f, r * 1.02f, r * 1.02f, body_h * 0.5f, seg, COLOR_LABEL, false); + shapes_addTube(&b, base + body_h * 0.25f, r * 1.02f, r * 1.02f, body_h * 0.5f, seg, label, false); shapes_addTube(&b, base + body_h * 0.75f, r, r, body_h * 0.25f, seg, glass, false); shapes_addTube(&b, base + body_h, r, neck, shoulder_h, seg, glass, false); /* shoulder */ shapes_addTube(&b, base + body_h + shoulder_h, neck, neck, neck_h, seg, glass, false); @@ -951,15 +954,40 @@ Mesh Shapes_carInside(Vec3 color) { return Mesh_fromBuilder(&b); } +Mesh Shapes_rain(const Vec3 *drops, int n, float len) { + 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 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); + GLuint i2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), side), vec3(0, 0, 1), color); + GLuint i3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), side), vec3(0, 0, 1), color); + MeshBuilder_quad(&b, i0, i1, i2, i3); + MeshBuilder_quad(&b, i3, i2, i1, i0); /* (seen from both sides) */ + GLuint j0 = MeshBuilder_vertex(&b, vec3_sub(p, across), vec3(1, 0, 0), color); + GLuint j1 = MeshBuilder_vertex(&b, vec3_add(p, across), vec3(1, 0, 0), color); + GLuint j2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), across), vec3(1, 0, 0), color); + GLuint j3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), across), vec3(1, 0, 0), color); + MeshBuilder_quad(&b, j0, j1, j2, j3); + MeshBuilder_quad(&b, j3, j2, j1, j0); + } + return Mesh_fromBuilder(&b); +} + /* A crate of bottles, centred: beer (brown bottles, gold caps, a red crate) or water (green glass, blue caps, a blue crate); the first `bottles` slots full. */ Mesh Shapes_crate(int variant, int bottles) { MeshBuilder b; MeshBuilder_init(&b); const float x = CRATE_SIZE_X, h = CRATE_SIZE_Y, z = CRATE_SIZE_Z, wall = 0.015f; - const Vec3 crate = variant == 0 ? (Vec3){ 0.62f, 0.16f, 0.12f } : (Vec3){ 0.22f, 0.42f, 0.72f }; - const Vec3 glass = variant == 0 ? (Vec3){ 0.38f, 0.22f, 0.08f } : (Vec3){ 0.55f, 0.75f, 0.62f }; - const Vec3 cap = variant == 0 ? (Vec3){ 0.85f, 0.70f, 0.25f } : (Vec3){ 0.20f, 0.35f, 0.75f }; + static const Vec3 CRATE[4] = { { 0.62f, 0.16f, 0.12f }, { 0.22f, 0.42f, 0.72f }, { 0.20f, 0.48f, 0.28f }, { 0.90f, 0.62f, 0.15f } }; + static const Vec3 GLASS[4] = { { 0.38f, 0.22f, 0.08f }, { 0.55f, 0.75f, 0.62f }, { 0.20f, 0.11f, 0.07f }, { 0.55f, 0.33f, 0.10f } }; + static const Vec3 CAP[4] = { { 0.85f, 0.70f, 0.25f }, { 0.20f, 0.35f, 0.75f }, { 0.78f, 0.12f, 0.10f }, { 0.20f, 0.35f, 0.75f } }; + variant = variant < 0 ? 0 : variant > 3 ? 3 : variant; + const Vec3 crate = CRATE[variant]; const Vec3 hole = vec3_scale(crate, 0.45f); addBlock(&b, vec3(0.0f, -h * 0.5f + 0.01f, 0.0f), vec3(x, 0.02f, z), crate); addBlock(&b, vec3(0.0f, 0.0f, z * 0.5f - wall * 0.5f), vec3(x, h, wall), crate); @@ -968,8 +996,11 @@ Mesh Shapes_crate(int variant, int bottles) { addBlock(&b, vec3(-x * 0.5f + wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate); for (int s = -1; s <= 1; s += 2) /* the handle holes in the ends */ addBlock(&b, vec3((float)s * (x * 0.5f + 0.001f), h * 0.32f, 0.0f), vec3(0.004f, 0.035f, 0.11f), hole); - const int cols = variant == 0 ? 5 : 4, rows = variant == 0 ? 4 : 3; + const bool big = variant == 1 || variant == 2; /* (water and soda: 12 big bottles; beer and mate: 20 small) */ + const int cols = big ? 4 : 5, rows = big ? 3 : 4; for (int k = 0; k < bottles && k < cols * rows; k++) { + Vec3 glass = GLASS[variant], cap = CAP[variant]; + if (variant == 2 && k % 2) { glass = (Vec3){ 0.96f, 0.56f, 0.14f }; cap = (Vec3){ 0.95f, 0.50f, 0.15f }; } /* (soda: cola and orange) */ float cx = -x * 0.5f + x * ((float)(k % cols) + 0.5f) / (float)cols, cz = -z * 0.5f + z * ((float)(k / cols) + 0.5f) / (float)rows; size_t first = b.vertex_count; shapes_addTube(&b, h * 0.5f - 0.07f, 0.024f, 0.012f, 0.1f, 6, glass, false); /* the neck out of the top */ diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c index 07a1e2a..4082104 100644 --- a/entropie/src/world/car.c +++ b/entropie/src/world/car.c @@ -44,8 +44,19 @@ bool Car_load(Object *car, CargoKind kind, Object *owner) { static const char *const CARGO_NAME[] = { [CARGO_BEER] = "a crate of beer", [CARGO_WATER] = "a crate of water", [CARGO_SLEEPING_BAG] = "a sleeping bag", [CARGO_MAT] = "a sleeping mat", [CARGO_TENT_SMALL] = "a tent", [CARGO_TENT_BIG] = "the big tent", + [CARGO_SODA] = "a crate of soda", [CARGO_MATE] = "a crate of mate", }; +int Crate_bottles(int variant) { return variant == 1 || variant == 2 ? 12 : 20; } +float Crate_bottleKg(int variant) { return variant == 1 || variant == 2 ? 1.2f : 0.85f; } +const char *Crate_name(int variant) { + static const char *const NAME[] = { "beer", "water", "soda", "mate" }; + return NAME[variant & 3]; +} + +static bool isCrate(CargoKind k) { return k == CARGO_BEER || k == CARGO_WATER || k == CARGO_SODA || k == CARGO_MATE; } +static int crateOf(CargoKind k) { return k == CARGO_WATER ? 1 : k == CARGO_SODA ? 2 : k == CARGO_MATE ? 3 : 0; } + const char *Car_nextName(const Object *car) { const CarLoad *l = car->load; return l && l->n > 0 ? CARGO_NAME[l->kind[l->n - 1]] : NULL; @@ -53,7 +64,7 @@ const char *Car_nextName(const Object *car) { bool Car_nextHeavy(const Object *car) { const CarLoad *l = car->load; - return l && l->n > 0 && (l->kind[l->n - 1] == CARGO_BEER || l->kind[l->n - 1] == CARGO_WATER || l->kind[l->n - 1] == CARGO_TENT_BIG); + return l && l->n > 0 && (isCrate(l->kind[l->n - 1]) || l->kind[l->n - 1] == CARGO_TENT_BIG); } /* Everybody's own colours for their things (by name: you, Volkram, Jana, Toni, anybody else). */ @@ -85,8 +96,8 @@ Object *Car_takeOut(World *world, Object *car) { Vec3 at = local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - SET_DOWN)); at.y = World_groundHeight(world, at.x, at.z, at.y + 1.0f) + 0.02f; Object *item; - if (kind == CARGO_BEER || kind == CARGO_WATER) { - item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), kind == CARGO_WATER ? 1.0f : 0.0f); + if (isCrate(kind)) { + item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), (float)crateOf(kind)); } else { GearKind g = kind == CARGO_SLEEPING_BAG ? GEAR_SLEEPING_BAG : kind == CARGO_MAT ? GEAR_MAT : kind == CARGO_TENT_SMALL ? GEAR_TENT_SMALL : GEAR_TENT_BIG; @@ -205,9 +216,11 @@ Object *Crate_takeBottle(World *world, Object *crate) { if (crate->type != OBJ_CRATE || crate->stored <= 0) return NULL; Vec3 top = vec3_add(Object_colliderCenter(crate, 0), vec3(0.0f, CRATE_SIZE_Y * 0.5f + 0.16f, 0.0f)); Object *b = Object_create(crate->variant ? OBJ_WATER : OBJ_BOTTLE, PALETTE_WARM, top, CRATE_BOTTLE, BOTTLE_KG); - if (!b || !World_add(world, b)) return NULL; + if (!b) return NULL; + if (crate->variant >= 2) Object_setDrink(b, crate->variant == 3 ? 3 : 1 + ((crate->stored + 1) % 2)); /* (soda: cola and orange in turn) */ + if (!World_add(world, b)) return NULL; crate->stored--; - crate->weight = fmaxf(2.0f, crate->weight - (crate->variant ? 1.2f : 0.85f)); + crate->weight = fmaxf(2.0f, crate->weight - Crate_bottleKg(crate->variant)); return b; } diff --git a/entropie/src/world/car.h b/entropie/src/world/car.h index 83a6ac6..c106c79 100644 --- a/entropie/src/world/car.h +++ b/entropie/src/world/car.h @@ -12,11 +12,14 @@ #include "world/world.h" -#define CRATE_BEER 20 /* bottles in a crate of beer */ -#define CRATE_WATER 12 /* ... of water */ typedef enum { GEAR_SLEEPING_BAG, GEAR_MAT, GEAR_TENT_SMALL, GEAR_TENT_BIG } GearKind; -typedef enum { CARGO_BEER, CARGO_WATER, CARGO_SLEEPING_BAG, CARGO_MAT, CARGO_TENT_SMALL, CARGO_TENT_BIG } CargoKind; +typedef enum { CARGO_BEER, CARGO_WATER, CARGO_SLEEPING_BAG, CARGO_MAT, CARGO_TENT_SMALL, CARGO_TENT_BIG, CARGO_SODA, CARGO_MATE } CargoKind; + +/* Crates: 0 beer, 1 water, 2 soda (cola and orange), 3 mate - how many bottles, how heavy each, its name. */ +int Crate_bottles(int variant); +float Crate_bottleKg(int variant); +const char *Crate_name(int variant); #define CAR_LOAD_MAX 20 typedef struct CarLoad { diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index f87de98..f40f8f2 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -363,10 +363,10 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size } case OBJ_CRATE: - obj->variant = weight > 0.5f ? 1 : 0; - obj->stored = obj->variant ? CRATE_WATER : CRATE_BEER; + obj->variant = (int)(weight + 0.5f) < 0 ? 0 : (int)(weight + 0.5f) > 3 ? 3 : (int)(weight + 0.5f); + obj->stored = Crate_bottles(obj->variant); obj->size = vec3(CRATE_SIZE_X, CRATE_SIZE_Y, CRATE_SIZE_Z); - obj->weight = 2.0f + (float)obj->stored * (obj->variant ? 1.2f : 0.85f); + obj->weight = 2.0f + (float)obj->stored * Crate_bottleKg(obj->variant); obj->mesh = Shapes_crate(obj->variant, obj->stored); obj->shown = obj->stored; obj->pivot = vec3(0.0f, CRATE_SIZE_Y * 0.5f, 0.0f); @@ -1190,8 +1190,22 @@ float Object_bottom(const Object *obj) { return Object_colliderCenter(obj, 0).y - extent; } -static const char *bottleName(ObjectType type) { - return type == OBJ_VODKA ? "Vodka" : type == OBJ_WATER ? "Water" : "Beer"; +static const char *bottleName(const Object *b) { + static const char *const DRINK[] = { "Water", "Cola", "Orange soda", "Mate" }; + return b->type == OBJ_VODKA ? "Vodka" : b->type == OBJ_WATER ? DRINK[b->variant & 3] : "Beer"; +} + +void Object_setDrink(Object *b, int drink) { + static const BottleStyle STYLE[] = { BOTTLE_WATER, BOTTLE_COLA, BOTTLE_ORANGE, BOTTLE_MATE }; + if (b->type != OBJ_WATER) return; + drink &= 3; + b->variant = drink; + Mesh_destroy(&b->mesh); + b->mesh = Shapes_bottle(2.0f * b->colliders[0].half_extents.x, 2.0f * b->colliders[0].half_extents.y, STYLE[drink]); + b->quench = drink == 0 ? 0.2f : 0.15f; + b->refresh = drink == 0 ? 0.15f : drink == 3 ? 0.12f : 0.1f; /* (sugar, or the mate's kick) */ + b->nourish = drink == 1 || drink == 2 ? 0.02f : 0.0f; + b->wake = drink == 3 ? 0.04f : drink == 1 ? 0.01f : 0.0f; /* (caffeine: less sleepy) */ } bool Object_isHard(const Object *o) { @@ -1211,8 +1225,8 @@ void Object_describe(const Object *obj, char *out, size_t size) { case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_WATER: - if (obj->sips > 0) snprintf(out, size, "%s - %d sip%s left", bottleName(obj->type), obj->sips, obj->sips == 1 ? "" : "s"); - else snprintf(out, size, "%s - empty", bottleName(obj->type)); + if (obj->sips > 0) snprintf(out, size, "%s - %d sip%s left", bottleName(obj), obj->sips, obj->sips == 1 ? "" : "s"); + else snprintf(out, size, "%s - empty", bottleName(obj)); break; case OBJ_APPLE: case OBJ_BREAD: @@ -1276,7 +1290,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { else snprintf(out, size, "Your car - left click at the boot: take out %s", Car_nextName(obj)); break; case OBJ_CRATE: - snprintf(out, size, "Crate of %s - %d bottle%s left%s", obj->variant ? "water" : "beer", obj->stored, obj->stored == 1 ? "" : "s", + snprintf(out, size, "Crate of %s - %d bottle%s left%s", Crate_name(obj->variant), obj->stored, obj->stored == 1 ? "" : "s", obj->stored > 0 && !obj->is_held ? " (left click: take one)" : ""); break; case OBJ_TENT: { diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 4c93460..6da5790 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -162,6 +162,8 @@ typedef struct Object { bool boot_open; /* cars: the boot lid up */ Mesh inside; /* cars: what you see of it from the driver's seat */ bool keep; /* trees: may not be cut down (round the meadow behind your garages) */ + float wake; /* drinks: sleepiness off per sip (mate, cola) */ + bool rain_out; /* characters: out in the rain (no roof over them) */ bool lives_here; /* characters: the house is theirs (Kurti and his sons): in and out of it */ float indoors; /* characters: inside the house this many more seconds (hidden: see ai.c goInside) */ struct Object *owner; /* camping gear, tents: whose ... */ @@ -509,6 +511,8 @@ void Object_describe(const Object *obj, char *out, size_t size); /* Advances a fall started by Object_topple. */ void Object_advanceTopple(Object *obj, float dt); +/* A bottle of water (OBJ_WATER) made a soft drink: 0 water, 1 cola, 2 orange soda, 3 mate. */ +void Object_setDrink(Object *b, int drink); /* Gone (eaten): no mesh, no collision. */ void Object_useUp(Object *obj); /* Bottles: turns into shards lying on the ground at ground_y. */ diff --git a/entropie/src/world/weather.c b/entropie/src/world/weather.c new file mode 100644 index 0000000..a546f6c --- /dev/null +++ b/entropie/src/world/weather.c @@ -0,0 +1,114 @@ +#include "world/weather.h" +#include "world/wetness.h" +#include "render/shapes.h" + +#include <stdlib.h> + +#define FIRST_DRY (3.0f * 3600.0f) /* game s: dry at first (you arrive, the tents go up) */ +#define DRY_MIN (2.0f * 3600.0f) /* a dry spell: this long ... */ +#define DRY_MORE (6.0f * 3600.0f) /* ... up to this much longer */ +#define SHOWER_MIN (12.0f * 60.0f) /* a shower: this long ... */ +#define SHOWER_MORE (40.0f * 60.0f) +#define RAIN_EASE 150.0f /* game s: it sets in / eases off over about this */ +#define SOAK_TIME (25.0f * 60.0f) /* game s of hard rain: everything soaked */ +#define DRY_TIME (3.0f * 3600.0f) /* ... and dry again (in the day; slower at night) */ +#define RAIN_MM_HOUR 7.0f /* mm an hour in hard rain */ +#define POUR_RADIUS 16.0f /* m round you the ground gets the water (puddles, mud) */ +#define POURS 8 /* ... in this many splashes a frame */ +#define DROP_RADIUS 9.0f /* m round the camera the drops fall */ +#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 */ + +static float dice(unsigned *rng) { + *rng = *rng * 1103515245u + 12345u; + return (float)((*rng >> 8) & 0xFFFFu) / 65536.0f; +} + +void Weather_init(World *world) { + world->rain = world->rain_goal = 0.0f; + world->rain_left = FIRST_DRY; + world->wet = 0.0f; + world->weather = WEATHER_AUTO; + world->drops = calloc(1, sizeof(*world->drops)); + if (world->drops) world->drops->rng = 0x5eed1234u; +} + +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]; +} + +bool Weather_covered(const World *world, Vec3 p) { + return World_rayCast(world, vec3_add(p, vec3(0.0f, 0.3f, 0.0f)), vec3(0.0f, 1.0f, 0.0f), 14.0f, NULL, NULL, NULL, NULL) >= 0.0f; +} + +static void respawn(World *world, int k, bool anywhere) { + RainDrops *d = world->drops; + float a = dice(&d->rng) * 2.0f * PI_F, r = DROP_RADIUS * sqrtf(dice(&d->rng)); + Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r)); + float ground = world->has_terrain ? Terrain_height(&world->terrain, p.x, p.z) : 0.0f; + p.y = anywhere ? ground + 7.0f * dice(&d->rng) : fmaxf(world->eye.y, ground) + 4.0f + 3.0f * dice(&d->rng); + d->hidden[k] = Weather_covered(world, vec3(p.x, fminf(world->eye.y, ground + 1.0f), p.z)); + d->p[k] = p; +} + +void Weather_update(World *world, float dt, float game_dt) { + /* where it's going: forced (dev), or showers and dry spells taking turns */ + if (world->weather == WEATHER_DRY) world->rain_goal = 0.0f; + else if (world->weather == WEATHER_SHOWER) world->rain_goal = 0.4f; + else if (world->weather == WEATHER_RAIN) world->rain_goal = 1.0f; + else if ((world->rain_left -= game_dt) <= 0.0f) { + unsigned *rng = world->drops ? &world->drops->rng : &world->rng; + if (world->rain_goal > 0.0f) { + world->rain_goal = 0.0f; + world->rain_left = DRY_MIN + DRY_MORE * dice(rng); + } else { + world->rain_goal = 0.25f + 0.75f * dice(rng); + world->rain_left = SHOWER_MIN + SHOWER_MORE * dice(rng); + } + } + world->rain += (world->rain_goal - world->rain) * fminf(1.0f, game_dt / RAIN_EASE); + 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); + if (world->rain <= 0.0f || !world->drops) return; + + /* the ground near you gets the water: puddles, mud (not under a roof) */ + RainDrops *d = world->drops; + float liters = RAIN_MM_HOUR * world->rain / 3600.0f * game_dt * PI_F * POUR_RADIUS * POUR_RADIUS / (float)POURS; + for (int k = 0; k < POURS && world->has_terrain; k++) { + float a = dice(&d->rng) * 2.0f * PI_F, r = POUR_RADIUS * sqrtf(dice(&d->rng)); + Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r)); + p.y = Terrain_height(&world->terrain, p.x, p.z); + if (!Weather_covered(world, p)) Wetness_pour(&world->terrain, p.x, p.z, liters, 2.2f, 0.0f); + } + /* a fire burns down faster in it */ + for (int i = 0; i < world->count; i++) { + Object *f = world->objects[i]; + if (f->type == OBJ_FIREPLACE && f->burning) f->fuel = fmaxf(0.0f, f->fuel - FIRE_DOUSE * world->rain * game_dt); + } + /* the drops round the camera */ + int n = (int)(RAIN_DROPS * world->rain); + for (int k = 0; k < n; k++) { + Vec3 *p = &d->p[k]; + 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) */ + 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); + } +} + +void Weather_draw(World *world, Renderer *renderer) { + if (world->rain <= 0.01f || !world->drops) return; + 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 (n == 0) return; + Mesh mesh = Shapes_rain(shown, n, DROP_LEN); + 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 new file mode 100644 index 0000000..a29601c --- /dev/null +++ b/entropie/src/world/weather.h @@ -0,0 +1,32 @@ +#ifndef WORLD_WEATHER_H +#define WORLD_WEATHER_H + +/* + * Weather: dry spells and showers (on their own, or set in the dev menu). + * While it rains, drops fall round the camera (none under a roof), the + * ground near you gets wet (puddles, mud: see wetness.c), everything looks + * darker and wetter, the sky greys over, a fire burns down faster. People go + * in out of it (ai.c shelter). + */ + +#include "world/world.h" + +typedef enum { WEATHER_AUTO, WEATHER_DRY, WEATHER_SHOWER, WEATHER_RAIN, WEATHER_KINDS } WeatherKind; + +#define RAIN_DROPS 1400 + +typedef struct RainDrops { + Vec3 p[RAIN_DROPS]; + bool hidden[RAIN_DROPS]; /* (under a roof: not drawn) */ + unsigned rng; +} RainDrops; + +void Weather_init(World *world); +/* dt real, game_dt game seconds. */ +void Weather_update(World *world, float dt, float game_dt); +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); + +#endif /* WORLD_WEATHER_H */ diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 903b2aa..9e9bee6 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -3,6 +3,7 @@ #include "world/clothes.h" #include "world/car.h" #include "world/tent.h" +#include "world/weather.h" #include "people/pockets.h" #include "people/condition.h" #include "people/dog.h" @@ -734,6 +735,10 @@ static void dressForNight(World *world, Object *o, float dt) { bool night = world->hour >= 21.0f || world->hour < 7.0f; bool by_fire = Camp_fireNear(world->objects, world->count, o->position, 4.0f, true) != NULL; float cold = !night ? -2.0f : by_fire || (o->in_tent && Tent_hasBed(o->in_tent, o)) ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f); + 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) */ 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)) { @@ -1136,6 +1141,7 @@ void World_init(World *world) { memset(world, 0, sizeof(*world)); world->rng = 0x2545F491u; world->hour = 12.0f; + Weather_init(world); } void World_destroy(World *world) { @@ -1148,6 +1154,7 @@ void World_destroy(World *world) { Grid_free(&world->grid); Mesh_destroy(&world->hose_piece); Mesh_destroy(&world->hose_tape); + free(world->drops); memset(world, 0, sizeof(*world)); } @@ -1232,6 +1239,7 @@ void World_update(World *world, float dt, float game_dt) { Object_indexKinds(world->objects, world->count); world->dt_props = dt; Clothes_update(world); + Weather_update(world, dt, game_dt); Car_serve(world); Tent_update(world, dt); showProps(world); /* (who's a person, where the fires are: looked up again and again) */ @@ -1251,12 +1259,13 @@ void World_update(World *world, float dt, float game_dt) { Condition_useGrid(&world->grid); Condition_update(o, world->objects, world->count, dt, game_dt); if (o->indoors > 0.0f) { /* in the house: out of sight a while, then out at the door */ + if (o->ai.sheltering && world->rain > 0.1f) o->indoors = fmaxf(o->indoors, 25.0f + 10.0f * (float)(i % 4)); /* (till the rain's over) */ o->indoors -= dt; Vec3 door = Site_houseDoor(); o->position = vec3(door.x, Terrain_height(&world->terrain, door.x, door.z), door.z); o->velocity = o->movement = vec3_zero(); Object_syncColliders(o); - if (o->indoors <= 0.0f) { o->indoors = 0.0f; o->body.replant = true; } + if (o->indoors <= 0.0f) { o->indoors = 0.0f; o->body.replant = true; o->ai.sheltering = false; } continue; } if (o->type == OBJ_HUMAN && !o->riding) { Ai_useGrid(&world->grid); Ai_useWorld(world); Ai_update(o, world->objects, world->count, dt); } @@ -1503,4 +1512,5 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { } } Chips_draw(&world->chips, renderer); + Weather_draw(world, renderer); } diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index 37ea5ef..deb62ca 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -28,6 +28,12 @@ typedef struct World { Chips chips; /* wood chips and sawdust flying off work */ Grid grid; /* where things are, by ground cell (built each frame) */ float hour; /* time of day (set by the game) */ + float rain; /* 0..1 how hard it rains now (see weather.h) ... */ + 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) */ + Vec3 eye; /* where the camera is (set by the game: the rain falls round it) */ + struct RainDrops *drops; } World; void World_init(World *world);