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.4.0: pullovers instead of the coat rack
- The coat rack is gone (code too); the jacket is now a pullover - Your pullover (B): put it on, take it off (tied round your hips), or put it down where you look: over a chair's back, a railing, a wall or a bench, folded on a table, or spread on the ground; click it to take it back - Sitting on the ground: spread it out and sit on it (C at it sits on it) - Others hang theirs over their chair or sit on it on the ground, take it along when they get up, and go and get it when it gets cold - Cold: it goes on for you once, after that it's up to you; put down somewhere, you're told where it is
entropie/ARCHITECTURE.md | 4 +- entropie/Makefile | 2 +- entropie/README.md | 10 +- entropie/STATE.md | 16 ++- entropie/commit_message.txt | 18 +-- entropie/src/game/game.c | 13 ++- entropie/src/game/game.h | 2 +- entropie/src/game/game_actions.c | 89 +++++++++++---- entropie/src/people/ai.c | 67 +++-------- entropie/src/people/ai.h | 3 +- entropie/src/render/shapes.h | 8 +- entropie/src/render/shapes_props.c | 70 +++++++----- entropie/src/world/clothes.c | 227 +++++++++++++++++++++++++++++++++++++ entropie/src/world/clothes.h | 42 +++++++ entropie/src/world/object.c | 9 +- entropie/src/world/object.h | 13 ++- entropie/src/world/scene.c | 1 - entropie/src/world/world.c | 42 +++---- 18 files changed, 481 insertions(+), 155 deletions(-) diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index cbf1100..e8ae814 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, 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, water, 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 | all | @@ -23,7 +23,7 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL `Game_update` → input, actions, talk, player → `World_update`: physics for every object (near ones from the `Grid`) → grid rebuilt → felling and cutting → hands (pushing, carrying) → hose → AI → bodies animated → props -(gates, chairs, ashtray, coat rack). Then the camera follows (`game.c`), and +(gates, chairs, ashtray, pullovers put down). Then the camera follows (`game.c`), and `Game_render`: shadow pass, scene pass (terrain, objects, figures, grass, smoke), ink pass, UI. diff --git a/entropie/Makefile b/entropie/Makefile index 3a4a82d..cac588a 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.3.0 +VERSION := 2.4.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index d8ffccc..89a05b9 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -516,9 +516,13 @@ say no more often and don't come to you for a bit. goes back when they think of it; owners ask for theirs - 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 and gets tied round the waist; - everybody's jacket is with them (the coat rack is gone) +- Cold nights: away from the fire the pullover goes on (each their own colour), + by the fire or in the day it comes off and gets tied round the waist +- Your pullover (B, your pockets): put it on, take it off (round your hips) or + put it down where you look: over a chair's back, a railing or a bench, on + a table, or spread on the ground. Sitting on the ground: spread it out and + sit on it. Click it to take it back. Others hang theirs over their chair or + sit on it, take it along when they get up, and fetch it when it gets cold - Ask them what they're doing, send them back to helping you, or give them a break ## Smoking diff --git a/entropie/STATE.md b/entropie/STATE.md index cc2a653..7030def 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -169,9 +169,15 @@ Round 2.3: - Sprint: everyone's own stride (`Body.stride`, from their quirks: longer steps and fewer, or shorter and quicker; fitter: longer at a run), step length up to 1.8 m, the foot reaching further ahead at a run (MAX_LEAD x (1 + 0.45 run)). Arms swing against the other foot (they stayed forward when both feet were behind the hips), bent more (1.2 + 0.45 run), the forearm eased less. - Size (`object.c` Object_setHeight, called in `scene.c`): the body box, its middle, the step height are from each person's own height (1.66 .. 1.86 m; it was 1.75 for all), top and jump speed x sqrt(height / 1.75). Audit NPCs: joint jumps 4516 -> 2418, knees back 76 -> 8; sprint 338 -> 225. +Round 2.4: +- The coat rack is gone from the code (FURN_COATRACK, Shapes_coatRack, `jacket_hung`, hangJacket). The jacket is called a pullover in the game. +- Pullovers as things (`clothes.c`, OBJ_CLOTHES: one each, made the first time it's put down, no mesh and no collision while with them again; `pullover`, `pullover_down`, `pullover_sat`; `cloth_of`, `cloth_host`, `cloth_where`). Clothes_spot: sitting on a chair: over its back; sitting on the ground: under you; a chair within 1 m in front: over its back (CHAIR_GAP 0.05, it goes along when the chair slides); else what you look at within LOOK_REACH 1.9 m: a top 0.35-1.5 m over the feet that drops 0.25 m within EDGE_THICK 0.42 m past the edge: hung over it (a railing up to 0.12 m thick, a wall top, a bench; the drape as wide as the edge), else folded on top (a table, a bench, a chair); else spread on the ground in front (up to 0.6 m). Meshes `Shapes_pullover` (spread with the sleeves out, hung over an edge, folded). +- You (B, pocket rows "Pullover", "Put your pullover down"): put on / take off (round your hips), put it down where the verb line says; click it (within 1.3 m, `takePullover`) to take it back; somebody else's: "That's Jana's pullover". C at your spread pullover: you sit on it. Cold: put on for you once a cold spell (`jacket_told`), after that it's up to you; put down: told where it is. +- People (`Clothes_update`): sitting down (settled), warm, theirs with them: over the chair's back (0.8 x tidiness) or under them on the ground (tidiness: 0.35 + 0.35 liking for clearing up + 0.2 pride - 0.3 drunk), "Not sitting in the dirt."; getting up, it comes along. Cold with it put down: `ai.c` fetchPullover (idle, wander, at a table; on their own chair or under them: just take it; else goTo it, on with it; gives up after 90 s, 80 m off or warm). + 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); 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. +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 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). @@ -312,7 +318,11 @@ Most important first. - Ladders, still to do: a holder for a ladder standing free; NPCs taking a ladder somewhere for their own work - 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) +- 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 +- Butts, next: into a bin or the fire too, a pocket ashtray for some, the tidy ask for an ashtray with no bin near +- Arrival, next: the first start by car (you, Volkram, Jana, Toni), unloading 2 crates of beer, 2 of water, sleeping bags, mats, tents +- Tents: Volkram and Jana share one, Toni and you a small one each; pitching together (poles, the cover, pegs); sleeping in them +- Rain: showers; people go into the house or their tents **Tools for us** - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback @@ -321,4 +331,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.3.0 (build 645f709)` +State of `entropie 2.4.0 (build 6d78a96)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 0d5253f..d96e1f1 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,9 +1,11 @@ -entropie 2.3.0: hands, shoulder roll, strides, size +entropie 2.4.0: pullovers instead of the coat rack -- Hands: wrist kept near straight; held bottles and cigarettes sit in the fist -- Shoulder roll: dropping from about 2.5 m the sporty roll over the left or - right shoulder in the direction they were moving; others go into the knees - or crash -- Sprint: everyone has their own stride (longer and fewer, or shorter and - quicker), the foot reaches further ahead, arms swing freely against the legs -- Size: body box, step height and speed follow each person's own height +- The coat rack is gone (code too); the jacket is now a pullover +- Your pullover (B): put it on, take it off (tied round your hips), or put + it down where you look: over a chair's back, a railing, a wall or a bench, + folded on a table, or spread on the ground; click it to take it back +- Sitting on the ground: spread it out and sit on it (C at it sits on it) +- Others hang theirs over their chair or sit on it on the ground, take it + along when they get up, and go and get it when it gets cold +- Cold: it goes on for you once, after that it's up to you; put down + somewhere, you're told where it is diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 3a624b6..0f925cb 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -4,6 +4,7 @@ */ #include "game/game_internal.h" +#include "world/clothes.h" #define START_HOUR 13.0f #define START_AT_COURSE 0 /* (testing) 1: start at the obstacle course at the chicken farm; 0: in the yard (the course is there either way) */ @@ -286,10 +287,16 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { puttingUp(game); game_climbLadder(game, input, dt); game_climb(game, input, dt); + bool warm = game->player->look.top == TOP_JACKET; 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"); + Object *you = game->player; + if (!warm && you->look.top == TOP_JACKET) { + say(game, "Brr, it's getting cold - you pull your pullover on (B: off again)"); + } else if (you->chill > 0.6f && you->pullover_down && you->look.top != TOP_JACKET && !you->jacket_told) { + static char line[96]; + you->jacket_told = true; + snprintf(line, sizeof(line), "Brr, it's getting cold - your pullover is %s", Clothes_where(you)); + say(game, line); } if (game->player->body.stepped_off) { game->player->body.stepped_off = false; diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 73f28b6..c05f699 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -94,7 +94,7 @@ const char *Game_status(const Game *game); /* Shadow pass and scene pass for all world objects. */ void Game_render(Game *game, Renderer *renderer); -#define POCKET_LINES 10 +#define POCKET_LINES 12 /* Your pockets as lines of text: a heading, what's in them, what Enter does; *picked: the line picked. */ int Game_pocketLines(Game *game, char lines[][96], int max, int *picked); diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index e87121d..1f21af6 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -5,6 +5,7 @@ #include "game/game_internal.h" #include "people/pockets.h" +#include "world/clothes.h" #include "people/climb.h" #include <stdlib.h> @@ -163,7 +164,7 @@ static void smokeOrStop(Game *game) { /* ---------- Your pockets (B): what's in them, taken out, given ---------- */ -enum { ROW_CIGS, ROW_TOBACCO, ROW_PAPERS, ROW_FILTERS, ROW_LIGHTER, ROW_KEYS, ROW_URINELLAS, POCKET_ROWS }; +enum { ROW_CIGS, ROW_TOBACCO, ROW_PAPERS, ROW_FILTERS, ROW_LIGHTER, ROW_KEYS, ROW_URINELLAS, ROW_PULLOVER, ROW_PUT_DOWN, POCKET_ROWS }; static bool rowHas(const Object *p, int row) { switch (row) { @@ -174,6 +175,8 @@ static bool rowHas(const Object *p, int row) { case ROW_LIGHTER: return p->lighter; case ROW_KEYS: return p->keys != NULL; case ROW_URINELLAS: return p->urinellas > 0; + case ROW_PULLOVER: return true; + case ROW_PUT_DOWN: return !p->pullover_down; } return false; } @@ -188,10 +191,18 @@ static void rowText(const Object *p, int row, char *out, size_t size) { p->lighter_uses > 0 ? "works" : "empty"); break; case ROW_KEYS: snprintf(out, size, "%s", p->keys); break; case ROW_URINELLAS: snprintf(out, size, "Urinellas - %d", p->urinellas); break; + case ROW_PULLOVER: snprintf(out, size, "Pullover - %s", p->pullover_down ? Clothes_where(p) : p->look.top == TOP_JACKET ? "on" + : "tied round your hips"); break; + case ROW_PUT_DOWN: snprintf(out, size, "Put your pullover down"); break; default: out[0] = 0; break; } } +/* Where your pullover would go if put down now (where you look). */ +static bool pulloverSpot(Game *game, ClothSpot *spot) { + return Clothes_spot(&game->world, game->player, game->camera.front, spot); +} + /* The row picked, kept on something that's there. */ static int pocketRow(Game *game) { const Object *p = game->player; @@ -214,6 +225,17 @@ static void pocketVerb(Game *game, int row, char *out, size_t size) { case ROW_LIGHTER: snprintf(out, size, name ? "Enter: lend %s your lighter" : "Enter: click - does it work?", name); break; case ROW_KEYS: snprintf(out, size, "Keys stay in your pocket"); break; case ROW_URINELLAS: snprintf(out, size, name ? "Enter: give %s one" : "They're for peeing standing (P)", name); break; + case ROW_PULLOVER: + snprintf(out, size, "%s", game->player->pullover_down ? "Look at it and click to take it" + : game->player->look.top == TOP_JACKET ? "Enter: take it off (round your hips)" : "Enter: put it on"); + break; + case ROW_PUT_DOWN: { + ClothSpot spot; + if (!pulloverSpot(game, &spot)) snprintf(out, size, "Nowhere to put it here"); + else if (game->player->sitting && !game->player->seat) snprintf(out, size, "Enter: spread it out and sit on it"); + else snprintf(out, size, "Enter: put it %s", spot.where); + break; + } default: out[0] = 0; break; } } @@ -223,6 +245,26 @@ static void smokeOrStop(Game *game); static void usePocket(Game *game, int row) { Object *p = game->player, *person = game_personInFront(game); char line[96]; + if (row == ROW_PULLOVER) { + if (p->pullover_down) { say(game, "Look at it and click to take it"); return; } + bool on = p->look.top != TOP_JACKET; + if (on) Clothes_takeBack(p, true); + else { p->look.top = TOP_SHIRT; p->look.jacket_tied = true; } + p->jacket_told = true; /* (your choice now, this cold spell) */ + say(game, on ? "You pull your pullover on" : "You take your pullover off and tie it round your hips"); + return; + } + if (row == ROW_PUT_DOWN) { + ClothSpot spot; + if (!pulloverSpot(game, &spot)) { say(game, "Nowhere to put it here"); return; } + bool under = p->sitting && !p->seat; + if (!Clothes_putDown(&game->world, p, &spot)) return; + p->jacket_told = true; + snprintf(game->info, sizeof(game->info), under ? "You spread your pullover out and sit on it" : "You put your pullover %s", spot.where); + game->info_about = NULL; + say(game, game->info); + return; + } if (!person || person->type != OBJ_HUMAN) { if (row == ROW_CIGS || row == ROW_TOBACCO || row == ROW_PAPERS || row == ROW_FILTERS) { if (!Body_isSmoking(&p->body)) smokeOrStop(game); @@ -586,6 +628,25 @@ static bool swingGate(Game *game) { return true; } +/* Looking at your pullover (put down) within reach: back round your hips. Somebody else's: whose. */ +static bool takePullover(Game *game) { + Object *p = game->player; + Object *c = Clothes_alongRay(&game->world, game->camera.position, game->camera.front, 2.3f); + if (!c) return false; + Vec3 to = vec3_sub(c->position, p->position); + to.y = 0.0f; + if (vec3_length(to) > 1.3f) return false; + if (c->cloth_of != p) { + snprintf(game->info, sizeof(game->info), "That's %s's pullover", c->cloth_of ? c->cloth_of->name : "somebody"); + game->info_about = NULL; + say(game, game->info); + return true; + } + Clothes_takeBack(p, false); + say(game, "You take your pullover and tie it round your hips"); + return true; +} + /* At the cigarette machine: a pack. */ static bool smokesMachine(Game *game) { Object *m = furnitureNear(game, FURN_SMOKES, 0.9f); @@ -597,22 +658,6 @@ static bool smokesMachine(Game *game) { return true; } -/* 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++) @@ -879,7 +924,7 @@ static int leftClick(Game *game) { Talk_showHere(&game->world, p, shown); return CLICK_NONE; } - if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || coatRack(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE; + if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || takePullover(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE; int watering = useWater(game); if (watering >= 0) return watering; if (bagTrash(game)) return CLICK_BAG; @@ -996,7 +1041,13 @@ void game_handleActions(Game *game, const Input *input) { else if (input->sit && pourOnFire(game)) {} /* (right at a burning fire with a drink: onto it) */ else if (input->sit && !Body_isBusy(&p->body) && !p->on_ladder) { if (p->sitting) p->sitting = false; - else Object_sit(p, Object_seatNear(game->world.objects, game->world.count, p->position, 1.0f), p->position); + else { + Object_sit(p, Object_seatNear(game->world.objects, game->world.count, p->position, 1.0f), p->position); + Object *c = p->pullover; + if (!p->seat && p->pullover_down && c && c->variant == CLOTH_SPREAD && + vec3_length(vec3(c->position.x - p->position.x, 0.0f, c->position.z - p->position.z)) < 0.6f) + say(game, "You sit down on your pullover"); + } } if (input->move_forward != 0.0f || input->move_right != 0.0f || input->jump) p->sitting = false; if (p->on_ladder) { /* climbing: hands on the rungs */ diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 345757e..3ee2260 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -10,6 +10,7 @@ #include "world/nav.h" #include "world/grid.h" #include "world/world.h" +#include "world/clothes.h" #include "render/shapes.h" /* ---------- Small helpers ---------- */ @@ -1638,64 +1639,32 @@ 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 */ +/* Cold, and the pullover is put down somewhere (over a chair, on the ground): there, and on with it. */ +#define JACKET_GIVE_UP 90.0f /* s on the way: forget it */ +#define PULLOVER_WALK 80.0f /* m: further off than this it's forgotten (for now) */ -static bool fetchJacket(Object *self, Object *const *objects, int count, float dt) { +static bool fetchPullover(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) { + Object *pullover = self->pullover; + if (!pullover || !self->pullover_down || self->look.top == TOP_JACKET || (ai->jacket_time += dt) > JACKET_GIVE_UP || + self->chill < 0.3f || flatDistance(pullover->position, self->position) > PULLOVER_WALK) { 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; + bool at_hand = self->sitting && (pullover->cloth_host == self->seat || + (!self->seat && flatDistance(pullover->position, self->position) < 0.5f)); /* (on their chair, under them) */ + if (!at_hand) { + if (self->sitting) self->sitting = false; + if (!goTo(self, pullover, objects, count, 0.45f, dt)) return true; + } + Clothes_takeBack(self, true); ai->want_jacket = false; if (random01(ai) < 0.5f) Ai_say(self, "Ah, better."); rest(ai, 1.0f, 2.0f); return true; } -#define RACK_WALK 60.0f /* m: the rack this near ... */ -#define RACK_CHANCE 0.004f /* ... the jacket back onto it this often a second (the tidy more, the lazy less) */ - -static Object *coatRack(Object *const *objects, int count) { - for (int i = 0; i < count; i++) - if (objects[i]->type == OBJ_FURNITURE && objects[i]->variant == FURN_COATRACK) return objects[i]; - return NULL; -} - -/* Warm again, the jacket tied round the waist: now and then back onto the rack with it. */ -static bool hangJacket(Object *self, Object *const *objects, int count, float dt) { - Ai *ai = &self->ai; - if (!ai->hang_jacket) { - if (self->jacket_hung || self->look.top == TOP_JACKET || self->chill > 0.05f || ai->want_jacket) return false; - float tidy = clampf(0.5f + ai->character.likes[ORDER_TRASH] - 0.4f * self->drunk, 0.1f, 1.5f); - if (random01(ai) > RACK_CHANCE * tidy * dt) return false; - Object *rack = coatRack(objects, count); - if (!rack || flatDistance(rack->position, self->position) > RACK_WALK) return false; - ai->hang_jacket = true; - ai->jacket_time = 0.0f; - if (random01(ai) < 0.3f) Ai_say(self, "I'll hang my jacket up."); - } - Object *rack = coatRack(objects, count); - if (!rack || self->jacket_hung || self->look.top == TOP_JACKET || self->chill > 0.3f || (ai->jacket_time += dt) > JACKET_GIVE_UP) { - ai->hang_jacket = false; - return false; - } - if (!goTo(self, rack, objects, count, 0.6f, dt)) return true; - Hands_touch(self, Object_colliderCenter(rack, 0)); /* (onto a hook) */ - self->jacket_hung = true; - ai->hang_jacket = false; - 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; @@ -1752,10 +1721,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; - /* Warm again: the jacket back onto the rack */ - if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && hangJacket(self, objects, count, dt)) return; + /* Cold, the pullover put down somewhere: off to get it */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && fetchPullover(self, objects, count, dt)) return; /* Out of cigarettes: to the machine */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && buySmokes(self, objects, count, dt)) return; /* Tidy people pick things up on the way */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index c58af24..83ad489 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -172,8 +172,7 @@ 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 */ - bool hang_jacket; /* warm again, the jacket tied round the waist: back onto the rack with it */ + bool want_jacket; /* cold, the pullover put down somewhere: off to get it */ bool want_smokes; /* out of cigarettes, nobody gives one: off to the machine */ float smokes_time; /* ... on the way this long */ bool asked_you; /* asked the player for one (nothing came of it: the machine next time) */ diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index 7562a58..e1d422f 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -115,7 +115,7 @@ 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_COATRACK, FURN_GATE, FURN_SMOKES, FURN_COUNT + FURN_CHAIR, FURN_BENCH, FURN_CAR, FURN_TIRES, FURN_TOILET, FURN_TAP, FURN_ASHTRAY, FURN_GATE, FURN_SMOKES, FURN_COUNT } FurnitureKind; #define SEAT_HEIGHT 0.45f /* chairs and log benches */ @@ -126,8 +126,10 @@ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size); Mesh Shapes_ashtray(Vec3 size, int butts); /* Ivy up a wall (at z = 0, facing +Z), this wide and high. */ Mesh Shapes_ivy(float width, float height, unsigned seed); -/* The coat rack with these jackets hanging on it (n at most 6). */ -Mesh Shapes_coatRack(Vec3 size, const Vec3 *jackets, int n); +/* A pullover put down (see clothes.h), its colour: lay 0 spread out flat on the ground (y = 0 under it, + the neck to +Z, sleeves out), 1 hung over an edge along X (y = 0 its top; gap: how thick that edge is), + 2 folded up (y = 0 under it). */ +Mesh Shapes_pullover(int lay, Vec3 color, float gap); /* A dachshund, its nose towards -Z, standing on y = 0: a plain body for picking it out (she's drawn as a painted figure, see people/dog.c); her proportions. */ diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index abdb1d9..8548503 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -81,6 +81,47 @@ Mesh Shapes_toy(int variant, Vec3 size) { return Mesh_fromBuilder(&b); } +/* A sleeve flat on the ground from the shoulder at (x, z), out and down at this angle off straight out. */ +static void addFlatSleeve(MeshBuilder *b, float side, float angle, Vec3 color, Vec3 cuff) { + const float len = 0.48f, wide = 0.13f, y = 0.013f; + float yaw = side > 0.0f ? -angle : PI_F + angle; /* (lays local +X along (side cos a, 0, -sin a)) */ + Mat4 at = mat4_mul(mat4_translate(vec3(side * 0.24f, 0.0f, 0.22f)), mat4_rotate_y(yaw)); + size_t first = b->vertex_count; + addBlock(b, vec3(len * 0.5f, y, 0.0f), vec3(len, 0.022f, wide), color); + addBlock(b, vec3(len - 0.03f, y + 0.002f, 0.0f), vec3(0.06f, 0.024f, wide * 0.9f), cuff); + transformSince(b, first, at); +} + +Mesh Shapes_pullover(int lay, Vec3 color, float gap) { + MeshBuilder b; + MeshBuilder_init(&b); + Vec3 dark = vec3_scale(color, 0.78f); /* (the ribbed band, cuffs and collar) */ + if (lay == 0) { /* spread out: the body, the band at the bottom, the collar, both sleeves out */ + addBlock(&b, vec3(0.0f, 0.012f, 0.0f), vec3(0.5f, 0.024f, 0.62f), color); + addBlock(&b, vec3(0.0f, 0.014f, -0.28f), vec3(0.5f, 0.026f, 0.06f), dark); + addBlock(&b, vec3(0.0f, 0.016f, 0.28f), vec3(0.17f, 0.028f, 0.06f), dark); + addFlatSleeve(&b, 1.0f, 0.55f, color, dark); + addFlatSleeve(&b, -1.0f, 0.55f, color, dark); + } else if (lay == 1) { /* over an edge: rolled over its top, the front (+Z) and the back hanging down, the sleeves in front */ + float in = gap * 0.5f + 0.015f; + addBlock(&b, vec3(0.0f, 0.016f, 0.0f), vec3(0.46f, 0.032f, gap + 0.06f), color); + addBlock(&b, vec3(0.0f, -0.15f, in), vec3(0.46f, 0.32f, 0.024f), color); + addBlock(&b, vec3(0.0f, -0.30f, in + 0.002f), vec3(0.46f, 0.05f, 0.026f), dark); + addBlock(&b, vec3(0.0f, -0.13f, -in), vec3(0.46f, 0.28f, 0.024f), color); + for (int s = -1; s <= 1; s += 2) { + size_t first = b.vertex_count; + addBlock(&b, vec3(0.0f, -0.22f, 0.0f), vec3(0.11f, 0.44f, 0.022f), color); + addBlock(&b, vec3(0.0f, -0.42f, 0.0f), vec3(0.1f, 0.05f, 0.024f), dark); + transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)s * 0.17f, 0.0f, in + 0.024f)), mat4_rotate_z((float)s * 0.08f))); + } + } else { /* folded up */ + addBlock(&b, vec3(0.0f, 0.03f, 0.0f), vec3(0.32f, 0.06f, 0.26f), color); + addBlock(&b, vec3(0.0f, 0.03f, 0.125f), vec3(0.322f, 0.04f, 0.014f), dark); + addBlock(&b, vec3(0.0f, 0.061f, -0.02f), vec3(0.14f, 0.004f, 0.05f), dark); + } + return Mesh_fromBuilder(&b); +} + Mesh Shapes_bread(Vec3 size) { MeshBuilder b; MeshBuilder_init(&b); @@ -751,25 +792,6 @@ static void buildAshtray(MeshBuilder *b, Vec3 size, int butts) { } } -/* 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); @@ -777,13 +799,6 @@ Mesh Shapes_ashtray(Vec3 size, int 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); @@ -922,9 +937,6 @@ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) { case FURN_ASHTRAY: buildAshtray(&b, size, 3); break; - case FURN_COATRACK: - buildCoatRack(&b, size, NULL, 0); - break; case FURN_GATE: { /* a wooden gate leaf, painted green: boards, two rails and a brace */ const Vec3 green = { 0.24f, 0.45f, 0.28f }, dark = { 0.17f, 0.34f, 0.21f }; int boards = (int)(size.x / 0.14f); diff --git a/entropie/src/world/clothes.c b/entropie/src/world/clothes.c new file mode 100644 index 0000000..75cf4ee --- /dev/null +++ b/entropie/src/world/clothes.c @@ -0,0 +1,227 @@ +#include "world/clothes.h" +#include "people/ai.h" +#include "people/hands.h" +#include "render/shapes.h" + +#include <stdio.h> +#include <string.h> + +#define CHAIR_REACH 1.0f /* m: a chair this near in front: over its back */ +#define LOOK_REACH 1.9f /* m: what you look at this near from the eye */ +#define EDGE_LOW 0.35f /* m over the feet: a top this high (up to EDGE_HIGH) takes it ... */ +#define EDGE_HIGH 1.5f +#define EDGE_DROP 0.25f /* ... hung over it when it drops this much ... */ +#define EDGE_THICK 0.42f /* ... within this far past the edge (a railing, a wall top, a bench); else laid on it */ +#define CHAIR_GAP 0.05f /* a chair's back this thick */ +#define AWAY 0.6f /* m: on the ground this far in front */ +#define SAT_CHANCE 0.8f /* sitting down on a chair: over its back this often (x tidiness) */ + +static Vec3 flat(Vec3 v, Vec3 or_else) { + Vec3 f = vec3_normalize(vec3(v.x, 0.0f, v.z)); + return vec3_length(f) > 0.5f ? f : or_else; +} + +static bool isChair(const Object *o) { return o && o->type == OBJ_FURNITURE && o->variant == FURN_CHAIR; } + +/* The top of a chair's back (its back at local -Z, see Shapes_furniture). */ +static Vec3 chairBack(const Object *chair) { + Vec3 local = vec3(0.0f, chair->size.y, -chair->size.z * 0.5f + 0.02f); + return vec3_add(chair->position, mat4_mul_dir(mat4_rotate_y(chair->rotation.y), local)); +} + +static void overChair(const Object *chair, ClothSpot *s) { + s->lay = CLOTH_HUNG; + s->host = (Object *)chair; + s->at = chairBack(chair); + s->yaw = chair->rotation.y; + s->gap = CHAIR_GAP; + s->where = "over the chair's back"; +} + +/* The nearest chair in front within reach. */ +static const Object *chairInFront(const World *world, const Object *who, Vec3 look) { + const Object *best = NULL; + float best_d = CHAIR_REACH; + for (int i = 0; i < world->count; i++) { + const Object *o = world->objects[i]; + if (!isChair(o)) continue; + Vec3 to = vec3_sub(chairBack(o), who->position); + to.y = 0.0f; + float d = vec3_length(to); + if (d < best_d && (d < 0.3f || vec3_dot(vec3_scale(to, 1.0f / d), look) > 0.4f)) { best = o; best_d = d; } + } + return best; +} + +static bool notThere(const Object *o, const void *ctx) { + return o == ctx || o->is_character || o->is_held || o->type == OBJ_DOG || o->type == OBJ_CLOTHES; +} + +/* Hung over it: a railing (thin), a wall top, a bench, a chair. */ +static const char *overIt(const Object *o, bool thin) { + if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH) return "over the bench"; + if (isChair(o)) return "over the chair"; + if (o->type == OBJ_WALL || o->type == OBJ_HOUSE || o->type == OBJ_BARN) return thin ? "over the railing" : "over the wall"; + return "over it"; +} + +static const char *onTopOf(const Object *o) { + if (o->type == OBJ_FURNITURE && o->variant == FURN_TABLE) return "on the table"; + if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH) return "on the bench"; + if (isChair(o)) return "on the chair"; + return "on top of it"; +} + +bool Clothes_spot(const World *world, const Object *who, Vec3 look, ClothSpot *s) { + memset(s, 0, sizeof(*s)); + Vec3 facing = Object_facing(who), ahead = flat(look, facing); + float feet = who->position.y; + if (who->sitting) { + if (isChair(who->seat)) { overChair(who->seat, s); return true; } + if (who->seat) return false; /* (a bench or a log: no) */ + s->lay = CLOTH_SPREAD; + s->at = vec3_add(who->position, vec3_scale(facing, 0.05f)); + s->at.y = World_groundHeight(world, s->at.x, s->at.z, feet + 0.3f); + s->yaw = yaw_facing(facing); + s->where = "on the ground"; + return true; + } + const Object *chair = chairInFront(world, who, ahead); + if (chair) { overChair(chair, s); return true; } + + Vec3 eye = vec3_add(who->position, vec3(0.0f, who->look.height * 0.92f, 0.0f)); + Object *hit = NULL; + Vec3 n = vec3_zero(); + float t = World_rayCast(world, eye, look, LOOK_REACH, notThere, who, &hit, &n); + if (t >= 0.0f && hit) { + Vec3 h = vec3_add(eye, vec3_scale(look, t)); + Vec3 in = flat(vec3_scale(n, -1.0f), ahead); /* (into it) */ + Vec3 edge = vec3_add(h, vec3_scale(in, 0.02f)); + float top = World_groundHeight(world, edge.x, edge.z, h.y + 0.6f), up = top - feet; + if (up > EDGE_LOW && up < EDGE_HIGH && top < h.y + 0.6f) { + float thick = EDGE_THICK; /* (how thick the top is: where it drops away past the edge) */ + for (float d = 0.04f; d < EDGE_THICK; d += 0.04f) { + Vec3 q = vec3_add(h, vec3_scale(in, d)); + if (top - World_groundHeight(world, q.x, q.z, top + 0.05f) > EDGE_DROP) { thick = d; break; } + } + s->yaw = yaw_facing(vec3_scale(in, -1.0f)); /* (the front towards you) */ + if (thick < EDGE_THICK) { + Vec3 mid = vec3_add(h, vec3_scale(in, thick * 0.5f)); + s->lay = CLOTH_HUNG; + s->at = vec3(mid.x, top, mid.z); + s->gap = thick; + s->where = overIt(hit, thick <= 0.12f); + } else { + s->lay = CLOTH_FOLDED; + s->at = vec3(h.x + in.x * 0.18f, top, h.z + in.z * 0.18f); + s->where = onTopOf(hit); + } + return true; + } + } + float away = t >= 0.0f ? clampf(t * sqrtf(fmaxf(1.0f - look.y * look.y, 0.0f)) - 0.35f, 0.25f, AWAY) : AWAY; + s->lay = CLOTH_SPREAD; + s->at = vec3_add(who->position, vec3_scale(ahead, away)); + s->at.y = World_groundHeight(world, s->at.x, s->at.z, feet + 0.5f); + s->yaw = yaw_facing(ahead); + s->where = "on the ground"; + return fabsf(s->at.y - feet) < 0.5f; +} + +bool Clothes_putDown(World *world, Object *who, const ClothSpot *s) { + Mesh mesh = Shapes_pullover((int)s->lay, who->look.jacket, fmaxf(s->gap, 0.04f)); + Object *c = who->pullover; + if (!c) { + c = Object_createDecal(mesh, s->at); + if (!c || !World_add(world, c)) return false; + c->type = OBJ_CLOTHES; + snprintf(c->name, sizeof(c->name), "Pullover"); + who->pullover = c; + } else { + Mesh_destroy(&c->mesh); + c->mesh = mesh; + c->position = s->at; + } + c->variant = (int)s->lay; + c->rotation = vec3(0.0f, s->yaw, 0.0f); + c->cloth_of = who; + c->cloth_host = s->host; + c->cloth_where = s->where; + if (who->look.top == TOP_JACKET) who->look.top = TOP_SHIRT; + who->pullover_down = true; + who->pullover_sat = false; + who->look.jacket_tied = false; + Hands_touch(who, vec3_add(s->at, vec3(0.0f, s->lay == CLOTH_HUNG ? 0.0f : 0.05f, 0.0f))); + return true; +} + +void Clothes_takeBack(Object *who, bool wear) { + Object *c = who->pullover; + if (c && who->pullover_down) { + Hands_touch(who, c->position); + Mesh_destroy(&c->mesh); /* (nothing to see where it was) */ + c->cloth_host = NULL; + } + who->pullover_down = false; + who->pullover_sat = false; + if (wear) who->look.top = TOP_JACKET; + who->look.jacket_tied = who->look.top != TOP_JACKET; +} + +const char *Clothes_where(const Object *who) { + return who->pullover_down && who->pullover ? who->pullover->cloth_where : NULL; +} + +Object *Clothes_alongRay(const World *world, Vec3 eye, Vec3 dir, float reach) { + Object *best = NULL; + float best_off = 0.35f; + for (int i = 0; i < world->count; i++) { + Object *c = world->objects[i]; + if (c->type != OBJ_CLOTHES || !c->mesh.vao) continue; + Vec3 mid = vec3_add(c->position, vec3(0.0f, c->variant == CLOTH_HUNG ? -0.15f : 0.03f, 0.0f)); + Vec3 rel = vec3_sub(mid, eye); + float along = vec3_dot(rel, dir); + if (along < 0.0f || along > reach) continue; + float off = vec3_length(vec3_sub(rel, vec3_scale(dir, along))); + if (off < best_off) { best = c; best_off = off; } + } + return best; +} + +static float dice(World *world) { + world->rng = world->rng * 1103515245u + 12345u; + return (float)((world->rng >> 8) & 0xFFFFu) / 65536.0f; +} + +/* How likely they bother (the tidy and the proud more, the drunk less). */ +static float tidiness(const Object *o) { + return clampf(0.35f + 0.35f * o->ai.character.likes[ORDER_TRASH] + 0.2f * o->temperament.pride - 0.3f * fminf(o->drunk, 1.0f), 0.05f, 0.9f); +} + +/* Sat down (settled): theirs over the chair's back, or under them on the ground. */ +static void satDown(World *world, Object *o) { + if (o->pullover_down || o->look.top == TOP_JACKET || o->chill > 0.4f) return; + bool chair = isChair(o->seat); + if (o->seat && !chair) return; + if (dice(world) > tidiness(o) * (chair ? SAT_CHANCE : 1.0f)) return; + ClothSpot s; + if (!Clothes_spot(world, o, Object_facing(o), &s) || !Clothes_putDown(world, o, &s)) return; + o->pullover_sat = true; + if (!chair && o->ai.speech_time <= 0.0f && dice(world) < 0.3f) Ai_say(o, "Not sitting in the dirt."); +} + +void Clothes_update(World *world) { + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (o->type == OBJ_CLOTHES && o->mesh.vao && isChair(o->cloth_host)) { + o->position = chairBack(o->cloth_host); + o->rotation.y = o->cloth_host->rotation.y; + continue; + } + if (o->type != OBJ_HUMAN) continue; + bool settled = o->sitting && !o->sit_moving; + if (settled && !o->was_sitting) satDown(world, o); + else if (!o->sitting && o->was_sitting && o->pullover_sat && o->pullover_down) Clothes_takeBack(o, o->chill > 0.6f); + o->was_sitting = settled; + } +} diff --git a/entropie/src/world/clothes.h b/entropie/src/world/clothes.h new file mode 100644 index 0000000..d5d162f --- /dev/null +++ b/entropie/src/world/clothes.h @@ -0,0 +1,42 @@ +#ifndef WORLD_CLOTHES_H +#define WORLD_CLOTHES_H + +/* + * A pullover (the figure's jacket: look.top TOP_JACKET worn, look.jacket_tied + * round the waist) put down somewhere: everybody's own OBJ_CLOTHES, made the + * first time it's put down, no mesh while it's with them again. Hung over a + * chair's back or a railing, spread on the ground to sit on, or folded on a + * table or a bench. + */ + +#include "world/world.h" + +typedef enum { CLOTH_SPREAD, CLOTH_HUNG, CLOTH_FOLDED } ClothLay; + +typedef struct { + ClothLay lay; + Vec3 at; /* spread / folded: under it; hung: the top of the edge */ + float yaw; + float gap; /* hung: how thick the edge is */ + Object *host; /* a chair it goes over (it follows the chair), or NULL */ + const char *where; /* said plainly: "over the chair's back" */ +} ClothSpot; + +/* Where who's pullover would go now: sitting, over their chair's back or + under them; else what they look at (look: unit) - a chair, a railing or a + wall top, a table - or the ground in front. False: nowhere. */ +bool Clothes_spot(const World *world, const Object *who, Vec3 look, ClothSpot *spot); +/* Put down there (taken off first if worn). */ +bool Clothes_putDown(World *world, Object *who, const ClothSpot *spot); +/* With them again: tied round the waist, or on (wear). */ +void Clothes_takeBack(Object *who, bool wear); +/* Where theirs is ("over the chair's back"), or NULL: with them. */ +const char *Clothes_where(const Object *who); +/* Somebody's pullover lying near this ray (from the eye, dir unit) within reach, or NULL. */ +Object *Clothes_alongRay(const World *world, Vec3 eye, Vec3 dir, float reach); +/* Every frame: pullovers go along with the chair they hang on; people sitting + down (not the player) now and then put theirs over the chair's back or + under them, and take it back getting up. */ +void Clothes_update(World *world); + +#endif /* WORLD_CLOTHES_H */ diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index f458518..1907b88 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -347,6 +347,10 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size break; } + case OBJ_CLOTHES: /* (made in clothes.c, its mesh given) */ + obj->is_static = true; + break; + case OBJ_WALL: /* nothing to see: the building's OBJ_STRUCTURE shows it */ obj->pivot = vec3(0.0f, size.y * 0.5f, 0.0f); obj->is_static = true; @@ -1197,7 +1201,6 @@ void Object_describe(const Object *obj, char *out, size_t size) { }; if (obj->variant == FURN_ASHTRAY) { snprintf(out, size, "Ashtray (an old tin) - %d butts in it", obj->stored); break; } if (obj->variant == FURN_SMOKES) { snprintf(out, size, "Cigarette machine - click: a pack of %d", SMOKES_PACKS); 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; } @@ -1216,6 +1219,10 @@ void Object_describe(const Object *obj, char *out, size_t size) { : "Ladder (slid together) - left click: slide it out, then lean it against something"); break; case OBJ_STONE: snprintf(out, size, kg >= 1 ? "A stone - %d kg" : "A pebble", kg); break; + case OBJ_CLOTHES: + if (obj->cloth_of && obj->cloth_of->type == OBJ_PLAYER) snprintf(out, size, "Your pullover - click: take it"); + else snprintf(out, size, "%s's pullover", obj->cloth_of ? obj->cloth_of->name : "Somebody"); + break; case OBJ_TOY: snprintf(out, size, obj->variant == 0 ? "Alice's ball - throw it (T), she brings it back" : "Alice's rope toy - throw it (T), she loves to chew it"); break; case OBJ_CAN: if (obj->water > 0.05f) snprintf(out, size, "Watering can - %.0f l, left click (hold) to pour", obj->water); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 635551a..2329f8e 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -81,6 +81,7 @@ typedef enum { size = box, position = its bottom middle; variant 1: a locked door (name says which) */ OBJ_STRUCTURE, /* static: how a building made of parts looks (one mesh, no collision: see OBJ_WALL) */ OBJ_TOY, /* Alice's toys: weight 0 = a ball (size.x across), 1 = a rope to bite (size.x long, size.y thick) */ + OBJ_CLOTHES, /* somebody's pullover put down (see clothes.h): variant = ClothLay; no collision */ } ObjectType; /* What a body needs over the day. All 0..1; 0 is fine (stamina: 1 is fine). */ @@ -271,9 +272,15 @@ typedef struct Object { bool steep; /* ... stood up straight: a wall in front (flat it would go into it) */ float grip_shift; /* ladders carried at the side: slid back (< 0) or on in the hand (m), off a wall */ 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 */ + struct Object *pullover; /* characters: their pullover as a thing (OBJ_CLOTHES, made the first time it's put down) ... */ + bool pullover_down; /* ... lying there (else with them: worn - look.top TOP_JACKET - or tied round the waist) */ + bool pullover_sat; /* ... put down as they sat (over the chair's back, under them): taken back getting up */ + bool was_sitting; /* characters: sitting last frame (see Clothes_update) */ + struct Object *cloth_of; /* clothes: whose */ + struct Object *cloth_host;/* clothes: over this chair's back (and along with it when it slides) */ + const char *cloth_where; /* clothes: where, said plainly ("over the chair's back") */ + bool jacket_told; /* the player: put the pullover on for you / told where it is, this cold spell */ + int shown; /* furniture: what its mesh shows (butts in the tin); -1: not built */ bool gate_open; /* gates: open (or opening) ... */ float gate_t; /* ... how far open (0 shut .. 1 open) */ Vec3 hinge; /* ... where it turns */ diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index 83b5f2f..d0bb9ec 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -247,7 +247,6 @@ Object *Scene_load(World *world) { obj->wits = WITS[characters % TEMPERAMENT_COUNT]; obj->height_fear = HEIGHT_FEAR[characters % TEMPERAMENT_COUNT]; obj->worn = YESTERDAY[characters % TEMPERAMENT_COUNT]; - obj->jacket_hung = false; /* (with them: tied round the waist till it gets cold) */ obj->cigarettes = CIGARETTES[characters % TEMPERAMENT_COUNT]; obj->tobacco = TOBACCO[characters % TEMPERAMENT_COUNT]; obj->papers = PAPERS[characters % TEMPERAMENT_COUNT]; diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 069d214..09d08d1 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -1,5 +1,6 @@ #include "world/world.h" #include "world/camp.h" +#include "world/clothes.h" #include "people/condition.h" #include "people/dog.h" #include "world/forestry.h" @@ -700,30 +701,32 @@ static void smoke(World *world, Object *o, Vec3 facing, float dt) { } } -/* Cold nights: away from a burning fire a chill builds up and the jacket goes - on; by the fire, in the day (or warm with drink) it comes off again. */ +/* Cold nights: away from a burning fire a chill builds up and the pullover goes + on; by the fire, in the day (or warm with drink) it comes off again (round + the waist). Put down somewhere: people go and get it, you are told where it + is. You: put on for you once a cold spell (then it's up to you, B). */ static void dressForNight(World *world, Object *o, float dt) { if (o->type == OBJ_DOG) return; 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 ? -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) { - 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 */ + bool player = o->type == OBJ_PLAYER; + if (o->look.top != TOP_JACKET && o->chill > 0.6f && !(player && o->jacket_told)) { + if (!o->pullover_down) { /* with them: on it goes */ + Clothes_takeBack(o, true); + if (player) o->jacket_told = true; + else if (o->ai.speech_time <= 0.0f && worldDice(world) < 0.3f) Ai_say(o, "Brr, getting cold."); + } else if (!player && !o->ai.want_jacket) { /* put down somewhere: 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."); + if (o->ai.speech_time <= 0.0f && worldDice(world) < 0.4f) Ai_say(o, "Brr - where did I leave my pullover?"); } } else if (o->look.top == TOP_JACKET && o->chill < 0.15f) { o->look.top = TOP_SHIRT; /* (off, and along with them: round the waist) */ } if (o->chill < 0.3f) o->jacket_told = false; - o->look.jacket_tied = !o->jacket_hung && o->look.top != TOP_JACKET; /* (off and with them) */ + o->look.jacket_tied = !o->pullover_down && o->look.top != TOP_JACKET; /* (off and with them) */ } /* A chair waits to be pushed in while somebody stands where it goes. */ @@ -770,8 +773,7 @@ static float scootTo(const World *world, const Object *chair) { return clampf(chair->chair_out - closer, -0.15f, chair->chair_out); } -/* The tin shows its butts, the coat rack the jackets on it: their meshes are - built again when that changes. */ +/* The tin shows its butts: its mesh is built again when that changes. */ static void showProps(World *world) { for (int i = 0; i < world->count; i++) { Object *f = world->objects[i]; @@ -784,19 +786,6 @@ static void showProps(World *world) { 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; } } } @@ -1213,6 +1202,7 @@ static void emptyTray(World *world, Object *o) { void World_update(World *world, float dt, float game_dt) { Object_indexKinds(world->objects, world->count); world->dt_props = dt; + Clothes_update(world); 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];