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.8.0: the welcome, the meadow behind the garages, the owners
- Arrival: off the street onto the gravel road beside the main building; from the driver's seat the car is whole (doors, seats, roof) and the others sit in it with you - First say hello: Kurti and his sons sit at the fire, the others go over; they welcome you, sit down for a chat with Kurti, then the work starts (the boot opens). The hint line says what's to do next - Behind your garages: a meadow 1.5 m up (steep bank behind them, the way up by the little path left of them), bushes and trees round it that may not be cut; the tents go there - Right of the main building: a meadow 0.6 m up, bushes behind and right; the tap moved to the main building's right end - The car room's door nearer its middle: its open gates no longer block the path - Crates go into the kitchen in your garages; carrying one tires you, put it down for a moment or carry it with two (F, E) - Kurti: a belly, never takes his shirt off. Kurti and his sons don't do or help with your jobs, go into the house now and then (call them: they come out); the others keep out of the house - On a break tools are put down: neatly or anyhow, by orderliness; an axe lies on its side - No stray bottles, only a little litter
entropie/Makefile | 2 +- entropie/README.md | 49 +++++++++----- entropie/STATE.md | 18 ++++- entropie/assets/shaders/figure.frag | 8 ++- entropie/commit_message.txt | 32 ++++++--- entropie/src/game/game.c | 10 +++ entropie/src/game/game.h | 6 ++ entropie/src/game/game_actions.c | 6 +- entropie/src/game/game_arrival.c | 131 +++++++++++++++++++++++++++++++++--- entropie/src/game/game_internal.h | 1 + entropie/src/game/game_talk.c | 6 +- entropie/src/people/ai.c | 72 +++++++++++++++++++- entropie/src/people/ai.h | 3 + entropie/src/people/ai_work.c | 40 ++++++++++- entropie/src/people/condition.c | 15 +++++ entropie/src/people/condition.h | 4 ++ entropie/src/people/hands.c | 3 +- entropie/src/people/talk.c | 15 +++-- entropie/src/render/figure.h | 2 + entropie/src/render/renderer.c | 2 + entropie/src/render/renderer.h | 2 +- entropie/src/render/shapes_props.c | 25 ++++++- entropie/src/world/car.c | 20 ++++-- entropie/src/world/object.c | 1 + entropie/src/world/object.h | 3 + entropie/src/world/physics.c | 8 ++- entropie/src/world/scene.c | 43 +++++++----- entropie/src/world/site.c | 122 ++++++++++++++++++++++++++++----- entropie/src/world/site.h | 15 ++++- entropie/src/world/terrain.c | 2 +- entropie/src/world/tools.c | 4 +- entropie/src/world/world.c | 15 ++++- 32 files changed, 579 insertions(+), 106 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 9123b08..0fd9a8b 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.7.0 +VERSION := 2.8.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 9322bbd..b6bcb35 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -25,21 +25,28 @@ Painted in the rotoscope style of *Waking Life* (2001): ## Arriving -- The game starts in your car: you, Volkram, Jana and Toni come in along the - gravel road and park in the yard (a short drive, look around with the mouse) -- Everybody gets out, Kurti greets you, the boot opens +- The game starts in your car: you, Volkram, Jana and Toni come off the + street onto the gravel road beside the main building and park in the yard + (a short drive; the others sit in the car with you; look around with the mouse) +- Kurti and his sons sit at the fire. First say hello: go over, they welcome + you. Then sit down with them (C) for a little chat with Kurti +- 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) -- The others unload: crates by the fire, the camping things over to the - orchard where the tents will go. Help them: left click at the boot takes - out the next thing (crates need both hands) +- 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 ## Tents -- In the orchard, where the camping things were put down: everybody pitches - their own (Volkram and Jana share the big one) +- On the meadow behind your garages (1.5 m up, the little path left of them), + where the camping things were put down: everybody pitches their own + (Volkram and Jana share the big one) - Left click on a packed tent spreads it out (the door towards you). Then hold left click: the poles in and up, the cover over it, the pegs in (a stone in the other hand: quicker) @@ -55,7 +62,12 @@ The map is Homburgshöhe at Geflügelhof Oderland, traced from the real map and satellite pictures (about 340 x 340 m). - The courtyard is clear for the real game: the fire with its benches, - some firewood, branches and litter, a tree, a few bottles + some firewood, branches, a little litter, a tree +- Behind your garages the ground is 1.5 m higher (a steep bank right behind + them): a small meadow with bushes and trees round it - they stay, nobody + may cut them. The way up: the little path left of the garages +- Right of the main building a small meadow 0.6 m up, bushes behind it and + on its right; the tap is on the main building's right end - An obstacle course in the chicken farm yard: three lanes of logs, crates, benches, walls and blocks, spaced and close together. Beside it the things to try out: crates to carry, heavy rollers to push together, a plank ramp @@ -78,7 +90,8 @@ and satellite pictures (about 340 x 340 m). its door on the side towards the woodpile, and in front of it the frame of tree trunks where the bar will be built - Your garages' doorways have green wooden gates: click to open or shut; open - they swing right round and lie flat against the wall + they swing right round and lie flat against the wall (the car room's door + sits nearer its middle: open, its gates don't block the little path) - 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 @@ -177,7 +190,7 @@ and satellite pictures (about 340 x 340 m). - Pee leaves a yellowish stain and a smell until it fades or is washed out - It all dries again over hours of game time -- Litter lies all over the yard and in the open garages +- A little litter lies in the yard and in the open garages - By hand: pick up two pieces, click on the green waste container to throw them in - Faster with a garbage bag (box by the container): bag in hand, click on trash (or hold the button) to bag it, piece by piece; broken glass too. @@ -193,7 +206,7 @@ and satellite pictures (about 340 x 340 m). - Houses have tiled roofs with overhanging eaves, framed windows with sills and front doors with a step - Roofs are walkable; colliders are built from the same sizes as the meshes -- Bottles lie around: water, beer (five sips) and much stronger vodka +- Bottles: water and beer (five sips) come from the crates you brought - Click a bottle: take it, click again to drink (hold to keep drinking). Dropped, a bottle nearly always falls over; things on a slope slide or roll down it when it's steep enough, and once going they keep going easier @@ -497,9 +510,15 @@ say no more often and don't come to you for a bit. - Everyone is their own person ("Who are you?"). With you came Volkram (calm, splits wood to clear his head), his girlfriend Jana (the fire and the people, likes it tidy) and Toni (first time out here, keen). The house is - Kurti's: an old man (74) who has felled more trees than anyone, with his - sons Martin (about 30, chainsaw, no brooms) and Jonas (about 25, helpful, - talks a lot) + Kurti's: an old man (74, a belly, never takes his shirt off) who has felled + more trees than anyone, with his sons Martin (about 30, chainsaw, no brooms) + and Jonas (about 25, talks a lot) +- Kurti and his sons own the land and lend it to you for the festival: they + don't do your jobs or help with them. Now and then they go into the house + for a while (call them and they come out); the others keep out of the + house unless they need it (the toilet) +- On a break people put their tools down: the orderly side by side, square, + a step back; the others just drop them. An axe put down lies on its side - They may say no: to jobs they don't like, when tired or drunk, or when it isn't needed ("We've got plenty of firewood") - They come to you with jobs that need doing and that they like: low on diff --git a/entropie/STATE.md b/entropie/STATE.md index bf58c02..fe921b5 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -195,6 +195,19 @@ Round 2.7: - You: left click on a packed tent (`tentClick`) - spread out; held at a tent going up (`CLICK_TENT`, 1.2 m): work on it; a hint line says what's next; Z at your tent's door: crawl in. - Test (all three tents): up about 2 min after unloading, the beds in; to bed at night: in their tents, hidden by them. +Round 2.8: +- The car from inside (`Shapes_carInside`): doors up to the windows, the pillars, the roof, the floor, the seats, the back seat, the boot full, the hatch - from the driver's seat you looked through the car. The others ride sitting (`Car_getIn`: SIT_SEAT, seat 0.28 m over the floor, CAR_FLOOR 0.35, `far_off` while riding: nothing to keep out of) and are drawn: you see them beside and behind you, and through the windows. +- The way in (`Site_arrivalPath`): off the street onto the gravel road beside the main building, along it, up past the main building into the yard; parked at court 25 / 19.5 facing the barn (about 25 s). +- First: hello. The friends get out and go to the fire (`ai.greet`, `greetHosts`: round it, sitting down); Kurti, Martin and Jonas sit on the benches at the fire from the start (`scene.c`; idle on a seat - or greeting - they stay sitting with the fire out; `ai_ensureReady` keeps a timer set beforehand). You go over (4.5 m): the welcome, four lines (`game_arrival.c` welcome); sit down (C) near Kurti (or 25 s): a chat, three lines; then the boot opens and the three unload (`startUnloading`). `game_task`: the hint line says what's to do (hello, the chat, the car and the tents) when nothing else is said there; done when the boot is empty and the tents stand. Kurti and his sons don't go in meanwhile. +- Behind your garages (`Site_rise`): 1.5 m up, a steep bank right behind their back wall (1.6 m), the way up by the little path left of them (9 m); a meadow court a 6..40, b -19..-34; bushes round it (`plantMeadows`), six trees that stay (`keep`: not felled by you or anybody, "this one stays"). The forest cleared off it (CLEAR_B0 -40). The tents go there (`Site_campSpot`, doors towards the garages: `Site_campDoor`). Right of the main building: a meadow 0.6 m up (a -6.5..3, b 18..29), bushes behind and on its right; the tap (and hose, can) on the right half's right end (TAP_A / TAP_B). +- The car room's doorway (your garages) z -12 .. -7.5 (was -14 .. -9): open, the left gate ends at the corner - it stuck 2 m out over the little path. +- Crates go into the kitchen of your garages (`Site_drinksSpot`: along its front wall right of the door). +- Heavy in the arms (`Condition_load`: weight over body weight x strength x (0.7 + 0.6 fitness); from 0.08): tiring (TIRE_HOLDING 0.12 x load); a crate is 0.21 (Volkram) .. 0.34 (Kurti). NPCs unloading put it down for a breather under 0.25 stamina, up again over 0.55 ("Phew - heavy one."); you put it down worn out (0.06). Two can carry it: F, then E (lift together). +- Kurti: `FigureLook.belly` (a round part over the belt, figure.frag, MAX_PARTS 36), `keeps_shirt` (no shirt off in the sun). +- Kurti and his sons (`lives_here`): orders refused ("Your festival, your work"), no helping (help calls, talk, tents). Into the house now and then (`ai.c` goInside: INSIDE_CHANCE 0.005/s idle, 90-390 s, `indoors`: hidden, no physics, no AI, at `Site_houseDoor`); called (`callOne`): out in 3 s, "Coming!". The others don't wander into the house (`Site_inHouse`). +- 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). + 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). @@ -343,7 +356,8 @@ 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 the house or their tents +- Rain: showers; people go into their tents (the house is Kurti's), or under the porch / into your garages +- Main building: "the left building's roof peak is too far left" - to be clarified **Tools for us** - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback @@ -352,4 +366,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.7.0 (build ba5c3a3)` +State of `entropie 2.8.0 (build 56bae88)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index b33aee3..413c0ef 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -34,6 +34,7 @@ uniform vec3 hairColor; uniform vec3 shirtColor; uniform vec3 jacketColor; // (top == 3: a jacket over it) uniform bool jacketTied; // the jacket off, tied round the waist by its sleeves +uniform float belly; // 0 .. 1: a belly out in front (Kurti's) uniform vec3 pantsColor; uniform vec3 shoeColor; uniform vec3 stream[4]; // pee or sick: an arc from the body to the ground (figure space) @@ -70,7 +71,7 @@ const float SELF_SHADOW_SKIP = 0.10; // meters // ---------- Body parts ---------- -const int MAX_PARTS = 35; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream) +const int MAX_PARTS = 36; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream) vec3 partA[MAX_PARTS], partB[MAX_PARTS]; float partR1[MAX_PARTS], partR2[MAX_PARTS]; float partBlend[MAX_PARTS]; // smooth-union radius with the rest of the body @@ -135,6 +136,11 @@ void buildBody() { addPart(pelvis + spineDir * (jacket ? 0.0 : 0.05), waist, 0.130 + puff, 0.135 + puff, 0.04, 0.70, sleeve); addPart(waist, chest + spineDir * 0.03 - chestForward * 0.005, 0.135 + puff, 0.160 + puff, 0.04, 0.66, jacket ? M_JACKET : top == 1 ? M_SKIN : M_SHIRT); addPart(joints[J_SHOULDER_L], joints[J_SHOULDER_R], 0.050 + puff, 0.050 + puff, 0.05, 0.75, sleeve); + if (belly > 0.0) { // a belly: round, out in front over the belt + vec3 b = mix(pelvis, chest, 0.3) + chestForward * (0.07 * belly); + addPart(b - spineDir * 0.06, b + spineDir * 0.07, 0.13 + 0.05 * belly, 0.12 + 0.04 * belly, 0.07, 0.95, + jacket ? M_JACKET : top == 1 ? M_SKIN : M_SHIRT); + } // A jacket tied round the waist: a band over the hips, the sleeves knotted in front, the rest hanging down behind if (jacketTied) { diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 7055ce2..f6b27b0 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,12 +1,22 @@ -entropie 2.7.0: tents +entropie 2.8.0: the welcome, the meadow behind the garages, the owners -- Tents in the orchard: everybody pitches their own (Volkram and Jana - share the big one), in stages: spread out, the poles in and up, the - cover over it, the pegs in (a stone in hand: quicker) -- Left click on a packed tent spreads it out (the door towards you), hold - left click to put it up; others come over and give you a hand -- Sleeping bags and mats put down at the door go in -- Z at your tent's door: crawl in and sleep (with your sleeping bag in it: - warmer, rested sooner); the others go to bed in theirs when sleepy -- Unloading lays the sleeping things out in front of where the doors will be -- People find the way round hedges for longer walks +- Arrival: off the street onto the gravel road beside the main building; + from the driver's seat the car is whole (doors, seats, roof) and the + others sit in it with you +- First say hello: Kurti and his sons sit at the fire, the others go over; + they welcome you, sit down for a chat with Kurti, then the work starts + (the boot opens). The hint line says what's to do next +- Behind your garages: a meadow 1.5 m up (steep bank behind them, the way up + by the little path left of them), bushes and trees round it that may not + be cut; the tents go there +- Right of the main building: a meadow 0.6 m up, bushes behind and right; + the tap moved to the main building's right end +- The car room's door nearer its middle: its open gates no longer block the path +- Crates go into the kitchen in your garages; carrying one tires you, put + it down for a moment or carry it with two (F, E) +- Kurti: a belly, never takes his shirt off. Kurti and his sons don't do or + help with your jobs, go into the house now and then (call them: they come + out); the others keep out of the house +- On a break tools are put down: neatly or anyhow, by orderliness; an axe + lies on its side +- No stray bottles, only a little litter diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 166ab32..0cff241 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -6,6 +6,7 @@ #include "game/game_internal.h" #include "world/clothes.h" #include "world/car.h" +#include "people/condition.h" #define START_HOUR 13.0f #define ARRIVE_BY_CAR 1 /* (testing) 0: start standing in the yard, no car */ @@ -272,6 +273,14 @@ static void puttingUp(Game *game) { Hands_holdEdge(p, grip, across, 0.0f); } +/* 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; + if (p->needs.stamina > 0.06f || Condition_load(p) < 0.15f) return; + for (int h = 0; h < 2; h++) if (p->hands.state[h] == HAND_HOLDING) { Hands_drop(p, h); break; } + say(game, "Too heavy - you have to put it down for a moment (or get somebody to take the other side: F, then E)"); +} + void Game_update(Game *game, const Input *input, float dt, float aspect) { /* Asleep: hold Space and the hours fly by */ const Body *pb = &game->player->body; @@ -294,6 +303,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game_climbLadder(game, input, dt); game_climb(game, input, dt); game_arrival(game, dt); + tooHeavy(game); bool warm = game->player->look.top == TOP_JACKET; World_update(&game->world, dt, dt * game->daycycle.time_scale * speed); Object *you = game->player; diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 4366617..8f0858b 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -17,6 +17,7 @@ #define ARRIVAL_POINTS 12 #define ARRIVAL_SAMPLES 160 typedef enum { ARRIVAL_NONE, ARRIVAL_DRIVING, ARRIVAL_PARKED, ARRIVAL_DONE } ArrivalStage; +typedef enum { TASK_NONE, TASK_HELLO, TASK_CHAT, TASK_UNLOAD } ArrivalTask; typedef struct { ArrivalStage stage; struct Object *car; @@ -25,6 +26,11 @@ typedef struct { float length, s, speed, time, park_yaw; int step; /* ... who's out of the car */ bool greeted; + ArrivalTask task; /* what to do now: hello, a chat, then the car and the tents */ + int chat; /* the chat with Kurti: lines said so far ... */ + float chat_wait; /* ... since the hello */ + int welcome; /* Kurti and his sons welcoming you at the fire: lines said so far ... */ + float welcome_t; /* ... until the next */ } Arrival; typedef struct { diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index 4c2ce62..97a15c9 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -854,7 +854,7 @@ static bool game_bootHint(Game *game, char *out, size_t size) { for (int h = 0; h < 2; h++) { const Object *it = p->hands.item[h]; if (!it || p->hands.state[h] != HAND_HOLDING) continue; - if (it->type == OBJ_CRATE) { snprintf(out, size, "The crates go by the fire, next to each other - right click: put it down"); return true; } + if (it->type == OBJ_CRATE) { snprintf(out, size, "The crates go into the kitchen in your garages, along the wall - right click: put it down"); return true; } if (it->type == OBJ_GEAR && it->variant <= GEAR_MAT) { /* (its owner's tent up: in at its door) */ const Object *tent = Tent_of(game->world.objects, game->world.count, it->owner); if (tent && tent->tent_stage >= TENT_COVER) { @@ -863,7 +863,7 @@ static bool game_bootHint(Game *game, char *out, size_t size) { } } if (it->type == OBJ_GEAR) { - snprintf(out, size, "%s - over to the orchard, where the tents go - right click: put it down", + snprintf(out, size, "%s - up to the meadow behind your garages (the path left of them), where the tents go - right click: put it down", it->owner == p ? "Yours" : it->owner ? it->owner->name : "Somebody's"); return true; } @@ -938,7 +938,7 @@ const char *Game_hint(Game *game) { snprintf(game->hint, sizeof(game->hint), at_top ? "Ladder: left click to climb down" : "Ladder: left click to climb - E / Q or right click: take it down"); else - return NULL; + return game_task(game); return game->hint; } diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c index 547bfbf..5091f7d 100644 --- a/entropie/src/game/game_arrival.c +++ b/entropie/src/game/game_arrival.c @@ -2,13 +2,14 @@ * The start: you, Volkram, Jana and Toni come in by car along the gravel road * and park in the yard (you at the wheel, the car drives itself: a short * scene), get out one after the other, the boot opens and the three start - * unloading: crates of beer and water by the fire, the camping things over to + * unloading: crates of beer and water into the garages' kitchen, the camping things up to * the orchard where the tents will go. You help: left click at the boot. */ #include "game/game_internal.h" #include "world/car.h" #include "world/site.h" +#include "world/tent.h" #define DRIVE_TOP 5.5f /* m/s on the gravel road ... */ #define DRIVE_ACCEL 1.6f /* ... speeding up ... */ @@ -41,6 +42,23 @@ static Object *person(Game *game, const char *name) { static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" }; +static void chat(Game *game, float dt); + +/* While you're still to say hello and have a chat: Kurti and his sons stay at the fire. */ +static void hostsStay(Game *game) { + if (game->arrival.task != TASK_HELLO && game->arrival.task != TASK_CHAT) return; + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->type == OBJ_HUMAN && o->lives_here) { o->ai.going_in = false; o->indoors = 0.0f; } + } +} + +static Object *fireplace(Game *game) { + for (int i = 0; i < game->world.count; i++) + if (game->world.objects[i]->type == OBJ_FIREPLACE) return game->world.objects[i]; + return NULL; +} + void game_arrivalStart(Game *game) { Arrival *a = &game->arrival; Vec3 pts[ARRIVAL_POINTS]; @@ -98,9 +116,40 @@ static void getOut(Game *game, const char *name, const char *line) { if (line && o->type == OBJ_HUMAN) Ai_say(o, line); } +/* Over at the fire: Kurti and his sons welcome you, one after the other. */ +#define WELCOME_NEAR 4.5f +static void welcome(Game *game, float dt) { + Arrival *a = &game->arrival; + static const struct { const char *who, *line; } LINES[] = { + { "Kurti", "Welcome, welcome! Good to see you all - sit down, warm up." }, + { "Martin", "You brought your own beer? Smart. Ours is gone." }, + { "Jonas", "The tents go up on the meadow behind the garages - the little path left of them." }, + { "Kurti", "The trees and bushes up there stay, mind. The toilet's in the house. Sit down a moment!" }, + }; + const int n = (int)(sizeof LINES / sizeof LINES[0]); + if (a->task != TASK_HELLO || a->welcome >= n || game->player->riding) return; + Object *kurti = person(game, "Kurti"), *p = game->player; + if (a->welcome == 0) { + if (!kurti || kurti->indoors > 0.0f || hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) > WELCOME_NEAR) return; + a->welcome_t = 0.0f; + } + if ((a->welcome_t -= dt) > 0.0f) return; + Object *o = person(game, LINES[a->welcome].who); + if (o && o->indoors <= 0.0f) { + Ai_say(o, LINES[a->welcome].line); + o->body.look_dir = vec3_normalize(vec3_sub(vec3_add(p->position, vec3(0.0f, 1.6f, 0.0f)), vec3_add(o->position, vec3(0.0f, 1.4f, 0.0f)))); + } + a->welcome++; + a->welcome_t = 3.2f; + if (a->welcome >= n) { a->task = TASK_CHAT; a->chat = 0; a->chat_wait = 0.0f; a->welcome_t = 0.0f; } +} + void game_arrival(Game *game, float dt) { Arrival *a = &game->arrival; Object *car = a->car; + hostsStay(game); + welcome(game, dt); + chat(game, dt); if (a->stage == ARRIVAL_NONE || a->stage == ARRIVAL_DONE || !car) return; a->time += dt; if (a->stage == ARRIVAL_DRIVING) { @@ -128,26 +177,87 @@ void game_arrival(Game *game, float dt) { if (a->step == k && a->time >= STEPS[k].at) { getOut(game, STEPS[k].who < 0 ? NULL : FRIENDS[STEPS[k].who], STEPS[k].line); a->step++; } if (a->step == 1 && !a->greeted) { Object *kurti = person(game, "Kurti"); - if (kurti) Ai_say(kurti, "There you are! Took your time."); - say(game, "You're here. Left click at the boot to help unloading"); + if (kurti) Ai_say(kurti, "Ha - there they are!"); + say(game, "You're here. Kurti and his sons are at the fire - go and say hello"); game->third_person = true; a->greeted = true; } - if (a->step == 4 && a->time >= 3.2f) { - Car_openBoot(car, true); + if (a->step == 4 && a->time >= 3.2f) { /* first: over to the fire, hello to Kurti and his sons */ + Object *fire = fireplace(game); for (int k = 0; k < 3; k++) { Object *o = person(game, FRIENDS[k]); - if (!o) continue; + if (!o || !fire) continue; o->ai.mode = AI_IDLE; - o->ai.unload = true; - o->ai.unload_car = car; - o->ai.unload_time = 0.0f; + o->ai.greet = true; o->ai.side = (float)(k - 1); } + a->task = TASK_HELLO; a->stage = ARRIVAL_DONE; } } +/* Hello said and a chat had: the car unloaded, the tents put up. */ +static void startUnloading(Game *game) { + Arrival *a = &game->arrival; + if (a->car) Car_openBoot(a->car, true); + for (int k = 0; k < 3; k++) { + Object *o = person(game, FRIENDS[k]); + if (!o) continue; + o->ai.greet = false; + o->ai.mode = AI_IDLE; + o->ai.unload = true; + o->ai.unload_car = a->car; + o->ai.unload_time = 0.0f; + o->ai.side = (float)(k - 1); + } + a->task = TASK_UNLOAD; +} + +/* Sitting with them (or a while after the hello): a little chat with Kurti, then the work. */ +#define CHAT_WAIT 25.0f /* s after the hello: the chat starts anyway */ +static void chat(Game *game, float dt) { + Arrival *a = &game->arrival; + if (a->task != TASK_CHAT) return; + static const struct { const char *who, *line; } LINES[] = { + { "Kurti", "So. Four of you this year - and a whole festival to build." }, + { "Kurti", "Build what you like, just keep the fire in its ring. And mind the old trees." }, + { "Volkram", "Deal. Right - let's get the car empty and the tents up before it gets dark." }, + }; + const int n = (int)(sizeof LINES / sizeof LINES[0]); + Object *p = game->player, *kurti = person(game, "Kurti"); + a->chat_wait += dt; + if (a->chat == 0) { + bool sat = p->sitting && kurti && hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) < 5.0f; + if (!sat && a->chat_wait < CHAT_WAIT) return; + a->welcome_t = 0.0f; + } + if ((a->welcome_t -= dt) > 0.0f) return; + if (a->chat >= n) { startUnloading(game); return; } + Object *o = person(game, LINES[a->chat].who); + if (o) Ai_say(o, LINES[a->chat].line); + a->chat++; + a->welcome_t = 3.6f; +} + +/* What to do now (the hint line when nothing else is said there), or NULL. */ +const char *game_task(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; + } + 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"; + case TASK_UNLOAD: return "To do: unload the car (crates into the garages' kitchen, camping things up behind the garages) and put the tents up"; + default: return NULL; + } +} + bool game_arriving(const Game *game) { return game->arrival.stage == ARRIVAL_DRIVING || (game->arrival.stage == ARRIVAL_PARKED && game->player->riding); } @@ -175,7 +285,8 @@ bool game_unloadCar(Game *game) { Object_syncColliders(item); Hands_pickUpObject(p, hand < 0 ? HAND_RIGHT : hand, item); snprintf(game->info, sizeof(game->info), "You take out %s - %s", next, - item->type == OBJ_CRATE ? "the crates go by the fire" : "the camping things go over to the orchard"); + item->type == OBJ_CRATE ? "the crates go into the kitchen in your garages" + : "the camping things go up to the meadow behind your garages"); game->info_about = NULL; say(game, game->info); return true; diff --git a/entropie/src/game/game_internal.h b/entropie/src/game/game_internal.h index 379adeb..d5b1825 100644 --- a/entropie/src/game/game_internal.h +++ b/entropie/src/game/game_internal.h @@ -26,6 +26,7 @@ void game_arrivalStart(Game *game); 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_callHelp(Game *game); void game_callOver(Game *game); diff --git a/entropie/src/game/game_talk.c b/entropie/src/game/game_talk.c index 8ebcf4c..76c931e 100644 --- a/entropie/src/game/game_talk.c +++ b/entropie/src/game/game_talk.c @@ -19,7 +19,7 @@ void game_callHelp(Game *game) { float best = HELP_CALL_RANGE; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; - if (o->type != OBJ_HUMAN || Ai_isHelping(o) || Body_isDown(&o->body)) continue; + if (o->type != OBJ_HUMAN || Ai_isHelping(o) || Body_isDown(&o->body) || o->lives_here) continue; /* (the owners don't help: your festival) */ float d = vec3_length(vec3_sub(o->position, game->player->position)); if (d < best) { best = d; @@ -64,6 +64,10 @@ static bool hears(const Game *game, const Object *o) { /* Calls one of them over: true if they heard it (they come, or say why not). */ static bool callOne(Game *game, Object *o) { + if (o->indoors > 0.0f) { /* in the house: out they come */ + o->indoors = fminf(o->indoors, 3.0f); + Ai_say(o, "Coming!"); + } if (!hears(game, o)) return false; if (o->type == OBJ_DOG) Dog_call(o, game->player); else if (!Ai_call(o, game->player)) Ai_say(o, "Not now!"); diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 9391d63..47b4fac 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -6,6 +6,7 @@ #include "people/ai_internal.h" #include "people/pockets.h" +#include "people/condition.h" #include "world/physics.h" #include "world/nav.h" #include "world/grid.h" @@ -13,6 +14,7 @@ #include "world/clothes.h" #include "world/car.h" #include "world/tent.h" +#include "world/site.h" #include "render/shapes.h" /* ---------- Small helpers ---------- */ @@ -559,6 +561,7 @@ static bool pickWanderGoal(Object *self, Object *const *objects, int count) { float radius = 2.0f + random01(ai) * (WANDER_RADIUS - 2.0f); Vec3 goal = vec3_add(ai->home, vec3(cosf(angle) * radius, 0.0f, sinf(angle) * radius)); if (Object_stinkAt(objects, count, goal) > 0.1f) continue; /* not over there */ + if (!self->lives_here && Site_inHouse(goal.x, goal.z)) continue; /* (the house is theirs: not for a stroll) */ if (!blockedAt(self, NULL, objects, count, goal)) { setGoal(ai, goal); return true; @@ -593,7 +596,7 @@ static void updateIdle(Object *self, Object *const *objects, int count, float dt Object *fire = Camp_fireNear(objects, count, self->position, FIRE_GATHER, true); bool by_fire = fire && flatDistance(fire->position, self->position) < FIRE_SIT_DISTANCE + 1.2f; if (by_fire && !self->sitting) Object_sit(self, Object_seatNear(objects, count, self->position, 1.2f), fire->position); - if (!by_fire) self->sitting = false; + if (!by_fire && !self->seat && !ai->greet) self->sitting = false; /* (on a bench or a chair, or with the others at the fire: they stay put a while) */ Object *player = findPlayer(objects, count); if (by_fire) { Vec3 to = vec3_sub(vec3_add(fire->position, vec3(0.0f, 0.4f, 0.0f)), @@ -1185,7 +1188,9 @@ void ai_ensureReady(Object *self) { ai->character.willing = 0.5f; ai->character.about = "Just here for the festival."; } + float set = ai->timer; /* (a while set beforehand - sitting at the fire at the start - stays) */ rest(ai, 1.0f, PAUSE_MAX); + ai->timer = fmaxf(ai->timer, set); } /* Leaves whatever they were doing: lets go, no job, no tools to fetch. */ @@ -1680,6 +1685,8 @@ static bool fetchPullover(Object *self, Object *const *objects, int count, float it, carried to where it goes (crates by the fire, the camping things where the tents will be), put down; again, till the boot is empty. */ #define UNLOAD_TRIP 70.0f /* s on one trip: stuck, give it up */ +#define UNLOAD_TIRED 0.25f /* stamina: a heavy thing put down for a breather ... */ +#define UNLOAD_ON 0.55f /* ... and taken up again */ static Object *carriedCargo(const Object *self) { for (int h = 0; h < 2; h++) { @@ -1718,8 +1725,22 @@ static bool unloadCar(Object *self, Object *const *objects, int count, float dt) return false; } if (self->sitting) self->sitting = false; + if (ai->unload_rest) { /* put it down for a breather: up again when there's some strength back */ + stand(self); + if (self->needs.stamina < UNLOAD_ON) return true; + ai->unload_rest = false; + ai->unload_time = 0.0f; + } if (item) { /* carrying it there */ for (int h = 0; h < 2; h++) if (self->hands.item[h] == item && self->hands.state[h] == HAND_REACHING) { stand(self); return true; } + if (self->needs.stamina < UNLOAD_TIRED && Condition_load(self) > 0.15f) { /* too heavy for the whole way: down for a moment */ + for (int h = 0; h < 2; h++) if (self->hands.item[h] == item) Hands_drop(self, h); + ai->target = item; + ai->unload_rest = true; + stand(self); + Ai_say(self, random01(ai) < 0.5f ? "Phew - heavy one. A moment." : "Hang on, my arms..."); + return true; + } Vec3 spot = Car_dropSpot(g_world, item); Vec3 from = vec3_sub(self->position, spot); from.y = 0.0f; @@ -1829,6 +1850,7 @@ static bool pitchTents(Object *self, Object *const *objects, int count, float dt if (!job && (ai->mode == AI_IDLE || ai->mode == AI_WANDER)) { for (int i = 0; i < count && !job; i++) { Object *t = objects[i]; + if (self->lives_here) break; /* (the owners: not their job) */ if (t->type != OBJ_TENT || t->tent_stage >= TENT_UP || t->tent_idle > 1.5f || Tent_isTheirs(t, self) || flatDistance(t->position, self->position) > TENT_HELP) continue; if (random01(ai) < HELP_CHANCE * dt * (0.5f + ai->character.willing)) job = t; @@ -1864,6 +1886,50 @@ static bool pitchTents(Object *self, Object *const *objects, int count, float dt return false; } +/* Kurti and his sons live here: now and then they go into the house for a + while (out of sight; called, they come out). */ +#define INSIDE_CHANCE 0.005f /* a second, idle or wandering */ +#define INSIDE_MIN 90.0f /* s in there ... */ +#define INSIDE_MORE 300.0f /* ... up to this much longer */ +#define INSIDE_WALK 60.0f /* m: the door this near */ + +static bool goInside(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (!self->lives_here || self->indoors > 0.0f || self->body.asleep) return false; + Vec3 door = Site_houseDoor(); + if (!ai->going_in) { + if (random01(ai) > INSIDE_CHANCE * dt || flatDistance(door, self->position) > INSIDE_WALK || Hands_freeHand(self) < 0) return false; + ai->going_in = true; + if (random01(ai) < 0.4f) Ai_say(self, random01(ai) < 0.5f ? "I'll be inside for a bit." : "Back in a while."); + } + if (self->sitting) self->sitting = false; + if (!goToPoint(self, door, 0.5f, objects, count, dt)) return true; + ai->going_in = false; + self->indoors = INSIDE_MIN + INSIDE_MORE * random01(ai); + 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) { + Ai *ai = &self->ai; + if (!ai->greet) return false; + if (self->sitting) return false; /* (there: whatever they do sitting) */ + Object *fire = Camp_fireNear(objects, count, self->position, 120.0f, false); + if (!fire) { ai->greet = false; return false; } + Vec3 to = vec3_sub(self->position, fire->position); + to.y = 0.0f; + float turn = ai->side * 0.8f, len = vec3_length(to); + to = len > 0.1f ? vec3_scale(to, 1.0f / len) : vec3(1.0f, 0.0f, 0.0f); + Vec3 dir = vec3(to.x * cosf(turn) - to.z * sinf(turn), 0.0f, to.x * sinf(turn) + to.z * cosf(turn)); + if (!goToPoint(self, vec3_add(fire->position, vec3_scale(dir, 2.7f)), 0.7f, objects, count, dt)) return true; + Object_sit(self, Object_seatNear(objects, count, self->position, 1.5f), fire->position); + rest(ai, 300.0f, 400.0f); + if (random01(ai) < 0.7f) Ai_say(self, random01(ai) < 0.5f ? "Hello hello! Good to be back." : "Kurti! Still the king of the fire?"); + return true; +} + /* Sleepy, their tent up: over to it and in. */ static bool goToBed(Object *self, Object *const *objects, int count, float dt) { if (self->needs.sleepiness < 0.8f || self->body.asleep || self->in_tent || self->body.pose != POSE_UP) return false; @@ -2014,6 +2080,10 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return; /* Unloading the car */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && unloadCar(self, objects, count, dt)) return; + /* Just arrived: hello at the fire */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && greetHosts(self, objects, count, dt)) return; + /* Kurti and his sons: into the house for a while (not while you're being welcomed) */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && goInside(self, objects, count, dt)) return; /* Sleepy: to bed in their tent */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && goToBed(self, objects, count, dt)) return; /* Tents: their own up, a hand with others', the bed things in */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index c98879e..e67c949 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -177,10 +177,13 @@ 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 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) ... */ float tent_time; /* ... this long */ bool tent_said; /* ... said so */ bool unload; /* unloading the car (ai.c unloadCar) ... */ + bool unload_rest; /* ... put it down for a breather */ struct Object *unload_car; float unload_time; /* ... on this trip this long */ float butt_time; /* holding a finished cigarette (Object.butt_held) this long ... */ diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index 0d73ec3..b93a7fe 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -79,7 +79,7 @@ bool ai_nextWoodTask(Object *self, Object *const *objects, int count, Vec3 area, if (Tools_splittable(o) && from_area < WOOD_RADIUS) { job = JOB_SPLIT; rank = 0; } else if (Tools_cuttable(o) && from_area < WOOD_RADIUS) { job = JOB_CUT; rank = 1; } else if (o->type == OBJ_TREE && o->toppled && o->topple >= 1.0f && o->branches > 0 && from_area < WOOD_RADIUS) { job = JOB_DELIMB; rank = 2; } - else if (trees && o->type == OBJ_TREE && !o->toppled && !o->variant && from_area < TREE_RADIUS) { job = JOB_FELL; rank = 3; } /* never the apple trees */ + else if (trees && o->type == OBJ_TREE && !o->toppled && !o->variant && !o->keep && from_area < TREE_RADIUS) { job = JOB_FELL; rank = 3; } /* never the apple trees (or the ones that stay) */ if (job == JOB_NONE) continue; if ((job == JOB_SPLIT || job == JOB_CUT) && claimed(self, objects, count, o)) continue; if (!canGetTool(self, objects, count, job)) continue; /* (the axe may be in someone else's hands) */ @@ -455,7 +455,7 @@ void ai_sunShirt(Object *self, float dt) { (ai->mode == AI_HELP && Tools_held(self, NULL) >= TOOL_AXE); float warm = sunny && hard ? 1.0f + 0.5f * (1.0f - self->needs.stamina) : sunny ? -0.5f : -3.0f; ai->sun_heat = clampf(ai->sun_heat + warm * dt, 0.0f, SUN_SHIRT_OFF * 1.5f); - if (self->look.top == TOP_SHIRT && ai->sun_heat >= SUN_SHIRT_OFF && self->temperament.pride > -0.3f) { + if (self->look.top == TOP_SHIRT && ai->sun_heat >= SUN_SHIRT_OFF && self->temperament.pride > -0.3f && !self->look.keeps_shirt) { self->look.top = self->female ? TOP_CROP : TOP_BARE; if (ai->speech_time <= 0.0f) Ai_say(self, "Phew, it's hot. Off with the shirt."); } else if ((self->look.top == TOP_BARE || self->look.top == TOP_CROP) && ai->sun_heat < SUN_SHIRT_ON) { @@ -712,6 +712,10 @@ static const char *const NOT_NEEDED[ORDER_COUNT] = { bool Ai_willDo(Object *self, Object *const *objects, int count, Order order, bool their_idea, const char **reply) { ai_ensureReady(self); + if (self->lives_here) { /* (Kurti and his sons: it's their land, your festival - your work) */ + *reply = random01(&self->ai) < 0.5f ? "Ha - it's your festival, lad. I'll watch." : "Not my job - we just lend you the land."; + return false; + } const Character *c = &self->ai.character; float like = c->likes[order], tired = 1.0f - self->needs.stamina, drunk = fminf(self->drunk, 1.0f); bool needed = Ai_needed(self, objects, count, order); @@ -874,6 +878,36 @@ static BreakKind pickBreak(Object *self, Object *const *objects, int count) { /* Now and then a break, short or long, their way or another: sitting down, a little walk, a chat, a coffee at the stove. The tool goes down first. True while on it. */ +/* A break: what they work with put down - side by side, square, a step back + from where they worked (the orderly), or just dropped any old way. */ +#define TIDY_TOOLS 0.5f /* this careful (ai_stackCare): put down neatly */ + +static void putToolsDown(Object *self) { + Ai *ai = &self->ai; + Hands_setAside(self, ai->set_aside); + bool tidy = ai_stackCare(self) > TIDY_TOOLS; + Vec3 f = Object_facing(self), side = vec3(-f.z, 0.0f, f.x); + for (int k = 0; k < 2; k++) { + Object *item = ai->set_aside[k]; + if (!item) continue; + bool flat = item->type == OBJ_AXE || item->type == OBJ_SHEARS || item->type == OBJ_LADDER; + if (tidy) { + Vec3 at = vec3_add(self->position, vec3_add(vec3_scale(f, -0.9f), vec3_scale(side, k ? 0.35f : -0.35f))); + item->rotation = flat ? (item->type == OBJ_LADDER ? vec3(PI_F * 0.5f, self->rotation.y + PI_F * 0.5f, 0.0f) + : vec3(0.0f, self->rotation.y, PI_F * 0.5f)) /* (on its side, the handle across) */ + : vec3(0.0f, self->rotation.y, 0.0f); + Object_syncColliders(item); + item->position = vec3(at.x, item->position.y, at.z); + } else { + float turn = (random01(ai) - 0.5f) * 2.5f; + item->rotation.y += turn; + item->position = vec3_add(item->position, vec3((random01(ai) - 0.5f) * 0.5f, 0.0f, (random01(ai) - 0.5f) * 0.5f)); + } + item->velocity = vec3_zero(); + Object_syncColliders(item); + } +} + static bool takeBreak(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; const Character *c = &ai->character; @@ -891,7 +925,7 @@ static bool takeBreak(Object *self, Object *const *objects, int count, float dt) ai->chat_with = ai->break_now == BREAK_CHAT ? chatPartner(self, objects, count) : NULL; ai->chat_line = 0.0f; ai->chat_said = 0; - if (self->hands.item[HAND_LEFT] || self->hands.item[HAND_RIGHT]) Hands_setAside(self, ai->set_aside); /* (the tool down: taken again after) */ + if (self->hands.item[HAND_LEFT] || self->hands.item[HAND_RIGHT]) putToolsDown(self); /* (taken again after) */ if (self->worn > 0.45f && random01(ai) < 0.5f) Ai_say(self, "Ugh, I'm still beat from yesterday."); else if (ai->break_now == BREAK_WALK) Ai_say(self, long_one ? "I need some air. Back in a bit." : "I'll stretch my legs for a bit."); else if (ai->break_now == BREAK_COFFEE) Ai_say(self, "Coffee time."); diff --git a/entropie/src/people/condition.c b/entropie/src/people/condition.c index 22ef003..5e00c08 100644 --- a/entropie/src/people/condition.c +++ b/entropie/src/people/condition.c @@ -30,6 +30,8 @@ #define TIRE_WORKING 0.040f /* tool strokes: a few minutes of chopping and you need a break */ #define TIRE_PUSHING 0.016f #define TIRE_CARRYING 0.024f +#define TIRE_HOLDING 0.12f /* ... carrying something in the arms, by how heavy it is for them (see Condition_load) */ +#define HOLD_EASY 0.08f /* ... light (a bottle, a bag): nothing */ #define TIRED_CAP 0.55f /* tired out: the most stamina this much lower ... */ #define TIRED_EFFORT 0.6f /* ... effort this much harder ... */ #define TIRED_REST 0.5f /* ... resting this much slower */ @@ -91,6 +93,17 @@ static float excess(float value, float from) { return clampf((value - from) / (1.0f - from), 0.0f, 1.0f); } +float Condition_load(const Object *self) { + float held = 0.0f; + for (int h = 0; h < 2; h++) { + const Object *it = self->hands.item[h]; + if (!it || self->hands.state[h] != HAND_HOLDING || (h == 1 && it == self->hands.item[0])) continue; + held += it->weight; + } + if (held <= 0.0f || self->weight <= 0.0f) return 0.0f; + return held / (self->weight * (self->strength / 100.0f) * (0.7f + 0.6f * self->fitness)); +} + /* Hunger, thirst, bladder, sleepiness over the day; stamina from work and rest. */ static void needs(Object *self, float dt, float game_dt) { Needs *n = &self->needs; @@ -103,6 +116,8 @@ static void needs(Object *self, float dt, float game_dt) { bool pushing = self->hands.grabbed && !carrying && self->ground_speed > 0.2f; float effort = working ? TIRE_WORKING : carrying ? TIRE_CARRYING : pushing ? TIRE_PUSHING : self->ground_speed > 4.0f ? TIRE_RUNNING : 0.0f; + float load = Condition_load(self); /* (something heavy in the arms: a crate - tiring for the weaker) */ + if (load > HOLD_EASY) effort = fmaxf(effort, TIRE_HOLDING * (load - HOLD_EASY)); float rest = asleep ? REST_SLEEPING : self->sitting ? REST_STANDING * 2.0f : self->ground_speed < 0.3f ? REST_STANDING : REST_WALKING; /* Tired (late in the day, drunk, sluggish): less to give - a lower ceiling, diff --git a/entropie/src/people/condition.h b/entropie/src/people/condition.h index 0752bac..26575bf 100644 --- a/entropie/src/people/condition.h +++ b/entropie/src/people/condition.h @@ -17,4 +17,8 @@ void Condition_useGrid(struct Grid *grid); void Condition_update(struct Object *self, struct Object *const *objects, int count, float dt, float game_dt); +/* How heavy what they hold in their arms is for them: its weight over what + they can carry well (body weight, strength, fitness); 0.08 and less: light. */ +float Condition_load(const struct Object *self); + #endif /* PEOPLE_CONDITION_H */ diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 5cfa984..1bff9f5 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -173,7 +173,8 @@ void Hands_drop(Object *who, int hand) { } if (item->type == OBJ_AXE || item->type == OBJ_SHEARS || item->type == OBJ_LADDER) { /* put down flat, where it was */ Vec3 center = Object_colliderCenter(item, 0); - item->rotation = vec3(PI_F / 2.0f, who->rotation.y, 0.0f); + item->rotation = item->type == OBJ_LADDER ? vec3(PI_F / 2.0f, who->rotation.y, 0.0f) + : vec3(0.0f, who->rotation.y - PI_F / 2.0f, PI_F / 2.0f); /* (a tool on its side: the blade flat, not up) */ Object_syncColliders(item); item->position = vec3_sub(center, item->colliders[0].offset); } diff --git a/entropie/src/people/talk.c b/entropie/src/people/talk.c index d2ff540..6df0805 100644 --- a/entropie/src/people/talk.c +++ b/entropie/src/people/talk.c @@ -198,6 +198,13 @@ static Object *pileOwnPlace(World *world, const Object *npc) { return World_add(world, pile) ? pile : NULL; } +/* Kurti and his sons lend you the land - your festival, your work: no help from them. */ +static bool ownerSaysNo(Talk *talk, const Object *npc) { + if (!npc->lives_here) return false; + wrapUp(talk, "Your festival, your work - I'll be at the fire."); + return true; +} + static bool woodJob(Order what) { return what == ORDER_FIREWOOD || what == ORDER_TREES; } /* A job: they may say no. Wood jobs: unless they need to be shown how, or @@ -318,7 +325,7 @@ static void answer(Talk *talk, World *world, Object *player, int choice) { } else if (choice == 1) go(talk, NODE_TASKS); else if (choice == 2) { if (npc->ai.character.willing < 0.3f && npc->needs.stamina < 0.5f) wrapUp(talk, "Maybe later, I'm beat."); - else { Ai_help(npc, player); wrapUp(talk, shy(npc) ? "Okay..." : "Sure, I'm with you."); } + else { if (!ownerSaysNo(talk, npc)) { Ai_help(npc, player); wrapUp(talk, shy(npc) ? "Okay..." : "Sure, I'm with you."); } } } else if (choice == 3 && lighterTopic(player, npc) != LIGHTER_NONE) { talkLighter(talk, player, lighterTopic(player, npc)); } else if (choice == 3) { @@ -340,19 +347,19 @@ static void answer(Talk *talk, World *world, Object *player, int choice) { case NODE_OFFER: if (choice == 1) order(talk, world, player, ORDER_FIREWOOD, true); else if (choice == 2) order(talk, world, player, ORDER_TREES, true); - else if (choice == 3) { Ai_help(npc, player); wrapUp(talk, "Sure, I'm with you."); } + else if (choice == 3) { if (!ownerSaysNo(talk, npc)) { Ai_help(npc, player); wrapUp(talk, "Sure, I'm with you."); } } else wrapUp(talk, "Alright."); break; case NODE_SUGGEST: if (choice == 1) order(talk, world, player, npc->ai.suggested != ORDER_NONE ? npc->ai.suggested : ORDER_FIREWOOD, true); - else if (choice == 2) { Ai_help(npc, player); wrapUp(talk, "Sure, I'm with you."); } + else if (choice == 2) { if (!ownerSaysNo(talk, npc)) { Ai_help(npc, player); wrapUp(talk, "Sure, I'm with you."); } } else wrapUp(talk, "Maybe later then."); break; case NODE_WORKER: if (choice == 1) wrapUp(talk, "Will do."); else if (choice == 2 && woodJob(npc->ai.order)) { Ai_show(npc, SHOW_PLACE, player); wrapUp(talk, "Lead the way."); } else if (choice == 2) { Ai_stopWork(npc); go(talk, NODE_TASKS); } - else if (choice == 3) { Ai_stopWork(npc); Ai_help(npc, player); wrapUp(talk, "Sure!"); } + else if (choice == 3) { Ai_stopWork(npc); if (!ownerSaysNo(talk, npc)) { Ai_help(npc, player); wrapUp(talk, "Sure!"); } } else if (choice == 5) { int tool = talkTool(npc); if (!(npc->ai.banned & (1u << tool))) { diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index d99787b..6e6c098 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -30,6 +30,8 @@ typedef struct { int top; /* TOP_*: the shirt on, or off (working in the sun), or a jacket over it (cold nights) */ Vec3 jacket; /* the jacket's colour */ bool jacket_tied; /* the jacket off and along with them: tied round the waist by its sleeves */ + float belly; /* 0 .. 1: a belly (Kurti's) */ + bool keeps_shirt; /* never takes the shirt off, sun or no sun */ } FigureLook; enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET }; diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index ee14cd8..a12b05b 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -103,6 +103,7 @@ static bool loadShaders(Renderer *r) { f->undress = glGetUniformLocation(f->program, "undress"); f->top = glGetUniformLocation(f->program, "top"); f->jacket_tied = glGetUniformLocation(f->program, "jacketTied"); + f->belly = glGetUniformLocation(f->program, "belly"); f->cig_a = glGetUniformLocation(f->program, "cigA"); f->uri_a = glGetUniformLocation(f->program, "uriA"); f->uri_b = glGetUniformLocation(f->program, "uriB"); @@ -544,6 +545,7 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { glUniform1f(f->undress, sk->undress); glUniform1i(f->top, pose->look.top); glUniform1i(f->jacket_tied, pose->look.jacket_tied && pose->look.top != TOP_JACKET ? 1 : 0); + glUniform1f(f->belly, pose->look.belly); setVec3(f->cig_a, sk->cig_a); setVec3(f->uri_a, sk->uri_a); setVec3(f->uri_b, sk->uri_b); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index d39d4b8..849b00b 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -68,7 +68,7 @@ typedef struct { GLint skin, hair, shirt, pants, shoes, hair_style, long_sleeves; GLint head_forward, head_up, chest_forward, waist_bend, face, expression; GLint stream, stream_kind, stream_width; - GLint garment, undress, top, jacket_tied; + GLint garment, undress, top, jacket_tied, belly; GLint cig_a, cig_tip, cig_glow, jacket, uri_a, uri_b, uri; GLint species, tail_a, tail_b; GLint view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon; diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 0866087..0ba242c 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -925,8 +925,29 @@ Mesh Shapes_carInside(Vec3 color) { addBlock(&b, vec3(0.0f, 0.3f, 0.0f), vec3(0.05f, 0.62f, 0.05f), color); transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)s * w * 0.44f, 0.9f, 0.8f)), mat4_rotate_x(-0.55f))); } - addBlock(&b, vec3(0.0f, 1.5f, -0.9f), vec3(w * 0.92f, 0.04f, 2.2f), roof); /* the roof lining */ - addBlock(&b, vec3(0.0f, 1.47f, 0.2f), vec3(0.25f, 0.05f, 0.06f), dash); /* the mirror */ + const float back = -l * 0.5f, cab0 = back + 0.12f, cab1 = 0.45f, cab = cab1 - cab0, mid = (cab0 + cab1) * 0.5f; + const Vec3 door = vec3_scale(color, 0.75f), seat = { 0.24f, 0.24f, 0.27f }, floor = { 0.12f, 0.12f, 0.13f }; + addBlock(&b, vec3(0.0f, 1.47f, mid), vec3(w * 0.92f, 0.04f, cab), roof); /* the roof lining */ + addBlock(&b, vec3(0.0f, 1.44f, 0.2f), vec3(0.25f, 0.05f, 0.06f), dash); /* the mirror */ + addBlock(&b, vec3(0.0f, 0.33f, mid), vec3(w * 0.9f, 0.04f, cab), floor); /* the floor */ + for (int s = -1; s <= 1; s += 2) { + float x = (float)s * (w * 0.5f - 0.04f); + addBlock(&b, vec3(x, 0.66f, mid), vec3(0.06f, 0.64f, cab), door); /* the doors, up to the windows */ + addBlock(&b, vec3(x, 1.2f, -0.6f), vec3(0.07f, 0.52f, 0.12f), door); /* the middle pillar ... */ + addBlock(&b, vec3(x, 1.2f, cab0 + 0.12f), vec3(0.07f, 0.52f, 0.24f), door); /* ... and the back one */ + addBlock(&b, vec3((float)s * 0.38f, 0.52f, -0.02f), vec3(0.5f, 0.12f, 0.5f), seat); /* the front seats */ + first = b.vertex_count; + addBlock(&b, vec3(0.0f, 0.28f, 0.0f), vec3(0.48f, 0.56f, 0.1f), seat); + addBlock(&b, vec3(0.0f, 0.62f, 0.0f), vec3(0.22f, 0.14f, 0.1f), seat); /* (the headrest) */ + transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)s * 0.38f, 0.55f, -0.3f)), mat4_rotate_x(-0.2f))); + } + addBlock(&b, vec3(0.0f, 0.52f, -1.05f), vec3(w * 0.84f, 0.12f, 0.5f), seat); /* the back seat */ + addBlock(&b, vec3(0.0f, 0.87f, -1.34f), vec3(w * 0.84f, 0.62f, 0.1f), seat); + addBlock(&b, vec3(0.0f, 0.6f, back + 0.55f), vec3(w * 0.84f, 0.04f, 0.8f), floor); /* the boot, full */ + static const Vec3 LOAD[] = { { 0.62f, 0.16f, 0.12f }, { 0.22f, 0.42f, 0.72f }, { 0.70f, 0.18f, 0.16f }, { 0.30f, 0.45f, 0.25f }, { 0.85f, 0.75f, 0.2f } }; + for (int k = 0; k < 5; k++) + addBlock(&b, vec3(-0.6f + 0.3f * (float)k, 0.78f, back + 0.45f + 0.1f * (float)(k % 2)), vec3(0.27f, 0.32f, 0.36f), LOAD[k]); + addBlock(&b, vec3(0.0f, 1.0f, back + 0.04f), vec3(w * 0.86f, 0.62f, 0.04f), door); /* the hatch from inside */ return Mesh_fromBuilder(&b); } diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c index df0b1ec..07a1e2a 100644 --- a/entropie/src/world/car.c +++ b/entropie/src/world/car.c @@ -13,6 +13,8 @@ #define BOOT_BACK 0.8f /* standing this far behind it to reach into the boot */ #define SET_DOWN 0.3f /* what comes out is set down this far behind it */ #define BOTTLE_KG 0.6f +#define CAR_FLOOR 0.35f /* the floor inside this high */ +#define SEAT_UP 0.28f /* the seats this high over it */ static const Vec3 CRATE_BOTTLE = { 0.07f, 0.25f, 0.0f }; @@ -131,8 +133,10 @@ void Car_place(World *world, Object *car, Vec3 at, float yaw) { for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->riding != car) continue; - Vec3 p = local(car, seatAt(o->ride_seat)); - o->position = vec3(p.x, at.y + 0.3f, p.z); + Vec3 p = local(car, vec3_add(seatAt(o->ride_seat), vec3(0.0f, 0.0f, 0.18f))); /* (the feet under the dashboard) */ + o->position = vec3(p.x, at.y + CAR_FLOOR, p.z); + o->sit_at = o->position; + o->sit_yaw = yaw; o->rotation.y = yaw; o->velocity = vec3_zero(); Object_syncColliders(o); @@ -144,7 +148,11 @@ void Car_getIn(Object *car, Object *who, int seat) { who->ride_seat = seat; who->velocity = vec3_zero(); who->movement = vec3_zero(); - who->sitting = false; + who->sitting = true; /* (sitting in it: seen through the windows) */ + who->seat = NULL; + who->sit_moving = false; + who->sit_style = SIT_SEAT; + who->body.seat_height = SEAT_UP; } void Car_getOut(Object *who) { @@ -152,6 +160,7 @@ void Car_getOut(Object *who) { if (!car) return; Vec3 door = Car_door(car, who->ride_seat); who->riding = NULL; + who->sitting = false; who->position = vec3(door.x, car->position.y, door.z); Vec3 out = vec3_sub(door, car->position); who->rotation.y = atan2f(out.z, out.x) + PI_F / 2.0f; /* (facing away from the car) */ @@ -190,10 +199,7 @@ Vec3 Car_dropSpot(const World *world, const Object *item) { return vec3_add(at, vec3_add(vec3_scale(along, a), vec3_scale(across, CAMP_BEDS_OUT))); } -Vec3 Car_campDoor(void) { - Vec3 along = Site_campAlong(); - return vec3(-along.z, 0.0f, along.x); -} +Vec3 Car_campDoor(void) { return Site_campDoor(); } Object *Crate_takeBottle(World *world, Object *crate) { if (crate->type != OBJ_CRATE || crate->stored <= 0) return NULL; diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 77b11c8..f87de98 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -1322,6 +1322,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { case OBJ_WOODPILE: snprintf(out, size, "Woodpile - %d pieces of firewood", obj->stored); break; case OBJ_TREE: if (!obj->toppled && obj->variant) snprintf(out, size, "Apple tree - pick an apple"); + else if (!obj->toppled && obj->keep) snprintf(out, size, "Tree - this one stays (Kurti's rule)"); else if (!obj->toppled) snprintf(out, size, "Tree - fell it with the axe or the chainsaw"); else snprintf(out, size, "Fallen tree - %d branch%s left", obj->branches, obj->branches == 1 ? "" : "es"); break; diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index ae2a6c4..4c93460 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -161,6 +161,9 @@ typedef struct Object { struct CarLoad *load; /* cars: what's in the boot (malloc'd, freed with the car) */ 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) */ + 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 ... */ struct Object *owner2; /* ... tents: and who shares it */ int tent_stage; /* tents: TentStage ... */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index 29bac22..e451e55 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -639,6 +639,12 @@ static Vec3 rotateAbout(Vec3 v, Vec3 axis, float angle) { static void settleOnGround(Object *obj, const Terrain *terrain) { if (obj->is_character || obj->type == OBJ_TREE || obj->type == OBJ_DOG || obj->collider_count == 0 || obj->lifted || obj->is_held || obj->thrown_by || (obj->topple > 0.0f && obj->topple < 1.0f) || obj->touched) return; + if ((obj->type == OBJ_AXE || obj->type == OBJ_SHEARS) && fabsf(obj->colliders[0].axes[1].y) < 0.7f && + fabsf(obj->colliders[0].axes[0].y) < 0.7f) { /* (an axe lying with its blade up or down: it tips onto its side) */ + Vec3 handle = obj->colliders[0].axes[1]; + obj->rotation = vec3(0.0f, atan2f(handle.z, handle.x) + PI_F, PI_F / 2.0f); + Object_syncColliders(obj); + } const Collider *c = &obj->colliders[0]; const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z }; int k = 0; @@ -1077,7 +1083,7 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain if (obj->over_t > 0.0f) { stepOverMove(obj, terrain, dt); return; } /* (a set move) */ /* At rest: skip most steps (a push or a fall wakes it) */ - if (obj->climbing || obj->on_grill || obj->toilet || obj->riding) return; /* moved by hand, or out of sight */ + if (obj->climbing || obj->on_grill || obj->toilet || obj->riding || obj->indoors > 0.0f) return; /* moved by hand, or out of sight */ if (obj->sitting && obj->sit_moving && obj->seat) return; /* (stepping over to a seat, turning, back onto it: a set move) */ bool still = !obj->is_character && !obj->lifted && !obj->thrown_by && obj->is_grounded && vec3_length(obj->movement) < 0.01f && diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index 7c41ad9..972d136 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -14,8 +14,6 @@ typedef struct { #define HUMAN_SIZE { 0.5f, 1.75f, 0.25f } #define NO_ROTATION { 0, 0, 0 } -#define BOTTLE_SIZE { 0.07f, 0.25f, 0.0f } -#define VODKA_SIZE { 0.08f, 0.30f, 0.0f } /* The yard where it all starts, around Site_yard(). The player must be the first entry. Order also defines physics order. */ @@ -32,21 +30,9 @@ static const Spawn SCENE[] = { /* Props (the ground itself is the terrain, see terrain.c) */ { OBJ_TREE, PALETTE_WARM, { 3, 0, 2 }, { 3.0f, 3.0f, 0.0f }, 0, NO_ROTATION }, /* canopy, trunk */ - /* A few bottles of beer (R drinks) */ - { OBJ_BOTTLE, PALETTE_WARM, { -0.6f, 0.3f, -1.2f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_BOTTLE, PALETTE_WARM, { -0.4f, 0.3f, -1.3f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_BOTTLE, PALETTE_WARM, { -0.5f, 0.3f, -1.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_BOTTLE, PALETTE_WARM, { 4.0f, 0.3f, -3.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_BOTTLE, PALETTE_WARM, { -2.0f, 0.3f, -6.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - /* ... and vodka, for getting there faster */ - { OBJ_VODKA, PALETTE_WARM, { -0.8f, 0.3f, -1.5f }, VODKA_SIZE, 0.9f, NO_ROTATION }, - - /* (the tools are in the tool room of your garages, see site.c) */ - /* Food and water: an apple crate and a water barrel that never run out, and a few to take along */ - { OBJ_WATER, PALETTE_WARM, { 6.5f, 0.3f, -1.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_WATER, PALETTE_WARM, { 6.7f, 0.3f, -1.3f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, + /* A few apples (the drinks come in the crates from your car) */ { OBJ_APPLE, PALETTE_WARM, { 2.2f, 0.2f, 1.2f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 3.9f, 0.2f, 1.4f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 2.6f, 0.2f, 3.1f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, @@ -119,7 +105,8 @@ static const FigureLook LOOKS[] = { { .skin = { 0.72f, 0.52f, 0.38f }, .hair = { 0.12f, 0.10f, 0.10f }, .shirt = { 0.30f, 0.62f, 0.60f }, .pants = { 0.55f, 0.48f, 0.36f }, .shoes = { 0.32f, 0.22f, 0.16f }, .height = 1.74f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* Toni */ { .skin = { 0.90f, 0.72f, 0.62f }, .hair = { 0.82f, 0.82f, 0.80f }, .shirt = { 0.55f, 0.50f, 0.42f }, - .pants = { 0.33f, 0.36f, 0.30f }, .shoes = { 0.20f, 0.16f, 0.12f }, .height = 1.70f, .hair_style = HAIR_SHORT, .long_sleeves = true }, /* Kurti: grey, a worn shirt */ + .pants = { 0.33f, 0.36f, 0.30f }, .shoes = { 0.20f, 0.16f, 0.12f }, .height = 1.70f, .hair_style = HAIR_SHORT, .long_sleeves = true, + .belly = 1.0f, .keeps_shirt = true }, /* Kurti: grey, a worn shirt (always on), a belly */ { .skin = { 0.88f, 0.66f, 0.50f }, .hair = { 0.30f, 0.22f, 0.16f }, .shirt = { 0.20f, 0.24f, 0.40f }, .pants = { 0.24f, 0.26f, 0.30f }, .shoes = { 0.18f, 0.14f, 0.12f }, .height = 1.84f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* Martin: broad, work clothes */ { .skin = { 0.90f, 0.70f, 0.55f }, .hair = { 0.36f, 0.26f, 0.18f }, .shirt = { 0.80f, 0.20f, 0.18f }, @@ -208,6 +195,29 @@ bool Scene_finish(World *world) { if (world->objects[i]->type == OBJ_WOODPILE) pile = world->objects[i]; for (int i = 0; i < world->count && pile; i++) if (world->objects[i]->type == OBJ_HUMAN) Ai_setWoodpile(world->objects[i], pile); + /* Kurti and his sons sit at the fire when you come (they welcome you when you go over) */ + Object *fire = NULL, *benches[3] = { NULL, NULL, NULL }; + int nb = 0; + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (o->type == OBJ_FIREPLACE && !fire) fire = o; + } + for (int i = 0; i < world->count && fire && nb < 3; i++) { + Object *o = world->objects[i]; + if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH && vec3_length(vec3_sub(o->position, fire->position)) < 5.0f) benches[nb++] = o; + } + for (int i = 0, k = 0; i < world->count && k < nb; i++) { + Object *o = world->objects[i]; + if (o->type != OBJ_HUMAN || !o->lives_here) continue; + Object *bench = benches[k++]; + Vec3 out = vec3_normalize(vec3_sub(fire->position, bench->position)); + o->position = vec3_add(bench->position, vec3(out.x * 0.55f, 0.0f, out.z * 0.55f)); + o->position.y = Terrain_height(&world->terrain, o->position.x, o->position.z); + Object_syncColliders(o); + Object_sit(o, bench, fire->position); + o->ai.mode = AI_IDLE; + o->ai.timer = 150.0f + 30.0f * (float)k; /* (sitting there a while) */ + } return addRamp(world, testCorner()); } @@ -254,6 +264,7 @@ Object *Scene_load(World *world) { obj->filters = FILTERS[characters % TEMPERAMENT_COUNT]; obj->filter_habit = FILTER_HABIT[characters % TEMPERAMENT_COUNT]; obj->pocket_tray = POCKET_TRAY[characters % TEMPERAMENT_COUNT]; + obj->lives_here = characters >= 4 && characters <= 6; /* (Kurti and his sons) */ obj->keys = KEYS_IN_POCKET[characters % TEMPERAMENT_COUNT]; obj->work_trousers = characters == 0; /* (you: work trousers, plenty of pockets) */ obj->craving = 0.3f + 0.15f * (float)(characters % 3); diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index 6fd60cf..bf94d54 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -236,8 +236,8 @@ static const Piece WALLED[] = { { OBJ_BOX_STATIC, { 19.25f, 3, 0.0f }, { 0.5f, 2, 29.0f }, { 0, 0, 0 } }, /* front, above the doors */ { OBJ_BOX_STATIC, { 19.25f, 0, 13.5f }, { 0.5f, 3, 2.0f }, { 0, 0, 0 } }, /* front, between doors */ { OBJ_BOX_STATIC, { 19.25f, 0, 7.0f }, { 0.5f, 3, 8.0f }, { 0, 0, 0 } }, - { OBJ_BOX_STATIC, { 19.25f, 0, -5.0f }, { 0.5f, 3, 8.0f }, { 0, 0, 0 } }, - { OBJ_BOX_STATIC, { 19.25f, 0, -14.25f }, { 0.5f, 3, 0.5f }, { 0, 0, 0 } }, + { OBJ_BOX_STATIC, { 19.25f, 0, -4.25f }, { 0.5f, 3, 6.5f }, { 0, 0, 0 } }, /* (the car room's doorway nearer its middle: */ + { OBJ_BOX_STATIC, { 19.25f, 0, -13.25f }, { 0.5f, 3, 2.5f }, { 0, 0, 0 } }, /* open, its gates lie on the wall, not the path) */ { OBJ_BOX_STATIC, { 23.25f, 5, 0.0f }, { 8.5f, 0.5f, 29.0f }, { 0, 0, 0 } }, /* roof */ }; static const Vec3 WALLED_CENTER = { 23.25f, 0.0f, 0.0f }; @@ -312,6 +312,8 @@ static const CourtDoorBuilding COURT[] = { #define FRAME_B 16.5f #define YARD_A 20.0f /* where you start, and the things to play with */ #define YARD_B 12.0f +#define TAP_A 3.0f /* the tap: on the right end of the main building */ +#define TAP_B 17.25f #define FIRE_A 16.0f /* the fireplace: near the main building and your garages */ #define FIRE_B -4.0f #define BENCH_RADIUS 2.8f /* log benches round it */ @@ -339,7 +341,7 @@ static const float APPLE_TREES[][2] = { { 25.0f, 37.0f }, { 37.0f, 36.0f } }; /* No forest round the courtyard, the orchard and the garages */ #define CLEAR_A0 -10.0f #define CLEAR_A1 58.0f -#define CLEAR_B0 -24.0f +#define CLEAR_B0 -40.0f #define CLEAR_B1 58.0f /* ---------- Other buildings ---------- */ @@ -527,22 +529,76 @@ Vec3 Site_groundColor(float x, float z, Vec3 meadow) { Vec3 Site_court(float a, float b) { return court(a, b); } -Vec3 Site_drinksSpot(void) { return court(FIRE_A + 4.5f, FIRE_B + 3.0f); } +/* ---------- Higher ground ---------- + * Behind your garages the ground is 1.5 m higher: a steep bank right behind + * their back wall, a gentler way up by the little path left of them, a small + * meadow up there (the tents) with bushes and trees round it. Right of the + * main building (towards the gravel road) a meadow 0.6 m up, bushes behind it + * and on its right. */ +#define CAMP_RISE 1.5f +#define CAMP_A0 6.0f /* the meadow behind the garages, along them ... */ +#define CAMP_A1 40.0f +#define CAMP_B_WALL -19.2f /* ... from just behind their back wall ... */ +#define CAMP_B1 -34.0f /* ... to its bushes at the back */ +#define SIDE_RISE 0.6f +#define SIDE_A0 -6.5f /* the meadow right of the main building */ +#define SIDE_A1 3.0f +#define SIDE_B0 18.0f +#define SIDE_B1 29.0f + +static void courtOf(float x, float z, float *a, float *b) { + Vec3 rel = vec3_sub(vec3(x, 0.0f, z), at(COURT_AT)), along = mapDir(COURT_ANGLE), across = mapDir(COURT_ANGLE - 90.0f); + *a = rel.x * along.x + rel.z * along.z; + *b = rel.x * across.x + rel.z * across.z; +} + +float Site_rise(float x, float z) { + float a, b; + courtOf(x, z, &a, &b); + /* behind the garages: the bank (steep behind them, the path's slope at their left end) */ + float path = smoothstepf(34.0f, 37.0f, a); + float edge = lerpf(CAMP_B_WALL, -13.5f, path), ramp = lerpf(1.6f, 9.0f, path); + float camp = CAMP_RISE * smoothstepf(edge, edge - ramp, b) + * smoothstepf(CAMP_A0 - 4.0f, CAMP_A0, a) * (1.0f - smoothstepf(CAMP_A1 + 1.0f, CAMP_A1 + 6.0f, a)) + * (1.0f - smoothstepf(CAMP_B1 - 1.0f, CAMP_B1 - 6.0f, b)); + float side = SIDE_RISE * smoothstepf(SIDE_A1 + 1.5f, SIDE_A1, a) * smoothstepf(SIDE_A0 - 2.0f, SIDE_A0, a) + * smoothstepf(SIDE_B0, SIDE_B0 + 1.5f, b) * (1.0f - smoothstepf(SIDE_B1, SIDE_B1 + 2.0f, b)); + return fmaxf(camp, side); +} + +/* Your garages as placed (see Site_build): for spots inside them later. */ +static struct { Vec3 origin; float yaw, zsign; bool set; } g_garages; + +Vec3 Site_drinksSpot(void) { /* in the kitchen of your garages, along its front wall right of the door */ + if (!g_garages.set) return court(FIRE_A + 4.5f, FIRE_B + 3.0f); + return placed(vec3(20.1f, 0.0f, 6.0f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); +} + +Vec3 Site_houseDoor(void) { return court(MANOR_A + 6.0f, MANOR_B - 7.6f); } /* (out in front of the main building's left door) */ + +bool Site_inHouse(float x, float z) { + float a, b; + courtOf(x, z, &a, &b); + return a > MANOR_A - 5.5f && a < MANOR_A + 7.5f && b > MANOR_B - 18.5f && b < MANOR_B + 18.5f; +} Vec3 Site_campSpot(int k) { - static const float AT[3][2] = { { 19.0f, 35.0f }, { 42.0f, 35.0f }, { 30.0f, 33.5f } }; /* (clear of the two apple trees) */ + static const float AT[3][2] = { { 13.0f, -27.5f }, { 22.0f, -28.0f }, { 31.0f, -27.5f } }; /* (up on the meadow behind your garages) */ k = k < 0 ? 0 : k > 2 ? 2 : k; return court(AT[k][0], AT[k][1]); } Vec3 Site_campAlong(void) { return mapDir(COURT_ANGLE); } +Vec3 Site_campDoor(void) { return mapDir(COURT_ANGLE - 90.0f); } /* (towards the garages' back wall) */ + void Site_arrivalPath(Vec3 *points, int *n, float *park_yaw) { - static const float AT[][2] = { { 80.0f, 38.5f }, { 62.0f, 27.5f }, { 45.0f, 26.2f }, { 33.0f, 25.8f }, { 26.5f, 24.0f }, - { 24.3f, 21.5f }, { 24.0f, 19.0f } }; /* (in off the road, clear of the frame for the bar) */ + /* off the street onto the gravel road beside the main building, up along it, into the yard */ + static const float AT[][2] = { { -65.0f, 52.0f }, { -64.0f, 41.0f }, { -56.0f, 36.0f }, { -20.0f, 36.0f }, { 2.0f, 36.0f }, + { 8.0f, 32.0f }, { 9.5f, 26.0f }, { 14.0f, 21.5f }, { 20.0f, 19.8f }, { 25.0f, 19.5f } }; *n = COUNT(AT); for (int i = 0; i < *n; i++) points[i] = court(AT[i][0], AT[i][1]); - *park_yaw = yaw_facing(mapDir(COURT_ANGLE + 90.0f)); /* (nose to the garages, the boot to the road) */ + *park_yaw = yaw_facing(mapDir(COURT_ANGLE)); /* (nose to the barn, the boot to the main building) */ } Vec3 Site_yard(void) { @@ -735,7 +791,7 @@ static const RoomPiece ROOMS[] = { { 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 }, /* 3: car repairs (the wide door) */ - { FURN_CAR, { 23.4f, 0, -11.2f }, { 1.8f, 1.5f, 4.2f }, 0.0f }, + { FURN_CAR, { 23.4f, 0, -9.8f }, { 1.8f, 1.5f, 4.2f }, 0.0f }, { FURN_WORKBENCH, { 26.6f, 0, -6.6f }, { 2.4f, 0.9f, 0.7f }, 0.0f }, { FURN_TIRES, { 25.0f, 0, -5.3f }, { 0.65f, 0.9f, 0.65f }, 0.0f }, }; @@ -816,7 +872,7 @@ static bool buildGarages(World *world) { return false; continue; } - for (int t = 0; t < 12; t++, n++) { + for (int t = 0; t < 2; t++, n++) { /* (a bit of junk) */ float u = hash(k, t, 31) - 0.5f, v = hash(k, t, 37); Vec3 at_pos = court(a + u * (GARAGE_WIDTH - 1.0f), GARAGES_B + 0.8f + v * (GARAGE_DEPTH - 1.6f)); at_pos.y = 0.1f + 0.3f * hash(k, t, 41); @@ -853,6 +909,34 @@ static bool plantOrchard(World *world) { return true; } +/* The meadows up the banks: behind your garages (bushes round it, trees that + stay) and right of the main building (bushes behind it and on its right). */ +static bool addBush(World *world, float a, float b, int k) { + a += (hash(k, 5, 91) - 0.5f) * 0.6f; + b += (hash(k, 6, 93) - 0.5f) * 0.6f; + Vec3 size = vec3(1.6f + 0.9f * hash(k, 7, 97), 1.4f + 0.8f * hash(k, 8, 101), 0.0f); + Object *bush = Object_create(OBJ_BUSH, PALETTE_WARM, court(a, b), size, 0.0f); + return bush && World_add(world, bush); +} + +static bool plantMeadows(World *world) { + int k = 0; + for (float a = CAMP_A0; a <= CAMP_A1; a += 1.7f) if (!addBush(world, a, CAMP_B1, k++)) return false; /* the back */ + for (float b = -21.5f; b > CAMP_B1; b -= 1.7f) if (!addBush(world, CAMP_A0 - 0.8f, b, k++)) return false; /* the right */ + for (float b = -25.0f; b > CAMP_B1; b -= 1.7f) if (!addBush(world, CAMP_A1 + 0.8f, b, k++)) return false; /* the left, past the way up */ + static const float TREES[][2] = { { 8.0f, -36.0f }, { 17.5f, -37.0f }, { 27.0f, -36.5f }, { 36.5f, -35.5f }, { 43.0f, -29.0f }, { 3.5f, -27.0f } }; + for (int i = 0; i < COUNT(TREES); i++) { + Object *tree = Object_create(OBJ_TREE, PALETTE_WARM, court(TREES[i][0], TREES[i][1]), + vec3(4.0f + 1.2f * hash(i, 9, 103), 3.5f + 2.0f * hash(i, 10, 107), 0.0f), 0.0f); + if (!tree) return false; + tree->keep = true; + if (!World_add(world, tree)) return false; + } + for (float b = SIDE_B0 + 0.5f; b <= SIDE_B1; b += 1.7f) if (!addBush(world, SIDE_A0 - 0.8f, b, k++)) return false; /* behind */ + for (float a = SIDE_A0; a <= SIDE_A1; a += 1.7f) if (!addBush(world, a, SIDE_B1 + 0.8f, k++)) return false; /* on its right */ + return true; +} + /* The fireplace with some wood beside it, sausages and garbage bags, the container, and the yard's litter. */ static bool furnishYard(World *world) { @@ -904,7 +988,7 @@ static bool furnishYard(World *world) { if (!addLoose(world, OBJ_BRANCH, PALETTE_WARM, court(FIRE_A - 1.0f - 0.35f * (float)k, FIRE_B - 2.4f), vec3(0.0f, 1.1f, 0.0f), 1.2f, vec3(PI_F / 2.0f, 1.0f + 0.4f * (float)k, 0.0f))) return false; - for (int t = 0; t < 26; t++) { /* litter all over the yard */ + for (int t = 0; t < 4; t++) { /* a little litter in the yard */ float a = 8.0f + 30.0f * hash(t, 3, 61), b = -9.0f + 29.0f * hash(t, 5, 67); Vec3 at_pos = court(a, b); if (vec3_length(vec3_sub(at_pos, fire)) < 2.0f) continue; @@ -919,7 +1003,7 @@ static bool furnishYard(World *world) { static const struct { float hinge, end; } GATES[] = { { 12.5f, 11.0f }, /* the tool room door */ { 3.0f, 1.0f }, { -1.0f, 1.0f }, /* the kitchen */ - { -9.0f, -11.5f }, { -14.0f, -11.5f }, /* the car repairs */ + { -7.5f, -9.75f }, { -12.0f, -9.75f }, /* the car repairs (opened, the left leaf ends at the corner) */ }; #define GATE_HEIGHT 2.9f @@ -997,23 +1081,27 @@ bool Site_build(World *world) { const float wyaw = yaw_along(vec3_scale(front, -1.0f)); float zsign = courtA(placed(vec3(23.25f, 0.0f, 12.0f), WALLED_CENTER, walled, wyaw, 1.0f)) < courtA(placed(vec3(23.25f, 0.0f, -12.0f), WALLED_CENTER, walled, wyaw, 1.0f)) ? 1.0f : -1.0f; + g_garages.origin = walled; + g_garages.yaw = wyaw; + g_garages.zsign = zsign; + g_garages.set = true; if (!placePieces(world, WALLED, COUNT(WALLED), WALLED_CENTER, walled, wyaw, zsign) || !placePieces(world, FRAME, COUNT(FRAME), FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f) || !Shell_build(world, placed(SHELL_AT, FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f), across) || !furnishGarages(world, walled, wyaw, zsign) || !hangGates(world, walled, wyaw, zsign)) return false; - if (!buildGarages(world) || !plantOrchard(world) || !furnishYard(world)) return false; + if (!buildGarages(world) || !plantOrchard(world) || !plantMeadows(world) || !furnishYard(world)) return false; - /* The tap left of the main door, the garden hose and a watering can (the toilet is in the house) */ + /* The tap on the right end of the main building, the garden hose and a watering can (the toilet is in the house) */ const float across_yaw = yaw_along(mapDir(COURT_ANGLE - 90.0f)); - Object *tap = Object_createFurniture(FURN_TAP, court(6.15f, -9.75f), vec3(0.3f, 1.0f, 0.3f), across_yaw); + Object *tap = Object_createFurniture(FURN_TAP, court(TAP_A, TAP_B), vec3(0.3f, 1.0f, 0.3f), yaw_facing(mapDir(COURT_ANGLE - 90.0f))); if (!tap || !World_add(world, tap)) return false; - Object *hose = Object_create(OBJ_HOSE, PALETTE_WARM, vec3_add(court(7.0f, -9.4f), vec3(0.0f, 0.1f, 0.0f)), vec3_zero(), 0.6f); + Object *hose = Object_create(OBJ_HOSE, PALETTE_WARM, vec3_add(court(TAP_A + 0.6f, TAP_B + 0.8f), vec3(0.0f, 0.1f, 0.0f)), vec3_zero(), 0.6f); if (!hose) return false; hose->hose_tap = tap; Object_setRotation(hose, vec3(PI_F / 2.0f, across_yaw, 0.0f)); if (!World_add(world, hose)) return false; - Object *can = Object_create(OBJ_CAN, PALETTE_WARM, vec3_add(court(6.6f, -10.1f), vec3(0.0f, 0.2f, 0.0f)), vec3_zero(), 1.5f); + Object *can = Object_create(OBJ_CAN, PALETTE_WARM, vec3_add(court(TAP_A - 0.5f, TAP_B + 0.5f), vec3(0.0f, 0.2f, 0.0f)), vec3_zero(), 1.5f); if (!can || !World_add(world, can)) return false; /* Alice, the dachshund */ diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h index 8d05d56..31d4c9a 100644 --- a/entropie/src/world/site.h +++ b/entropie/src/world/site.h @@ -24,12 +24,18 @@ /* A point of the courtyard: a along the garages' long side, b across (towards the gravel road). */ Vec3 Site_court(float a, float b); -/* Where the crates of drinks go (by the fire), and where the tents go (the - orchard): k 0 yours, 1 Volkram and Jana's, 2 Toni's; along: the way things - are laid out side by side there. */ +/* Where the crates of drinks go (the kitchen in your garages), and where the tents go (the + meadow behind your garages): k 0 yours, 1 Volkram and Jana's, 2 Toni's; + along: the way things are laid out side by side there; door: the way the + tents' doors face. */ Vec3 Site_drinksSpot(void); Vec3 Site_campSpot(int k); Vec3 Site_campAlong(void); +Vec3 Site_campDoor(void); +/* In front of the main building's door by your garages (the one Kurti and his sons use), and whether + a spot is in the main building. */ +Vec3 Site_houseDoor(void); +bool Site_inHouse(float x, float z); /* Your car: where it comes in on the gravel road, the way in, and where it's parked (facing). */ void Site_arrivalPath(Vec3 *points, int *n, float *park_yaw); /* Where the yard is (world); the things to play with lie around it. */ @@ -45,6 +51,9 @@ bool Site_finish(World *world); /* Where the obstacle course runs (the chicken farm's yard): its start and direction. */ void Site_courseFrame(Vec3 *start, Vec3 *along); +/* For the terrain: how much higher the ground is here (the meadow behind + your garages, the one right of the main building). */ +float Site_rise(float x, float z); /* For the terrain: the ground's color here (forest floor, dry meadow, field, farm yard) given the meadow color, and how much a road is here (0..1). */ Vec3 Site_groundColor(float x, float z, Vec3 meadow); diff --git a/entropie/src/world/terrain.c b/entropie/src/world/terrain.c index 523c6bc..125f89a 100644 --- a/entropie/src/world/terrain.c +++ b/entropie/src/world/terrain.c @@ -290,7 +290,7 @@ bool Terrain_init(Terrain *t, Object *const *objects, int count) { for (int iz = 0; iz <= TERRAIN_GRID; iz++) { for (int ix = 0; ix <= TERRAIN_GRID; ix++) { float x = gridX(ix), z = gridX(iz); - t->heights[gridIndex(ix, iz)] = rawHeight(x, z) * openness(&zones, x, z) + Site_surface(x, z).lift; + t->heights[gridIndex(ix, iz)] = rawHeight(x, z) * openness(&zones, x, z) + Site_surface(x, z).lift + Site_rise(x, z); } } if (!buildSurface(t)) { diff --git a/entropie/src/world/tools.c b/entropie/src/world/tools.c index 424e1ad..a8d234a 100644 --- a/entropie/src/world/tools.c +++ b/entropie/src/world/tools.c @@ -212,7 +212,7 @@ static Plan planFor(const Object *who, ToolKind tool, Object *const *objects, in Object *o = objects[i]; if (o == who || o->is_held || o->collider_count == 0) continue; - if (o->type == OBJ_TREE && !o->toppled && workFor(tool, JOB_FELL) > 0.0f) { + if (o->type == OBJ_TREE && !o->toppled && !o->keep && workFor(tool, JOB_FELL) > 0.0f) { /* The cut goes in where the worker looks, within the tool's range */ Vec3 c = Object_colliderCenter(o, 0); float distance = vec3_length(flat(vec3_sub(c, who->position))); @@ -316,7 +316,7 @@ static bool stillThere(const Work *w) { const Object *o = w->target; if (!o) return false; switch (w->job) { - case JOB_FELL: return o->type == OBJ_TREE && !o->toppled; + case JOB_FELL: return o->type == OBJ_TREE && !o->toppled && !o->keep; case JOB_DELIMB: return o->type == OBJ_TREE && o->toppled && o->topple >= 1.0f; case JOB_CUT: return o->type == OBJ_LOG && !o->is_held; case JOB_SPLIT: return (o->type == OBJ_LOG || (o->type == OBJ_FIREWOOD && o->size.z < 4.0f)) && !o->is_held; diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 363cf5b..903b2aa 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -1250,6 +1250,15 @@ 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 */ + 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; } + continue; + } if (o->type == OBJ_HUMAN && !o->riding) { Ai_useGrid(&world->grid); Ai_useWorld(world); Ai_update(o, world->objects, world->count, dt); } if (o->type == OBJ_HUMAN && o->ai.wants_bag) handOutBag(world, o); if (o->type == OBJ_HUMAN && o->ai.empty_tray) emptyTray(world, o); @@ -1323,7 +1332,7 @@ void World_update(World *world, float dt, float game_dt) { if (!o->is_character) continue; if (player) { /* (far off: less detail; a bit of give either way, no flicking back and forth) */ float d = hypotf(o->position.x - player->position.x, o->position.z - player->position.z); - o->far_off = o->far_off ? d > FAR_DETAIL - 5.0f : d > FAR_DETAIL; + o->far_off = (o->far_off ? d > FAR_DETAIL - 5.0f : d > FAR_DETAIL) || o->riding != NULL; /* (in the car: nothing to keep out of) */ } takeKnock(o); takeBump(world, o, dt); @@ -1397,7 +1406,7 @@ static bool playerIn(const World *world, const Object *car) { void World_drawShadows(const World *world, Renderer *renderer) { for (int i = 0; i < world->count; i++) { const Object *obj = world->objects[i]; - if (flatOnGround(obj) || (obj->is_character && (obj->toilet || obj->riding)) || (!obj->is_character && !obj->mesh.vao)) continue; /* flat on the ground, hidden, nothing to see: no shadow */ + if (flatOnGround(obj) || (obj->is_character && (obj->toilet || obj->riding || obj->indoors > 0.0f)) || (!obj->is_character && !obj->mesh.vao)) continue; /* flat on the ground, hidden, nothing to see: no shadow */ bool big = fmaxf(obj->size.x, obj->size.z) >= BIG_SIZE * 3.0f; if (!big && flatDistance2(obj->position, renderer->shadow_focus) > SHADOW_REACH * SHADOW_REACH) continue; if (obj->is_character) { @@ -1451,7 +1460,7 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { if (obj->type == OBJ_PLAYER && !draw_player) continue; /* first person */ if (!obj->is_character && obj->type != OBJ_DOG && !obj->mesh.vao) continue; /* (invisible: a building's walls) */ if (outOfSight(obj, renderer->viewer_pos)) continue; - if (obj->is_character && (obj->toilet || obj->riding)) continue; /* in the toilet, in the car */ + if (obj->is_character && (obj->toilet || obj->indoors > 0.0f)) continue; /* in the toilet, in the house */ if (obj->is_character) { FigurePose pose = figurePose(obj); Renderer_drawFigure(renderer, &pose);