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 1.89.0: the coat rack, a full tin, a real ramp
- A coat rack in your kitchen with everybody's jacket: getting cold, people walk over and put theirs on; yours: click the rack (on, or back on it) - The ashtray tin shows its butts, filling up to a little heap - The tilted test box that hung in the air at one end is now a plank ramp resting on a block - you can walk up it - Fix: walking onto anything tilted (a ramp) was blocked: the step up took its highest corner instead of the surface in front of you
entropie/Makefile | 2 +- entropie/README.md | 12 +++++--- entropie/STATE.md | 10 ++++-- entropie/commit_message.txt | 18 +++++------ entropie/src/game/game.c | 6 +++- entropie/src/game/game_actions.c | 18 ++++++++++- entropie/src/people/ai.c | 25 +++++++++++++++ entropie/src/people/ai.h | 2 ++ entropie/src/render/shapes.h | 6 +++- entropie/src/render/shapes_props.c | 62 ++++++++++++++++++++++++++++++++------ entropie/src/world/object.c | 2 ++ entropie/src/world/object.h | 3 ++ entropie/src/world/physics.c | 33 +++++++++++++++++++- entropie/src/world/scene.c | 45 ++++++++++++++++++++++++++- entropie/src/world/scene.h | 2 ++ entropie/src/world/site.c | 1 + entropie/src/world/world.c | 51 ++++++++++++++++++++++++++++--- 17 files changed, 261 insertions(+), 37 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index d7907f0..b935137 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.88.0 +VERSION := 1.89.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 7be479d..a5dd0cb 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -27,6 +27,7 @@ Painted in the rotoscope style of *Waking Life* (2001): The map is Homburgshöhe at Geflügelhof Oderland, traced from the real map and satellite pictures (about 340 x 340 m). +- A plank ramp in the yard, its top end on a block: walk up it - An obstacle course in the chicken farm yard: three lanes of logs, crates, benches, walls and blocks, spaced and close together. (Testing: set `START_AT_COURSE` in `src/game/game.c` to 1 to start there) @@ -37,7 +38,7 @@ and satellite pictures (about 340 x 340 m). Floors are 2.8 m high - Your garages have three rooms: tools next to the main building (workbench, tool wall, shelf, axe, shears, chainsaw and a ladder), a kitchen (counter, stove, - table and chairs; hold food over the stove to cook it) and a car repair + table and chairs, the coat rack; hold food over the stove to cook it) and a car repair place with an old car behind the wide door - Alice the dachshund lives here: she trots after you, sniffs around, begs for sausages, comes when called (R), lies by the fire and loves being petted (click) @@ -161,7 +162,7 @@ and satellite pictures (about 340 x 340 m). - The others do it too: a long log across the way they step onto and walk on (and wobble), shorter things they walk round - Off the edge of something you go once your middle is past it - no standing - on air beside it; slopes (a roof, a tilted box) no longer show invisible steps + on air beside it; slopes (a roof, the ramp) no longer show invisible steps - The sporty fit their steps to what's coming, so a foot lands at a good take-off spot - Jogging or faster, low things (up to about the knee) are jumped on their @@ -455,7 +456,9 @@ say no more often and don't come to you for a bit. - Working hard in the midday sun the shirt comes off (women keep a top on; the shy keep it all on), yours too; it goes back on when cooled down - Cold nights: away from the fire a jacket goes on (each their own colour), - by the fire or in the day it comes off + by the fire or in the day it comes off (and stays with them) +- Everybody's jacket starts on the coat rack in your kitchen: getting cold, + they walk over and put it on. Yours too: click the rack (on / back on it) - Ask them what they're doing, send them back to helping you, or give them a break ## Smoking @@ -468,7 +471,8 @@ say no more often and don't come to you for a bit. - A drag now and then (the tip glows), smoke breathed out, the hand up in front between drags. Hands needed: it goes into the mouth - Done: flicked away or dropped as a butt (into the fire when close; the - tidy and you use the old tin on the cool box by the fire as ashtray) + tidy and you use the old tin on the cool box by the fire as ashtray; you see + it fill up) - A full ashtray gets emptied by somebody tidy (into a scrap of paper, off to the bin) - K: light one (yours, or somebody next to you gives you a light), K again diff --git a/entropie/STATE.md b/entropie/STATE.md index 84a85f3..3b2e291 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -77,6 +77,10 @@ Smooth lines (`ink.frag`): edgeAt looked at in 4 spots inside the pixel (rotated NPCs onto things (1.88): the onto-step (up to STEP_OVER_HEIGHT x 1.15) and walking onto flat things work for OBJ_HUMAN too; `ai.c` longToStepOnto: standable loose things 1.5 m+ long and up to ONTO_HIGH 0.55 m are not in the way for steering (`Physics_standsOn`), so people step onto a log across their way instead of walking round; a step off says "Whoa!" sometimes. Edges (`physics.c` middleOverAny): a character lands on something only with its middle over one of its colliders (OVER_EDGE 0.12 m past an edge at most); else it slides off away from it (the body box's corner used to hold it in the air). `world.c` clutterQuery: a tilted collider (no axis within 0.98 of upright) counts with its surface at the foot (rays down there and towards it), not its highest corner (invisible steps on roofs and tilted boxes). +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); the coat rack (FURN_COATRACK, in your kitchen by the doors, garage coords 20.6 / 2.4) shows the jackets hanging on it (`Shapes_coatRack`, up to 6, a bitmask of whose in `shown`). `jacket_hung` (characters, true at the start): cold (chill 0.6+) and the jacket with them: on it goes (as before); hanging: NPCs `ai.want_jacket` -> `ai.c` fetchJacket (idle / wander: goTo the rack, on with it, "Ah, better."; gives up after 90 s or warm again); you are told once ("your jacket hangs on the rack in your garage") and click the rack (`game_actions.c` coatRack: on, or back onto it). Warm again: it comes off and stays with them. + +The ramp (`scene.c` addRamp via Scene_finish, after the terrain): a static plank 1.2 x 0.15 x 3.2 m, 20 degrees up, its foot on the ground, its top on a block (the loose tilted test box hung in the air at one end: loose things never turn in the physics). Tilted things in the physics (1.89): `tryStepUp` takes the surface just ahead of you (STEP_PROBE 0.3 m, rayDownInto), not the highest corner; the footprints (middleOver, clutterQuery) use the whole box's outline seen from above (a tilted plank's thickness had counted as narrow). + Tiredness (`object.c` Object_tiredness: sleepiness from 0.25 up x (1 - 0.35 energy) x (1.2 - 0.4 fitness) + 0.4 drunk): the stamina ceiling TIRED_CAP 0.55 lower, effort TIRED_EFFORT 0.6 harder, rest (awake) TIRED_REST 0.5 slower (`condition.c`), Object_vigor 0.25 lower (speed, jumps, work). Balancing (`world.c` -> `body.balance_goal` from `Physics_footing`: 1 at a top 0.3 m wide or less, 0 from 0.75 m; `body.c` updateBalance): `teeter` is a sprung wobble (3.2 rad/s, damping 0.35) kicked at random (TEETER_KICK 40 x (0.25 + 0.6 clumsy + 1.6 drunk + 0.35 speed + 0.4 nerves) x (1.25 - 0.75 fitness)); past 1: Body_stagger sideways (`stepped_off` -> "Whoa - you lose your balance and step off"). Pose: arms out to the sides (the one on the tipping side lower), the hips with the wobble, the upper body against it; walking BALANCE_SLOW 0.55 slower. @@ -212,9 +216,9 @@ Most important first. **People** - The work place, still to do: other jobs than split / cut (felling room, trash); walking a heavy piece round tight corners - Ladders, still to do: a holder for a ladder standing free; NPCs taking a ladder somewhere for their own work -- Smoking, still to do: buying more (a kiosk); the butts showing in the tin (the mesh always has 3) +- Smoking, still to do: buying more (a kiosk) - "No shirt no service" policy (decision open) -- Clothing, still to do: more outfits for extra people (they all wear trousers); fetching the jacket from somewhere +- Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers); hanging the jacket back on the rack **Tools for us** - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback @@ -223,4 +227,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 1.88.0 (build d1db253)` +State of `entropie 1.89.0 (build c1f9348)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 22763be..f64dcfc 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,11 +1,9 @@ -entropie 1.88.0: the others step onto logs, no invisible steps, tired days +entropie 1.89.0: the coat rack, a full tin, a real ramp -- The others step onto a long log across their way and walk on (wobbling), - shorter things they still walk round -- Fix: an invisible step next to things - the edge of the body held you up - in the air beside it; now you go down once your middle is past the edge -- Fix: invisible steps on slopes (a roof, a tilted box): the feet took the - highest corner of it instead of the surface right there -- Tired: the later the day, the less energy - a lower ceiling, used up - quicker, back slower, all a bit weaker; lively and sporty people hold up - better, drink makes it worse +- A coat rack in your kitchen with everybody's jacket: getting cold, people + walk over and put theirs on; yours: click the rack (on, or back on it) +- The ashtray tin shows its butts, filling up to a little heap +- The tilted test box that hung in the air at one end is now a plank ramp + resting on a block - you can walk up it +- Fix: walking onto anything tilted (a ramp) was blocked: the step up took + its highest corner instead of the surface in front of you diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 520548c..6f15418 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -186,7 +186,7 @@ bool Game_init(Game *game) { Camera_init(&game->camera, vec3(0.0f, 1.8f, 5.0f), -90.0f, 0.0f, CAMERA_FOV); game->player = Scene_load(&game->world); - if (!game->player || !World_buildTerrain(&game->world) || !Site_finish(&game->world)) { + if (!game->player || !World_buildTerrain(&game->world) || !Site_finish(&game->world) || !Scene_finish(&game->world)) { SDL_Log("Scene loading failed"); Game_shutdown(game); return false; @@ -249,6 +249,10 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game_climbLadder(game, input, dt); game_climb(game, input, dt); World_update(&game->world, dt, dt * game->daycycle.time_scale * speed); + if (game->player->chill > 0.6f && game->player->jacket_hung && game->player->look.top != TOP_JACKET && !game->player->jacket_told) { + game->player->jacket_told = true; + say(game, "Brr, it's getting cold - your jacket hangs on the rack in your garage"); + } if (game->player->body.stepped_off) { game->player->body.stepped_off = false; say(game, "Whoa - you lose your balance and step off"); diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index e90e0cd..4f7864c 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -427,6 +427,22 @@ static Object *furnitureNear(const Game *game, FurnitureKind kind, float reach) return NULL; } +/* At the coat rack: your jacket off it and on, or off and onto it. */ +static bool coatRack(Game *game) { + Object *p = game->player; + if (!furnitureNear(game, FURN_COATRACK, 0.9f)) return false; + if (p->jacket_hung) { + p->jacket_hung = false; + p->look.top = TOP_JACKET; + say(game, "You put your jacket on"); + } else { + p->jacket_hung = true; + if (p->look.top == TOP_JACKET) p->look.top = TOP_SHIRT; + say(game, "You hang your jacket on the rack"); + } + return true; +} + /* The hose or the watering can in a hand, or NULL. */ static Object *waterInHand(const Object *p) { for (int h = 0; h < 2; h++) @@ -677,7 +693,7 @@ static int leftClick(Game *game) { Talk_showHere(&game->world, p, shown); return CLICK_NONE; } - if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game)) return CLICK_NONE; + if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || coatRack(game)) return CLICK_NONE; int watering = useWater(game); if (watering >= 0) return watering; if (bagTrash(game)) return CLICK_BAG; diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 344412c..7ca8393 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1302,6 +1302,29 @@ static bool stepAside(Object *self, float dt) { return true; } +/* Cold, and the jacket hangs on the coat rack: there, and on with it. */ +#define JACKET_GIVE_UP 90.0f /* s on the way: forget it */ + +static bool fetchJacket(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (!ai->want_jacket) return false; + if (!self->jacket_hung || self->look.top == TOP_JACKET || (ai->jacket_time += dt) > JACKET_GIVE_UP || self->chill < 0.3f) { + ai->want_jacket = false; + return false; + } + Object *rack = NULL; + for (int i = 0; i < count && !rack; i++) + if (objects[i]->type == OBJ_FURNITURE && objects[i]->variant == FURN_COATRACK) rack = objects[i]; + if (!rack) { ai->want_jacket = false; return false; } + if (!goTo(self, rack, objects, count, 0.6f, dt)) return true; + self->jacket_hung = false; + self->look.top = TOP_JACKET; + ai->want_jacket = false; + if (random01(ai) < 0.5f) Ai_say(self, "Ah, better."); + rest(ai, 1.0f, 2.0f); + return true; +} + void Ai_update(Object *self, Object *const *objects, int count, float dt) { ai_ensureReady(self); Ai *ai = &self->ai; @@ -1358,6 +1381,8 @@ 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; + /* Cold, the jacket on the rack: off to get it */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && fetchJacket(self, objects, count, dt)) return; /* Tidy people pick things up on the way */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || (ai->mode == AI_HELP && !ai->pushing && !ai->copied) || ai->mode == AI_SUPPLY || ai->mode == AI_FETCH) && ai_tidyUp(self, objects, count, dt)) diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index 008c50a..48c2780 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -168,6 +168,8 @@ typedef struct { bool tool_asked; /* asked the player for their axe (once per order) */ float tidy_pause; /* bending down for trash on the way (seconds left) */ float tray_time; /* on the way to empty the ashtray this long */ + bool want_jacket; /* cold, the jacket on the rack: off to get it */ + float jacket_time; /* ... on the way this long */ bool tool_waiting; /* waiting for the player to put the axe down */ unsigned char banned; /* tools (1 << ToolKind) they were told to leave alone ... */ unsigned char defies; /* ... and those they use anyway ("I know what I'm doing") */ diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index 1a736d8..db0f8c1 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -105,13 +105,17 @@ Mesh Shapes_grill(void); typedef enum { FURN_WORKBENCH, FURN_TOOLWALL, FURN_SHELF, FURN_STOVE, FURN_COUNTER, FURN_TABLE, - FURN_CHAIR, FURN_BENCH, FURN_CAR, FURN_TIRES, FURN_TOILET, FURN_TAP, FURN_ASHTRAY, FURN_COUNT + FURN_CHAIR, FURN_BENCH, FURN_CAR, FURN_TIRES, FURN_TOILET, FURN_TAP, FURN_ASHTRAY, FURN_COATRACK, FURN_COUNT } FurnitureKind; #define SEAT_HEIGHT 0.45f /* chairs and log benches */ /* Furniture standing on y = 0, size = its box (length along X, height, depth along Z). Fronts face +Z. */ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size); +/* The ashtray with this many butts in it (a few show, a heap when full). */ +Mesh Shapes_ashtray(Vec3 size, int butts); +/* The coat rack with these jackets hanging on it (n at most 6). */ +Mesh Shapes_coatRack(Vec3 size, const Vec3 *jackets, int n); /* A dachshund, its nose towards -Z, standing on y = 0: body (with head and ears), one leg (hanging down from its top), the tail (from its root, along +Z). */ diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index ecc2b71..513e99c 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -680,6 +680,54 @@ static void addWheel(MeshBuilder *b, Vec3 center, float radius, float width) { transformSince(b, first, mat4_mul(mat4_translate(center), mat4_rotate_z(PI_F / 2.0f))); } +/* An old tin can as ashtray: the butts stick out of the ash, more and more, + a little heap of them when it's full. */ +static void buildAshtray(MeshBuilder *b, Vec3 size, int butts) { + const Vec3 tin = { 0.62f, 0.62f, 0.60f }, filter = { 0.86f, 0.6f, 0.34f }, ash = { 0.25f, 0.24f, 0.22f }; + shapes_addTube(b, 0.0f, size.x * 0.5f, size.x * 0.48f, size.y, 10, tin, true); + float fill = fminf((float)butts / 14.0f, 1.0f); + addBlock(b, vec3(0.0f, size.y * (0.7f + 0.28f * fill), 0.0f), vec3(size.x * 0.8f, 0.006f, size.x * 0.8f), ash); + int shown = butts < 12 ? butts : 12; + for (int k = 0; k < shown; k++) { + float a = (float)k * 2.39996f, r = 0.012f + 0.022f * sqrtf((float)k / 12.0f); + float up = size.y * (0.72f + 0.28f * fill) + 0.006f + 0.008f * (float)(k / 6); /* (full: piled up) */ + addBlock(b, vec3(r * cosf(a), up, r * sinf(a)), vec3(0.018f, 0.012f, 0.011f), filter); + } +} + +/* A coat rack: a post on a cross foot, hooks at the top; jackets hang on it + (boxes in their colours, sleeves down). */ +static void buildCoatRack(MeshBuilder *b, Vec3 size, const Vec3 *jackets, int n) { + const Vec3 wood = { 0.40f, 0.27f, 0.16f }; + addBlock(b, vec3(0.0f, 0.02f, 0.0f), vec3(size.x, 0.04f, 0.06f), wood); + addBlock(b, vec3(0.0f, 0.02f, 0.0f), vec3(0.06f, 0.04f, size.z), wood); + addBlock(b, vec3(0.0f, size.y * 0.5f, 0.0f), vec3(0.05f, size.y, 0.05f), wood); + for (int k = 0; k < 6; k++) { + float a = (float)k * PI_F / 3.0f; + addBlock(b, vec3(0.06f * cosf(a), size.y - 0.08f, 0.06f * sinf(a)), vec3(0.08f, 0.02f, 0.02f), COLOR_IRON); + } + for (int k = 0; k < n && k < 6; k++) { + float a = (float)k * PI_F / 3.0f + 0.3f; + Vec3 at = vec3(0.13f * cosf(a), size.y - 0.42f, 0.13f * sinf(a)); + addBlock(b, at, vec3(0.16f, 0.62f, 0.12f), jackets[k]); /* the body */ + addBlock(b, vec3_add(at, vec3(0.0f, -0.05f, 0.0f)), vec3(0.2f, 0.4f, 0.06f), vec3_scale(jackets[k], 0.8f)); /* sleeves */ + } +} + +Mesh Shapes_ashtray(Vec3 size, int butts) { + MeshBuilder b; + MeshBuilder_init(&b); + buildAshtray(&b, size, butts); + return Mesh_fromBuilder(&b); +} + +Mesh Shapes_coatRack(Vec3 size, const Vec3 *jackets, int n) { + MeshBuilder b; + MeshBuilder_init(&b); + buildCoatRack(&b, size, jackets, n); + return Mesh_fromBuilder(&b); +} + Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) { MeshBuilder b; MeshBuilder_init(&b); @@ -785,16 +833,12 @@ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) { transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.4f, -hz + 0.1f)), mat4_rotate_x(PI_F / 2.0f))); break; } - case FURN_ASHTRAY: { /* an old tin can by the fire, a few butts sticking out */ - const Vec3 tin = { 0.62f, 0.62f, 0.60f }, filter = { 0.86f, 0.6f, 0.34f }; - shapes_addTube(&b, 0.0f, size.x * 0.5f, size.x * 0.48f, size.y, 10, tin, true); - addBlock(&b, vec3(0.0f, size.y * 0.98f, 0.0f), vec3(size.x * 0.8f, 0.006f, size.x * 0.8f), (Vec3){ 0.25f, 0.24f, 0.22f }); /* ash */ - for (int k = 0; k < 3; k++) { - float a = (float)k * 2.1f; - addBlock(&b, vec3(0.02f * cosf(a), size.y + 0.008f, 0.02f * sinf(a)), vec3(0.018f, 0.012f, 0.011f), filter); - } + case FURN_ASHTRAY: + buildAshtray(&b, size, 3); + break; + case FURN_COATRACK: + buildCoatRack(&b, size, NULL, 0); break; - } case FURN_COUNT: break; } diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index c69406a..416eeb8 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -236,6 +236,7 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size case OBJ_FURNITURE: obj->variant = (int)weight; /* the kind, see Object_createFurniture */ + obj->shown = -1; obj->weight = 100.0f; obj->mesh = Shapes_furniture((FurnitureKind)obj->variant, size); obj->is_static = true; @@ -1021,6 +1022,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { [FURN_TOILET] = "Toilet - P to use it", [FURN_TAP] = "Tap - the garden hose; fill the watering can here", }; if (obj->variant == FURN_ASHTRAY) { snprintf(out, size, "Ashtray (an old tin) - %d butts in it", obj->stored); break; } + if (obj->variant == FURN_COATRACK) { snprintf(out, size, "Coat rack - everybody's jacket hangs here (click: yours on or off)"); break; } snprintf(out, size, "%s", obj->variant >= 0 && obj->variant < FURN_COUNT && WHAT[obj->variant] ? WHAT[obj->variant] : "Furniture"); break; } diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index fca2c2f..a6e7283 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -249,6 +249,9 @@ typedef struct Object { float raised, raise_goal; /* ladders carried slid out: held near the foot, this steep up in front (radians, 0 = carried at the side) */ bool dipped; /* ... tipped down flat to get under something (a beam, a doorway) */ bool just_caught; /* ladders: fell against something just now and rests there (the game says so) */ + bool jacket_hung; /* characters: the jacket hangs on the coat rack (else it's with them, worn or not) */ + bool jacket_told; /* the player: told where the jacket is this cold spell */ + int shown; /* furniture: what its mesh shows (butts in the tin, jackets on the rack); -1: not built */ bool leaning; /* ladders: leaning against something (stands still) */ float air_fall; /* characters: in the air, falling this fast at most (m/s; for the landing) */ float ladder_skill; /* characters: practice climbing ladders one-handed (0..1) ... */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index 4b99f6b..240c9ab 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -217,6 +217,28 @@ static void slideAlong(Object *obj, Vec3 normal, float depth) { * Characters walking into a low, flat-topped obstacle step onto it. * Returns true if the step was taken. */ +/* Straight down from origin into the collider: how far to its surface (-1: misses it). */ +#define STEP_PROBE 0.3f /* m ahead where a step lands */ + +static float rayDownInto(Vec3 origin, const Object *o, int ic) { + const Collider *c = &o->colliders[ic]; + Vec3 rel = vec3_sub(origin, Object_colliderCenter(o, ic)); + const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z }; + float t0 = 0.0f, t1 = 1e9f; + for (int k = 0; k < 3; k++) { + float p = vec3_dot(rel, c->axes[k]), d = -c->axes[k].y; /* (the ray goes down) */ + if (fabsf(d) < 1e-6f) { + if (fabsf(p) > half[k]) return -1.0f; + continue; + } + float a = (-half[k] - p) / d, b = (half[k] - p) / d; + if (a > b) { float x = a; a = b; b = x; } + t0 = fmaxf(t0, a); + t1 = fminf(t1, b); + } + return t0 <= t1 ? t0 : -1.0f; +} + static bool tryStepUp(Object *obj, int io, const Object *other, int ic, Vec3 normal, float most) { if (!obj->is_character) return false; /* Only walking into it, not brushing past along its side */ @@ -227,6 +249,13 @@ static bool tryStepUp(Object *obj, int io, const Object *other, int ic, Vec3 nor if (c->axes[1].y < WALKABLE_SLOPE) return false; /* top face not walkable */ float top = Object_colliderCenter(other, ic).y + projectRadius(c, WORLD_UP); + if (fmaxf(fabsf(c->axes[0].y), fmaxf(fabsf(c->axes[1].y), fabsf(c->axes[2].y))) < 0.98f) { + /* tilted (a ramp): its surface just in front of you, not its highest corner */ + Vec3 ahead = vec3_add(obj->position, vec3_scale(vec3_normalize(mv), STEP_PROBE)); + float t = rayDownInto(vec3(ahead.x, top + 0.5f, ahead.z), other, ic); + if (t < 0.0f) return false; + top = top + 0.5f - t; + } float bottom = Object_colliderCenter(obj, io).y - projectRadius(&obj->colliders[io], WORLD_UP); float rise = top - bottom; if (rise <= 0.0f || rise > most) return false; @@ -347,8 +376,10 @@ static bool middleOver(const Object *o, int ic, Vec3 p, float margin) { Vec3 axis = vec3(c->axes[a].x, 0.0f, c->axes[a].z); float len = vec3_length(axis); if (len < 0.3f) continue; /* (an upright axis) */ - if (fabsf(vec3_dot(rel, vec3_scale(axis, 1.0f / len))) > half[a] * len + margin) return false; + Vec3 dir = vec3_scale(axis, 1.0f / len); + if (fabsf(vec3_dot(rel, dir)) > projectRadius(c, dir) + margin) return false; /* (its whole outline seen from above) */ } + (void)half; return true; } diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index 120ca46..3723a6a 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -52,7 +52,6 @@ static const Spawn SCENE[] = { /* Pushable objects */ { OBJ_CYLINDER, PALETTE_WARM, { -3, 0, 1 }, { 1, 3, 1 }, 1200, { 90, 0, 0 } }, /* takes two or more */ { OBJ_CYLINDER, PALETTE_MODERN, { -3, 0, 3 }, { 1, 3, 1 }, 100, NO_ROTATION }, - { OBJ_BOX, PALETTE_MODERN, { 2, 2, 8 }, { 1, 1, 3 }, 100, { 20, 0, 0 } }, { OBJ_BOX, PALETTE_MODERN, { 1, 0, 2 }, { 2, 1, 2 }, 20, NO_ROTATION }, /* (the tools are in the tool room of your garages, see site.c) */ @@ -117,6 +116,49 @@ static const Character CHARACTERS[] = { 0.75f, 2.4f, BREAK_SIT, 50.0f, 40.0f }, /* slow, ponders, sits down a lot */ }; +/* A ramp to walk up: a plank 20 degrees up, its foot on the ground, its top + resting on a block (a loose box left tilted would hang in the air: loose + things don't turn in the physics). */ +#define RAMP_AT { 2.0f, 0.0f, 8.0f } +#define RAMP_SIZE { 1.2f, 0.15f, 3.2f } +#define RAMP_SLOPE 20.0f + +static bool addRamp(World *world, Vec3 yard) { + const Vec3 at = vec3_add(yard, (Vec3)RAMP_AT); + Object *ramp = Object_create(OBJ_BOX_STATIC, PALETTE_MODERN, at, (Vec3)RAMP_SIZE, 100.0f); + if (!ramp) return false; + Object_setRotation(ramp, vec3(deg2rad(RAMP_SLOPE), 0.0f, 0.0f)); + /* its lowest and highest corners: the low end goes onto the ground */ + const Collider *c = &ramp->colliders[0]; + Vec3 center = Object_colliderCenter(ramp, 0), low = center, high = center; + for (int k = 0; k < 8; k++) { + Vec3 p = center; + p = vec3_add(p, vec3_scale(c->axes[0], (k & 1 ? 1.0f : -1.0f) * c->half_extents.x)); + p = vec3_add(p, vec3_scale(c->axes[1], (k & 2 ? 1.0f : -1.0f) * c->half_extents.y)); + p = vec3_add(p, vec3_scale(c->axes[2], (k & 4 ? 1.0f : -1.0f) * c->half_extents.z)); + if (p.y < low.y) low = p; + if (p.y > high.y) high = p; + } + ramp->position.y += Terrain_height(&world->terrain, low.x, low.z) - 0.03f - low.y; + Object_syncColliders(ramp); + if (!World_add(world, ramp)) return false; + /* the block under its top end (up to the ramp's underside there) */ + Vec3 up_end = vec3_add(center, vec3_scale(c->axes[2], vec3_dot(vec3_sub(high, center), c->axes[2]) > 0.0f ? c->half_extents.z : -c->half_extents.z)); + up_end.y += Terrain_height(&world->terrain, low.x, low.z) - 0.03f - low.y; + Vec3 out = vec3_normalize(vec3(c->axes[2].x, 0.0f, c->axes[2].z)); + if (vec3_dot(out, vec3_sub(high, low)) < 0.0f) out = vec3_scale(out, -1.0f); + Vec3 foot = vec3_sub(up_end, vec3_scale(out, 0.25f)); /* (a bit in from the end: the plank sticks out over it) */ + float ground = Terrain_height(&world->terrain, foot.x, foot.z); + float under = up_end.y - fabsf(c->axes[1].y) * c->half_extents.y - 0.12f - ground; /* (the underside, a bit lower there) */ + if (under < 0.1f) return true; + Object *block = Object_create(OBJ_BOX_STATIC, PALETTE_GROUND, vec3(foot.x, ground, foot.z), vec3(1.3f, under, 0.4f), 100.0f); + return block && World_add(world, block); +} + +bool Scene_finish(World *world) { + return addRamp(world, Site_yard()); +} + Object *Scene_load(World *world) { Object *player = NULL; int characters = 0; @@ -147,6 +189,7 @@ Object *Scene_load(World *world) { obj->lighter_uses = obj->lighter ? LIGHTER_USES * (1 + characters % 3) / 3 : 0; /* (some nearly empty) */ obj->smoker = SMOKER[characters % TEMPERAMENT_COUNT]; obj->wits = WITS[characters % TEMPERAMENT_COUNT]; + obj->jacket_hung = true; /* (on the rack in your garage) */ obj->cigarettes = CIGARETTES[characters % TEMPERAMENT_COUNT]; obj->tobacco = TOBACCO[characters % TEMPERAMENT_COUNT]; obj->craving = 0.3f + 0.15f * (float)(characters % 3); diff --git a/entropie/src/world/scene.h b/entropie/src/world/scene.h index 31c7ecf..4d1d556 100644 --- a/entropie/src/world/scene.h +++ b/entropie/src/world/scene.h @@ -9,5 +9,7 @@ /* Fills the world. Returns the player object, or NULL on failure. */ Object *Scene_load(World *world); +/* Once the ground is there: what stands on it just so (the ramp in the yard). */ +bool Scene_finish(World *world); #endif /* WORLD_SCENE_H */ diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index eaa1ce7..bb4f432 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -709,6 +709,7 @@ static const RoomPiece ROOMS[] = { { FURN_TABLE, { 23.3f, 0, 4.0f }, { 1.6f, 0.75f, 0.9f }, 0.0f }, { FURN_CHAIR, { 22.5f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, PI_F }, { FURN_CHAIR, { 24.1f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, 0.0f }, + { FURN_COATRACK, { 20.6f, 0, 2.4f }, { 0.5f, 1.75f, 0.5f }, 0.0f }, /* everybody's jacket */ /* 3: car repairs (the wide door) */ { FURN_CAR, { 23.4f, 0, -11.2f }, { 1.8f, 1.5f, 4.2f }, 0.0f }, { FURN_WORKBENCH, { 26.6f, 0, -6.6f }, { 2.4f, 0.9f, 0.7f }, 0.0f }, diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index ef40ce1..ef89e0a 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -117,7 +117,9 @@ static float clutterQuery(const void *ctx, float x, float z, float radius, float Vec3 axis = vec3(col->axes[a].x, 0.0f, col->axes[a].z); float len = vec3_length(axis); if (len < 0.3f) continue; /* (an upright axis) */ - float d = fabsf(vec3_dot(rel, vec3_scale(axis, 1.0f / len))) - half[a] * len; + Vec3 dir = vec3_scale(axis, 1.0f / len); + float extent = fabsf(vec3_dot(dir, col->axes[0])) * half[0] + fabsf(vec3_dot(dir, col->axes[1])) * half[1] + fabsf(vec3_dot(dir, col->axes[2])) * half[2]; + float d = fabsf(vec3_dot(rel, dir)) - extent; /* (its outline seen from above) */ gap = fmaxf(gap, d); } if (gap > radius) continue; @@ -667,10 +669,48 @@ static void dressForNight(World *world, Object *o, float dt) { float cold = !night ? -2.0f : by_fire ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f); o->chill = clampf(o->chill + cold * dt / 60.0f, 0.0f, 1.0f); /* (about a minute to feel it) */ if (o->look.top != TOP_JACKET && o->chill > 0.6f) { - o->look.top = TOP_JACKET; - if (o->type == OBJ_HUMAN && o->ai.speech_time <= 0.0f && worldDice(world) < 0.3f) Ai_say(o, "Brr, getting cold."); + bool rack = false; /* (no rack anywhere: it's just there, like before) */ + for (int i = 0; i < world->count && !rack; i++) rack = world->objects[i]->type == OBJ_FURNITURE && world->objects[i]->variant == FURN_COATRACK; + if (!o->jacket_hung || !rack) { /* with them: on it goes */ + o->look.top = TOP_JACKET; + if (o->type == OBJ_HUMAN && o->ai.speech_time <= 0.0f && worldDice(world) < 0.3f) Ai_say(o, "Brr, getting cold."); + } else if (o->type == OBJ_HUMAN && !o->ai.want_jacket) { /* on the rack: they go and get it */ + o->ai.want_jacket = true; + o->ai.jacket_time = 0.0f; + if (o->ai.speech_time <= 0.0f && worldDice(world) < 0.4f) Ai_say(o, "Brr - I'll get my jacket."); + } } else if (o->look.top == TOP_JACKET && o->chill < 0.15f) { - o->look.top = TOP_SHIRT; + o->look.top = TOP_SHIRT; /* (off, and along with them: round the waist) */ + } + if (o->chill < 0.3f) o->jacket_told = false; +} + +/* The tin shows its butts, the coat rack the jackets on it: their meshes are + built again when that changes. */ +static void showProps(World *world) { + for (int i = 0; i < world->count; i++) { + Object *f = world->objects[i]; + if (f->type != OBJ_FURNITURE) continue; + if (f->variant == FURN_ASHTRAY) { + int butts = f->stored < 12 ? f->stored : 12 + (f->stored >= 14); + if (butts == f->shown) continue; + Mesh_destroy(&f->mesh); + f->mesh = Shapes_ashtray(f->size, f->stored); + f->shown = butts; + } else if (f->variant == FURN_COATRACK) { + Vec3 colors[6]; + int n = 0, mask = 0, k = 0; + for (int j = 0; j < world->count && n < 6; j++) { + const Object *o = world->objects[j]; + if (o->type != OBJ_HUMAN && o->type != OBJ_PLAYER) continue; + if (o->jacket_hung) { colors[n++] = o->look.jacket; mask |= 1 << k; } + k++; + } + if (mask == f->shown) continue; + Mesh_destroy(&f->mesh); + f->mesh = Shapes_coatRack(f->size, colors, n); + f->shown = mask; + } } } @@ -1013,7 +1053,8 @@ static void emptyTray(World *world, Object *o) { } void World_update(World *world, float dt, float game_dt) { - Object_indexKinds(world->objects, world->count); /* (who's a person, where the fires are: looked up again and again) */ + Object_indexKinds(world->objects, world->count); + showProps(world); /* (who's a person, where the fires are: looked up again and again) */ for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type == OBJ_DOG) Dog_update(o, world->objects, world->count, dt);