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.6.0: arriving by car
- The game starts in your car: you, Volkram, Jana and Toni drive in along the gravel road and park in the yard (seen from the driver's seat) - Everybody gets out, Kurti greets you, the boot opens - 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 to the orchard where the tents will go; left click at the boot to help - Crates: left click takes a bottle out, E / Q carries the crate; the others take their beer and water from them too - Fix: the fire's ground slab stuck out past the stones; people beside the fire stepped on and off its corners and jittered - ARRIVE_BY_CAR in src/game/game.c (testing) skips the drive
entropie/ARCHITECTURE.md | 4 +- entropie/Makefile | 2 +- entropie/README.md | 15 ++- entropie/STATE.md | 13 ++- entropie/commit_message.txt | 21 ++-- entropie/src/game/game.c | 6 ++ entropie/src/game/game.h | 15 +++ entropie/src/game/game_actions.c | 39 ++++++- entropie/src/game/game_arrival.c | 182 +++++++++++++++++++++++++++++++ entropie/src/game/game_internal.h | 6 ++ entropie/src/game/player.c | 2 +- entropie/src/people/ai.c | 97 ++++++++++++++++- entropie/src/people/ai.h | 4 + entropie/src/people/hands.c | 4 + entropie/src/render/shapes.h | 15 +++ entropie/src/render/shapes_props.c | 109 +++++++++++++++++++ entropie/src/world/car.c | 212 +++++++++++++++++++++++++++++++++++++ entropie/src/world/car.h | 57 ++++++++++ entropie/src/world/object.c | 59 ++++++++++- entropie/src/world/object.h | 10 ++ entropie/src/world/physics.c | 2 +- entropie/src/world/site.c | 20 ++++ entropie/src/world/site.h | 10 ++ entropie/src/world/world.c | 22 +++- 24 files changed, 899 insertions(+), 27 deletions(-) diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index e8ae814..e4ab575 100644 --- a/entropie/ARCHITECTURE.md +++ b/entropie/ARCHITECTURE.md @@ -10,9 +10,9 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL | `src/core/` | window, GL loader (`gl.h`: every GL call is listed there), input | core | | `src/math/` | vectors, matrices, frustum, yaw conventions (`linalg.h`) | math | | `src/render/` | renderer and its passes, shaders' uniforms, meshes, procedural shapes (`shapes*.c`), figures, camera, sky, UI | core, math, render | -| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, water, grass, grid, day | everything but game | +| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, car, water, 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 | +| `src/game/` | the frame: input to actions, player, camera follow, talk and dev UI, the arrival by car at the start | all | - `render` knows nothing of the world: it gets meshes, matrices and plain numbers. - `world` and `people` are one layer: an `Object` owns its `Body`, `Ai` and `Hands`. diff --git a/entropie/Makefile b/entropie/Makefile index f2a1637..cb27b9d 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.5.0 +VERSION := 2.6.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 360b6f4..0b674dc 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -23,6 +23,19 @@ Painted in the rotoscope style of *Waking Life* (2001): with only a faint paper edge - Stats on a painted sheet of paper (Tab) +## 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 +- 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) +- Crates: left click takes a bottle out (the others help themselves too), + E / Q carries the whole crate + ## The site The map is Homburgshöhe at Geflügelhof Oderland, traced from the real map @@ -59,7 +72,7 @@ and satellite pictures (about 340 x 340 m). place with an old car behind the wide door - The woodpile by the barn: everybody stacks firewood there unless you show them another place -- The fireplace is a ring of field stones; its tripod of long wooden poles +- The fireplace is a ring of field stones (its ground slab no wider than the ring); its tripod of long wooden poles (3.9 m up, the grill on a long chain) stands well away from the fire; you walk round them - Alice the dachshund lives here (painted like the people): she trots after you, sniffs around, begs diff --git a/entropie/STATE.md b/entropie/STATE.md index b23e4f6..7cb7c71 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -180,6 +180,13 @@ Round 2.5: - `ai.c` binButt (idle, wander, at a table): with `butt_held`, the tin, a bin or a burning fire within BUTT_WALK 30 m: there (gives up after 40 s: skipped) and in; none: asks somebody within 6 m with a pocket ashtray (NPCs: "Sure, put it in." - into theirs; you: `asking_you` POCKET_ASHTRAY, the hint line, B Enter "hold it out"); nothing after BUTT_WAIT 25 s or held BUTT_KEEP 90 s: wrapped up into the pocket ("I'll keep it till I find a bin."). Pocket butts are emptied passing within BUTT_PASS 2 m of the tin, a bin or the fire; a pocket ashtray with TRAY_FULL 8 is taken there on purpose. - Your pockets: row "Pocket ashtray - n butts": Enter looking at somebody holding a butt: it goes into yours ("Thanks - much better than on the ground."); else at the tin, a bin or the fire (within 1 m): emptied. +Round 2.6: +- Arrival (`game_arrival.c`, `ARRIVE_BY_CAR`): your car (OBJ_CAR, `car.c`, an estate: `Shapes_car`, from the seat `Shapes_carInside` - the body round the eye hid everything) on `Site_arrivalPath` (gravel road, into the yard, parked at court 24 / 19 facing the garages; Catmull-Rom, ARRIVAL_SAMPLES 160), DRIVE_TOP 5.5 m/s, braking 1.4 m/s^2, about 12 s. You (driver), Volkram, Jana, Toni `riding` (hidden, no physics, no AI, moved with it; first person from the seat, nothing to do but look). Parked: out one after the other (0.6-2.6 s, a line each), Kurti "There you are!", the boot open at 3.2 s, the three friends `ai.unload`. +- The boot (`CarLoad`, last in first out): sleeping bags, mats, tents (big: Volkram and Jana; small: Toni, you), then 2 crates of beer, 2 of water. `Car_takeOut`: set down behind it. Unloading (`ai.c` unloadCar, idle / wander): to the boot (side by side, `ai.side`), `ai.take_from` = the car -> `Car_serve` puts the next thing there for them (`ai.target`), picked up (crates and the big tent with both hands), carried to `Car_dropSpot` (crates: `Site_drinksSpot` by the fire, 0.5 m apart; the camping things: `Site_campSpot` in the orchard - yours, Volkram and Jana's, Toni's - side by side by kind), put down there; gives up a trip after 70 s. All 15 things out in about 110 s. You: left click at the boot (`game_unloadCar`, 1.1 m): the next thing into your hands; hint lines at the boot and carrying. +- Crates (OBJ_CRATE: beer 20 bottles, water 12; 19 / 16 kg; `Shapes_crate` shows what's left; set down standing). Left click: a bottle (`Crate_takeBottle`); E / Q: the crate. NPCs wanting a beer or a drink count crates like bottles (`findConsumable`), at it `ai.take_from` -> a bottle on top for them. +- Camping gear (OBJ_GEAR: sleeping bag, mat, small and big tent bag; `owner`; colours by owner, `Shapes_gear`). +- The fireplace's ground slab is 0.6 of the ring wide (its corners at the stones): people beside the fire stepped on and off its corners (Kurti's joint jumps in the NPC audit 1465 -> 421). + 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). @@ -299,6 +306,7 @@ Ground: meadow wears into paths; tar, concrete, gravel and tracks are part of th - Esc: menu (the game waits): Resume, Keys (show / hide), Quit; mouse or Up / Down + Enter - Dev menu (F1), second line: teleport you to the parkour (Right) or the yard (Left). Then pick anyone (Left / Right on the first line) and change: female, Urinella, sporty, drunk, energy, bladder, hunger, thirst, sleepy, strength, annoyed, pride, lively, nerves, clumsy, willing, likes and skill for each job (Up / Down: line, the list scrolls; Left / Right: value) - `START_AT_COURSE` in `src/game/game.c`: start at the obstacle course (1) or in the courtyard (0, default); the course is there either way +- `ARRIVE_BY_CAR` in `src/game/game.c`: 1 (default) the start in the car; 0: standing in the yard, the car on the road with everything in it ## Speed @@ -311,7 +319,7 @@ Most important first. **Bugs** - Joint jumps left in the audit: jog+stop 171, sprint 297 (worst 26 cm) - running into things: a hurried catch step swings through in a few frames (step 0.07 -> 0.34, the ankle up 20 cm). Capping the swing speed helped sprint (278) but made jumps, drunk walk and the NPCs worse - tried and reverted in 1.83; ladder 45: toes on the sloping roof (a landing step's toes snap onto the slope, 16 cm; blending them in made all walking worse), toes turning onto the rungs getting on from the top (14 cm) -- NPC audit: Kurti idles by the fire with the chainsaw in his hand; it knocks against the tripod poles and he jitters (about 1400 of the 3500 joint jumps) +- NPC audit: Kurti walks about with the chainsaw (putting it away?) and jitters at the garage gates and in the tool room (300-900 joint jumps a run) **Body and moving** - Obstacle approach, still to do: thin round logs (under 0.2 m) are still stepped over; a step in between on top of something unstable (if clumsy); NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things @@ -326,7 +334,6 @@ Most important first. - Smoking, still to do: paying for a pack (no money in the game yet) - "No shirt no service" policy (decision open) - Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers); the pullover over a railing NPCs never do on their own -- 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 @@ -337,4 +344,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.5.0 (build 74f2023)` +State of `entropie 2.6.0 (build 963c4fd)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index f1efa7b..ffdb5fa 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,9 +1,14 @@ -entropie 2.5.0: cigarette butts go somewhere +entropie 2.6.0: arriving by car -- Finished cigarettes go into the fire when close, else the tin or the bin - when near; tidy people walk there with the butt -- Pocket ashtrays (you and Jana): the butt goes in there -- No bin near: the tidy ask for a pocket ashtray (from you too: B, look at - them, Enter holds yours out), else they keep it wrapped up till they pass - a bin; the others still flick it away -- Your pocket ashtray: B, Enter at the tin, a bin or the fire empties it +- The game starts in your car: you, Volkram, Jana and Toni drive in along + the gravel road and park in the yard (seen from the driver's seat) +- Everybody gets out, Kurti greets you, the boot opens +- 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 to the orchard + where the tents will go; left click at the boot to help +- Crates: left click takes a bottle out, E / Q carries the crate; the + others take their beer and water from them too +- Fix: the fire's ground slab stuck out past the stones; people beside the + fire stepped on and off its corners and jittered +- ARRIVE_BY_CAR in src/game/game.c (testing) skips the drive diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 0f925cb..0ab49bd 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -5,8 +5,10 @@ #include "game/game_internal.h" #include "world/clothes.h" +#include "world/car.h" #define START_HOUR 13.0f +#define ARRIVE_BY_CAR 1 /* (testing) 0: start standing in the yard, no car */ #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) */ #define TIME_SCALE 3.0f /* game seconds per real second */ #define CAMERA_FOV 70.0f @@ -52,6 +54,7 @@ static void followPlayer(Game *game, float dt) { Camera *cam = &game->camera; const Object *p = game->player; Vec3 eye = vec3_add(p->position, vec3(0, PLAYER_EYE_HEIGHT, 0)); + if (p->riding) { cam->position = Car_eye(p->riding, p->ride_seat); return; } /* (in the car: from the seat) */ if (Body_isDown(&p->body)) { /* On the ground: see from the head, wherever it is */ Vec3 facing = Object_facing(p); @@ -239,6 +242,8 @@ bool Game_init(Game *game) { } Camera_init(&game->camera, game->camera.position, look * 180.0f / PI_F, 0.0f, CAMERA_FOV); game->marker = Shapes_marker(1.8f, 0.8f); + memset(&game->arrival, 0, sizeof(game->arrival)); + if (ARRIVE_BY_CAR && !START_AT_COURSE) game_arrivalStart(game); followPlayer(game, 0.0f); return true; } @@ -287,6 +292,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { puttingUp(game); game_climbLadder(game, input, dt); game_climb(game, input, dt); + game_arrival(game, dt); 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 c05f699..f217356 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -13,6 +13,20 @@ #include "render/camera.h" #include "render/renderer.h" +/* The start: arriving by car (see game_arrival.c). */ +#define ARRIVAL_POINTS 12 +#define ARRIVAL_SAMPLES 160 +typedef enum { ARRIVAL_NONE, ARRIVAL_DRIVING, ARRIVAL_PARKED, ARRIVAL_DONE } ArrivalStage; +typedef struct { + ArrivalStage stage; + struct Object *car; + Vec3 path[ARRIVAL_SAMPLES]; /* the way in, sampled ... */ + float dist[ARRIVAL_SAMPLES]; /* ... how far along each sample is */ + float length, s, speed, time, park_yaw; + int step; /* ... who's out of the car */ + bool greeted; +} Arrival; + typedef struct { World world; Object *player; @@ -52,6 +66,7 @@ typedef struct { float brace_hold; /* hands on an edge you walked up to: kept there this much longer ... */ Vec3 brace_grip; /* ... here */ Object *putting_up; /* the ladder you are putting up (hands on it, standing still) */ + Arrival arrival; /* the start: in by car */ } Game; /* A line of speech above someone's head, in screen pixels (y up from the bottom). */ diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index aa4cbfa..f9be548 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -6,6 +6,7 @@ #include "game/game_internal.h" #include "people/pockets.h" #include "world/clothes.h" +#include "world/car.h" #include "people/climb.h" #include <stdlib.h> @@ -554,7 +555,7 @@ static bool takeFromSupply(Game *game) { Vec3 facing = Object_facing(p); for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; - if (o->type != OBJ_SUPPLY || Hands_gapFrom(p->position, p->look.height, o) > SUPPLY_REACH) continue; + if ((o->type != OBJ_SUPPLY && (o->type != OBJ_CRATE || o->is_held)) || Hands_gapFrom(p->position, p->look.height, o) > SUPPLY_REACH) continue; Vec3 to = vec3_sub(o->position, p->position); to.y = 0.0f; if (vec3_length(to) > 1e-3f && vec3_dot(vec3_normalize(to), facing) < 0.3f) continue; @@ -573,6 +574,12 @@ static bool takeFromSupply(Game *game) { int hand = p->hands.state[HAND_RIGHT] == HAND_FREE ? HAND_RIGHT : p->hands.state[HAND_LEFT] == HAND_FREE ? HAND_LEFT : -1; if (hand < 0) { say(game, "Your hands are full"); return true; } + if (o->type == OBJ_CRATE) { /* a bottle out of the crate (E / Q: the crate itself) */ + if (o->stored <= 0) { say(game, "The crate is empty"); return true; } + Object *bottle = Crate_takeBottle(&game->world, o); + if (bottle && Hands_pickUpObject(p, hand, bottle)) tell(game, bottle); + return true; + } ObjectType what = o->dispenses; Vec3 top = vec3_add(Object_colliderCenter(o, 0), vec3(0.0f, o->colliders[0].half_extents.y + 0.1f, 0.0f)); Vec3 size = what == OBJ_APPLE ? vec3(0.08f, 0.08f, 0.08f) : what == OBJ_SAUSAGE ? vec3(0.0f, 0.16f, 0.0f) @@ -785,6 +792,26 @@ static int useWater(Game *game) { return CLICK_WATER; } +/* At the car's open boot, or carrying something out of it: what to do with it. */ +static bool game_bootHint(Game *game, char *out, size_t size) { + const Object *p = game->player, *car = game->arrival.car; + 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_GEAR) { + snprintf(out, size, "%s - over to the orchard, where the tents go - right click: put it down", + it->owner == p ? "Yours" : it->owner ? it->owner->name : "Somebody's"); + return true; + } + } + if (!car || !car->boot_open || !car->load || car->load->n == 0) return false; + Vec3 boot = Car_bootSpot(car); + if (hypotf(boot.x - p->position.x, boot.z - p->position.z) > 1.6f) return false; + snprintf(out, size, "The boot: left click - take out %s%s", Car_nextName(car), Car_nextHeavy(car) ? " (both hands)" : ""); + return true; +} + /* Somebody near who asked you for a paper, a filter or tobacco (and is still waiting). */ static const Object *askingYou(const Game *game) { const Object *p = game->player; @@ -811,6 +838,9 @@ const char *Game_hint(Game *game) { snprintf(game->hint, sizeof(game->hint), "%s asks you for %s - B: your pockets, look at %s, Enter: %s", asking->name, Pockets_needName((PocketNeed)asking->ai.asking_you), asking->female ? "her" : "him", asking->ai.asking_you == POCKET_ASHTRAY ? "hold it out" : "give it"); + else if (game->player->riding) + snprintf(game->hint, sizeof(game->hint), "Nearly there - Homburgsh\xC3\xB6he. Look around (mouse)"); + else if (game_bootHint(game, game->hint, sizeof(game->hint))) {} else if (shown) snprintf(game->hint, sizeof(game->hint), "%s follows you: walk to where the firewood should go and left click - Backspace: never mind", shown->name); else if (watcher) @@ -972,7 +1002,7 @@ static int leftClick(Game *game) { Talk_showHere(&game->world, p, shown); return CLICK_NONE; } - if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || takePullover(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE; + if (game_unloadCar(game) || 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; @@ -1073,6 +1103,11 @@ void game_handleActions(Game *game, const Input *input) { Object *const *objects = game->world.objects; int count = game->world.count; + if (p->riding) { /* (in the car: looking about only) */ + game->click_action = CLICK_NONE; + if (input->show_keys) game->show_keys = !game->show_keys; + return; + } if (input->toggle_view) game->third_person = !game->third_person; if (input->show_keys) game->show_keys = !game->show_keys; if (input->show_stats) game->show_stats = !game->show_stats; diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c new file mode 100644 index 0000000..547bfbf --- /dev/null +++ b/entropie/src/game/game_arrival.c @@ -0,0 +1,182 @@ +/* + * 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 + * 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" + +#define DRIVE_TOP 5.5f /* m/s on the gravel road ... */ +#define DRIVE_ACCEL 1.6f /* ... speeding up ... */ +#define DRIVE_BRAKE 1.4f /* ... and slowing down to park (m/s^2) */ +#define PATH_SAMPLES ARRIVAL_SAMPLES + +/* Catmull-Rom through the points (the ends doubled). */ +static Vec3 along(const Vec3 *p, int n, float u) { + float f = clampf(u, 0.0f, 1.0f) * (float)(n - 1); + int i = (int)f; + if (i >= n - 1) i = n - 2; + float t = f - (float)i; + Vec3 p0 = p[i > 0 ? i - 1 : 0], p1 = p[i], p2 = p[i + 1], p3 = p[i + 2 < n ? i + 2 : n - 1]; + float t2 = t * t, t3 = t2 * t; + Vec3 r = vec3_zero(); + r = vec3_add(r, vec3_scale(p1, 2.0f)); + r = vec3_add(r, vec3_scale(vec3_sub(p2, p0), t)); + r = vec3_add(r, vec3_scale(vec3_add(vec3_sub(vec3_scale(p0, 2.0f), vec3_scale(p1, 5.0f)), vec3_sub(vec3_scale(p2, 4.0f), p3)), t2)); + r = vec3_add(r, vec3_scale(vec3_add(vec3_sub(vec3_scale(p1, 3.0f), p0), vec3_sub(p3, vec3_scale(p2, 3.0f))), t3)); + return vec3_scale(r, 0.5f); +} + +static Object *person(Game *game, const char *name) { + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->type == OBJ_HUMAN && !strcmp(o->name, name)) return o; + } + return NULL; +} + +static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" }; + +void game_arrivalStart(Game *game) { + Arrival *a = &game->arrival; + Vec3 pts[ARRIVAL_POINTS]; + int n = 0; + Site_arrivalPath(pts, &n, &a->park_yaw); + if (n < 2) return; + a->length = 0.0f; + for (int k = 0; k < PATH_SAMPLES; k++) { /* (the curve, sampled; how far along each sample is) */ + a->path[k] = along(pts, n, (float)k / (float)(PATH_SAMPLES - 1)); + if (k > 0) a->length += vec3_length(vec3_sub(vec3(a->path[k].x, 0.0f, a->path[k].z), vec3(a->path[k - 1].x, 0.0f, a->path[k - 1].z))); + a->dist[k] = a->length; + } + Vec3 head = vec3_sub(a->path[1], a->path[0]); + Object *car = Car_create(a->path[0], yaw_facing(vec3_normalize(vec3(head.x, 0.0f, head.z)))); + if (!car || !World_add(&game->world, car)) return; + a->car = car; + Object *friends[3]; + for (int k = 0; k < 3; k++) friends[k] = person(game, FRIENDS[k]); + /* the boot, loaded last first: the camping things on top, the crates under them */ + Car_load(car, CARGO_WATER, NULL); + Car_load(car, CARGO_WATER, NULL); + Car_load(car, CARGO_BEER, NULL); + Car_load(car, CARGO_BEER, NULL); + Car_load(car, CARGO_TENT_BIG, friends[0]); + Car_load(car, CARGO_TENT_SMALL, friends[2]); + Car_load(car, CARGO_TENT_SMALL, game->player); + Object *everybody[4] = { game->player, friends[0], friends[1], friends[2] }; + for (int k = 0; k < 4; k++) if (everybody[k]) Car_load(car, CARGO_MAT, everybody[k]); + for (int k = 0; k < 4; k++) if (everybody[k]) Car_load(car, CARGO_SLEEPING_BAG, everybody[k]); + for (int k = 0; k < 4; k++) if (everybody[k]) Car_getIn(car, everybody[k], k); + Car_place(&game->world, car, a->path[0], car->rotation.y); + a->stage = ARRIVAL_DRIVING; + a->speed = 3.0f; + a->s = 0.0f; + a->time = 0.0f; + game->third_person = false; + float yaw = atan2f(head.z, head.x) * 180.0f / PI_F; + Camera_init(&game->camera, Car_eye(car, 0), yaw, -4.0f, game->camera.fov); +} + +/* Where the car is s meters along, and which way it heads. */ +static void at(const Arrival *a, float s, Vec3 *pos, Vec3 *dir) { + int k = 1; + while (k < PATH_SAMPLES - 1 && a->dist[k] < s) k++; + float span = fmaxf(a->dist[k] - a->dist[k - 1], 1e-4f), t = clampf((s - a->dist[k - 1]) / span, 0.0f, 1.0f); + *pos = vec3_lerp(a->path[k - 1], a->path[k], t); + Vec3 d = vec3_sub(a->path[k], a->path[k - 1]); + *dir = vec3_normalize(vec3(d.x, 0.0f, d.z)); +} + +static void getOut(Game *game, const char *name, const char *line) { + Object *o = name ? person(game, name) : game->player; + if (!o || !o->riding) return; + Car_getOut(o); + if (line && o->type == OBJ_HUMAN) Ai_say(o, line); +} + +void game_arrival(Game *game, float dt) { + Arrival *a = &game->arrival; + Object *car = a->car; + if (a->stage == ARRIVAL_NONE || a->stage == ARRIVAL_DONE || !car) return; + a->time += dt; + if (a->stage == ARRIVAL_DRIVING) { + float left = a->length - a->s; + float brake = sqrtf(fmaxf(0.0f, 2.0f * DRIVE_BRAKE * left)); + a->speed = fminf(fminf(a->speed + DRIVE_ACCEL * dt, DRIVE_TOP), brake + 0.15f); + a->s = fminf(a->length, a->s + a->speed * dt); + Vec3 pos, dir; + at(a, a->s, &pos, &dir); + float yaw = yaw_facing(dir); + if (a->length - a->s < 4.0f) /* (the last bit: straight in to its spot) */ + yaw = lerpf(yaw, a->park_yaw, smoothstepf(4.0f, 0.0f, a->length - a->s)); + Car_place(&game->world, car, pos, yaw); + if (a->s >= a->length - 0.01f) { a->stage = ARRIVAL_PARKED; a->time = 0.0f; } + return; + } + /* parked: out one after the other, the boot up, unloading */ + static const struct { float at; int who; const char *line; } STEPS[] = { + { 0.6f, -1, NULL }, + { 1.2f, 0, "Made it! Right - beer first." }, + { 1.9f, 1, "Oh, it's lovely here." }, + { 2.6f, 2, "My back... that seat is a crime." }, + }; + for (int k = 0; k < 4; k++) + 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"); + game->third_person = true; + a->greeted = true; + } + if (a->step == 4 && a->time >= 3.2f) { + Car_openBoot(car, true); + for (int k = 0; k < 3; k++) { + Object *o = person(game, FRIENDS[k]); + if (!o) continue; + o->ai.mode = AI_IDLE; + o->ai.unload = true; + o->ai.unload_car = car; + o->ai.unload_time = 0.0f; + o->ai.side = (float)(k - 1); + } + a->stage = ARRIVAL_DONE; + } +} + +bool game_arriving(const Game *game) { + return game->arrival.stage == ARRIVAL_DRIVING || (game->arrival.stage == ARRIVAL_PARKED && game->player->riding); +} + +/* Left click at the open boot: the next thing out, into your hands. */ +bool game_unloadCar(Game *game) { + Object *car = game->arrival.car, *p = game->player; + if (!car || !car->boot_open) return false; + Vec3 boot = Car_bootSpot(car); + if (hypotf(boot.x - p->position.x, boot.z - p->position.z) > 1.1f) return false; + const char *next = Car_nextName(car); + if (!next) { say(game, "The boot is empty"); return true; } + bool heavy = Car_nextHeavy(car); + bool both = p->hands.state[HAND_LEFT] == HAND_FREE && p->hands.state[HAND_RIGHT] == HAND_FREE; + int hand = Hands_freeHand(p); + if (heavy ? !both : hand < 0) { + snprintf(game->info, sizeof(game->info), "Next is %s - %s", next, heavy ? "both hands free for it" : "a hand free for it"); + game->info_about = NULL; + say(game, game->info); + return true; + } + Object *item = Car_takeOut(&game->world, car); + if (!item) return true; + item->position = vec3_add(p->position, vec3_add(vec3_scale(Object_facing(p), 0.45f), vec3(0.0f, 0.5f, 0.0f))); /* (right at your hands) */ + 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"); + 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 87725b0..379adeb 100644 --- a/entropie/src/game/game_internal.h +++ b/entropie/src/game/game_internal.h @@ -21,6 +21,12 @@ #define MESSAGE_TIME 2.5f /* seconds a hint stays on screen */ +/* game_arrival.c: in by car at the start; still in it (no walking, no hands); the boot clicked. */ +void game_arrivalStart(Game *game); +void game_arrival(Game *game, float dt); +bool game_arriving(const Game *game); +bool game_unloadCar(Game *game); + void game_callHelp(Game *game); void game_callOver(Game *game); Object *game_personInFront(const Game *game); diff --git a/entropie/src/game/player.c b/entropie/src/game/player.c index 3ed7ac8..9070d1d 100644 --- a/entropie/src/game/player.c +++ b/entropie/src/game/player.c @@ -48,7 +48,7 @@ void Player_update(Object *player, const Input *input, const Camera *camera, flo player->body.look_dir = camera->front; player->body.look_weight = 1.0f; player->body.urge = clampf(input->move_forward, 0.0f, 1.0f); /* W gets you up sooner after a fall */ - if (Body_isBusy(&player->body) || player->on_ladder) { /* (climbing: see the game) */ + if (Body_isBusy(&player->body) || player->on_ladder || player->riding) { /* (climbing: see the game; in the car) */ player->movement = vec3_zero(); player->move_local = vec3_zero(); return; diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 9b9ec72..fc83009 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -11,6 +11,7 @@ #include "world/grid.h" #include "world/world.h" #include "world/clothes.h" +#include "world/car.h" #include "render/shapes.h" /* ---------- Small helpers ---------- */ @@ -490,16 +491,19 @@ static Object *findConsumable(const Object *self, Object *const *objects, int co float best_dist = want == WANT_BEER ? DRINK_RANGE : DRINK_RANGE * 2.0f; for (int i = 0; i < count; i++) { Object *o = objects[i]; + bool crate = o->type == OBJ_CRATE; /* (a crate: a bottle out of it) */ bool fits = want == WANT_FOOD ? Object_isFood(o) : Object_isBottle(o); if (want == WANT_BEER && o->alcohol <= 0.0f) fits = false; - if (!fits || o->is_held || o->sips <= 0 || o->broken) continue; + if (crate) fits = want != WANT_FOOD && o->stored > 0 && (want != WANT_BEER || o->variant == 0); + if (!fits || o->is_held || (!crate && o->sips <= 0) || o->broken) continue; if (ai_skipped(&self->ai, o)) continue; /* couldn't get at it */ bool taken = false; for (int j = 0; j < count; j++) { const Object *c = objects[j]; if (c != self && c->is_character && c->ai.target == o) taken = true; } - float d = flatDistance(o->position, self->position) + (want == WANT_DRINK && o->alcohol > 0.0f ? 8.0f : 0.0f); + float d = flatDistance(o->position, self->position) + (want == WANT_DRINK && (crate ? o->variant == 0 : o->alcohol > 0.0f) ? 8.0f : 0.0f); + if (crate) taken = false; /* (enough in it for all) */ if (!taken && d < best_dist) { best_dist = d; best = o; @@ -742,7 +746,8 @@ static void updateFetch(Object *self, Object *const *objects, int count, float d Ai *ai = &self->ai; Object *bottle = ai->target; ai->timer += dt; - if (!bottle || bottle->is_held || bottle->sips <= 0 || ai->timer > FETCH_TIMEOUT || + bool crate = bottle && bottle->type == OBJ_CRATE; + if (!bottle || bottle->is_held || (crate ? bottle->stored <= 0 : bottle->sips <= 0) || ai->timer > FETCH_TIMEOUT || ai_skipped(ai, bottle)) { /* gone, or can't get at it */ stand(self); giveUpBottle(ai); @@ -752,6 +757,11 @@ static void updateFetch(Object *self, Object *const *objects, int count, float d if (Hands_gapFrom(self->position, self->look.height, bottle) < PICKUP_REACH) { stand(self); turnTowards(self, vec3_sub(bottle->position, self->position), dt); + if (crate) { /* one out of the crate (car.c Car_serve: then that's the one) */ + Hands_touch(self, Object_colliderCenter(bottle, 0)); + ai->take_from = bottle; + return; + } if (Hands_pickUpObject(self, self->main_hand, bottle)) { ai->mode = AI_DRINK; ai->timer = 0.0f; @@ -1665,6 +1675,85 @@ static bool fetchPullover(Object *self, Object *const *objects, int count, float return true; } +/* Unloading the car (after the arrival): to the boot, the next thing out of + 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 */ + +static Object *carriedCargo(const Object *self) { + for (int h = 0; h < 2; h++) { + Object *it = self->hands.item[h]; + if (it && (self->hands.state[h] == HAND_HOLDING || self->hands.state[h] == HAND_REACHING) && + (it->type == OBJ_CRATE || it->type == OBJ_GEAR)) return it; + } + return NULL; +} + +static bool goToPoint(Object *self, Vec3 p, float reach, Object *const *objects, int count, float dt) { + float d = flatDistance(p, self->position); + if (d < reach) { stand(self); return true; } + Vec3 dir = ai_steer(self, NULL, objects, count, p); + if (vec3_length(dir) < 0.5f) { stand(self); return false; } + ai_walk(self, dir, fminf(WALK_SPEED, d * 2.0f + 0.3f), dt); + return false; +} + +static bool unloadCar(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + Object *car = ai->unload_car; + if (!ai->unload || !car) { ai->unload = false; return false; } + Object *item = carriedCargo(self); + bool empty = !car->load || car->load->n == 0; + bool waiting = ai->target && (ai->target->type == OBJ_CRATE || ai->target->type == OBJ_GEAR) && !ai->target->is_held; + if (!item && !waiting && empty) { + ai->unload = false; + if (random01(ai) < 0.5f) Ai_say(self, "That's everything out of the car."); + return false; + } + if ((ai->unload_time += dt) > UNLOAD_TRIP) { + for (int h = 0; h < 2; h++) if (self->hands.item[h] == item && item) Hands_drop(self, h); + ai->unload = false; + return false; + } + if (self->sitting) self->sitting = false; + 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; } + Vec3 spot = Car_dropSpot(g_world, item); + Vec3 from = vec3_sub(self->position, spot); + from.y = 0.0f; + from = vec3_length(from) > 0.1f ? vec3_normalize(from) : vec3_scale(Object_facing(self), -1.0f); + if (!goToPoint(self, vec3_add(spot, vec3_scale(from, 0.5f)), 0.25f, objects, count, dt)) return true; /* (the hands over the spot) */ + turnTowards(self, vec3_scale(from, -1.0f), dt); + for (int h = 0; h < 2; h++) if (self->hands.item[h] == item) Hands_drop(self, h); + item->position.x = spot.x; /* (right there: side by side, not on top of each other) */ + item->position.z = spot.z; + Object_syncColliders(item); + ai->target = NULL; + ai->unload_time = 0.0f; + return true; + } + if (waiting) { /* out of the boot, lying there: take it */ + Object *it = ai->target; + if (Hands_gapFrom(self->position, self->look.height, it) < PICKUP_REACH) { + stand(self); + turnTowards(self, vec3_sub(it->position, self->position), dt); + freeHands(self, NULL); + if (!Hands_pickUpObject(self, self->main_hand, it) && !Hands_pickUpObject(self, 1 - self->main_hand, it)) ai->target = NULL; + return true; + } + goToPoint(self, it->position, 0.3f, objects, count, dt); + return true; + } + /* to the boot (a little to one side: room for the others) */ + Vec3 back = vec3_sub(Car_bootSpot(car), car->position), across = vec3_normalize(vec3(-back.z, 0.0f, back.x)); + Vec3 spot = vec3_add(Car_bootSpot(car), vec3_scale(across, ai->side * 0.4f)); + if (!goToPoint(self, spot, 0.45f, objects, count, dt)) return true; + turnTowards(self, vec3_sub(car->position, self->position), dt); + freeHands(self, NULL); + if (car->boot_open && !ai->take_from) { ai->take_from = car; Hands_touch(self, vec3_add(car->position, vec3_scale(back, 0.45f))); } + return true; +} + /* A finished cigarette in the fingers (the tidy): to the tin, a bin or the fire with it; none near: somebody's pocket ashtray (asked for), else wrapped up and into the pocket. Butts in the pocket (ashtray) go into the tin, a @@ -1799,6 +1888,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; + /* Unloading the car */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && unloadCar(self, objects, count, dt)) return; /* A finished cigarette in the fingers: to the tin, a bin, the fire */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && binButt(self, objects, count, dt)) return; /* Cold, the pullover put down somewhere: off to get it */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index 72a3659..2d68ede 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -176,6 +176,10 @@ typedef struct { 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) */ + 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 unload; /* unloading the car (ai.c unloadCar) ... */ + struct Object *unload_car; + float unload_time; /* ... on this trip this long */ float butt_time; /* holding a finished cigarette (Object.butt_held) this long ... */ bool butt_asked; /* ... and asked somebody for their pocket ashtray already */ int asking_you; /* asked the player for this (PocketNeed: a paper, a filter, tobacco), 0: nothing */ diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 2a37649..5cfa984 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -167,6 +167,10 @@ void Hands_drop(Object *who, int hand) { } item->is_held = false; item->velocity = vec3(who->velocity.x, 0.0f, who->velocity.z); + if (item->type == OBJ_CRATE) { /* (set down standing, not tipped over) */ + item->rotation = vec3(0.0f, who->rotation.y, 0.0f); + Object_syncColliders(item); + } 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); diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index e1d422f..e68c8ed 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -126,6 +126,21 @@ 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); +/* Your estate car standing on y = 0, nose to +Z (CAR_WIDTH x about 1.45 m x CAR_LENGTH); the boot open: + the hatch up. */ +#define CAR_WIDTH 1.78f +#define CAR_LENGTH 4.6f +Mesh Shapes_car(Vec3 color, bool boot_open); +/* ... seen from the driver's seat: the bonnet, the dashboard, the wheel, the pillars, the roof. */ +Mesh Shapes_carInside(Vec3 color); +/* A crate of bottles (centred): variant 0 beer, 1 water; this many bottles in it. */ +#define CRATE_SIZE_X 0.40f +#define CRATE_SIZE_Y 0.30f +#define CRATE_SIZE_Z 0.30f +Mesh Shapes_crate(int variant, int bottles); +/* Camping things (centred, lying along X): kind 0 a sleeping bag in its sack, 1 a foam mat rolled up, + 2 a small tent in its bag, 3 a big one. */ +Mesh Shapes_gear(int kind, Vec3 color); /* 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). */ diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 8548503..e72d0e5 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -777,6 +777,115 @@ static void addWheel(MeshBuilder *b, Vec3 center, float radius, float width) { transformSince(b, first, mat4_mul(mat4_translate(center), mat4_rotate_z(PI_F / 2.0f))); } +/* Your estate car (see shapes.h). */ +Mesh Shapes_car(Vec3 color, bool boot_open) { + MeshBuilder b; + MeshBuilder_init(&b); + const float w = CAR_WIDTH, l = CAR_LENGTH, wheel = 0.31f, back = -l * 0.5f; + const Vec3 glass = { 0.30f, 0.40f, 0.52f }, dark = { 0.10f, 0.10f, 0.11f }, trim = vec3_scale(color, 0.7f); + addBlock(&b, vec3(0.0f, 0.62f, 0.0f), vec3(w, 0.6f, l), color); /* the body */ + addBlock(&b, vec3(0.0f, 0.35f, 0.0f), vec3(w * 0.96f, 0.1f, l * 0.98f), dark); /* under it */ + const float cab0 = back + 0.12f, cab1 = 0.45f; /* the cabin, right to the back: an estate */ + addBlock(&b, vec3(0.0f, 1.18f, (cab0 + cab1) * 0.5f), vec3(w * 0.9f, 0.54f, cab1 - cab0), color); + addBlock(&b, vec3(0.0f, 1.2f, (cab0 + cab1) * 0.5f + 0.05f), vec3(w * 0.91f, 0.34f, cab1 - cab0 - 0.3f), glass); /* side windows */ + size_t first = b.vertex_count; /* the windscreen, leaning back */ + addBlock(&b, vec3(0.0f, 0.0f, 0.0f), vec3(w * 0.86f, 0.58f, 0.03f), glass); + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 1.16f, cab1 + 0.12f)), mat4_rotate_x(0.55f))); + addBlock(&b, vec3(0.0f, 1.2f, 0.4f), vec3(w * 0.92f, 0.04f, 0.05f), trim); /* door pillars */ + addBlock(&b, vec3(0.0f, 1.2f, -0.65f), vec3(w * 0.92f, 0.34f, 0.07f), trim); + addBlock(&b, vec3(0.0f, 0.55f, l * 0.5f + 0.03f), vec3(w * 0.92f, 0.13f, 0.07f), dark); /* bumpers */ + addBlock(&b, vec3(0.0f, 0.55f, back - 0.03f), vec3(w * 0.92f, 0.13f, 0.07f), dark); + for (int s = -1; s <= 1; s += 2) { + addBlock(&b, vec3((float)s * w * 0.36f, 0.75f, l * 0.5f + 0.005f), vec3(0.26f, 0.1f, 0.02f), (Vec3){ 0.95f, 0.93f, 0.80f }); /* headlights */ + addBlock(&b, vec3((float)s * w * 0.42f, 0.95f, back - 0.005f), vec3(0.14f, 0.2f, 0.02f), (Vec3){ 0.75f, 0.12f, 0.10f }); /* tail lights */ + addBlock(&b, vec3((float)s * (w * 0.5f + 0.06f), 1.0f, 0.35f), vec3(0.1f, 0.08f, 0.14f), dark); /* mirrors */ + } + for (int k = 0; k < 4; k++) + addWheel(&b, vec3(k % 2 ? w * 0.43f : -w * 0.43f, wheel, k / 2 ? l * 0.31f : -l * 0.31f), wheel, 0.22f); + if (boot_open) { /* the hatch up (hinged at the roof's back edge), the boot open: dark inside */ + addBlock(&b, vec3(0.0f, 1.0f, back + 0.005f), vec3(w * 0.82f, 0.62f, 0.02f), dark); + first = b.vertex_count; + addBlock(&b, vec3(0.0f, 0.0f, -0.36f), vec3(w * 0.86f, 0.04f, 0.72f), color); + addBlock(&b, vec3(0.0f, -0.025f, -0.38f), vec3(w * 0.72f, 0.02f, 0.5f), glass); + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 1.45f, cab0)), mat4_rotate_x(-0.35f))); + } else { + addBlock(&b, vec3(0.0f, 1.0f, back + 0.005f), vec3(w * 0.86f, 0.62f, 0.02f), color); + addBlock(&b, vec3(0.0f, 1.2f, cab0 - 0.005f), vec3(w * 0.72f, 0.3f, 0.02f), glass); /* the back window */ + } + return Mesh_fromBuilder(&b); +} + +/* The car seen from the driver's seat (the body round you would hide it all): + the bonnet, the dashboard, the wheel, the pillars and the roof. */ +Mesh Shapes_carInside(Vec3 color) { + MeshBuilder b; + MeshBuilder_init(&b); + const float w = CAR_WIDTH, l = CAR_LENGTH; + const Vec3 dash = { 0.16f, 0.16f, 0.17f }, roof = { 0.62f, 0.60f, 0.56f }; + addBlock(&b, vec3(0.0f, 0.84f, (0.8f + l * 0.5f) * 0.5f), vec3(w, 0.06f, l * 0.5f - 0.8f), color); /* the bonnet */ + addBlock(&b, vec3(0.0f, 0.86f, 0.66f), vec3(w * 0.92f, 0.12f, 0.3f), dash); /* the dashboard */ + size_t first = b.vertex_count; /* the wheel, the driver's side */ + shapes_addTube(&b, -0.02f, 0.19f, 0.19f, 0.03f, 18, dash, false); + shapes_addTube(&b, -0.02f, 0.03f, 0.03f, 0.04f, 8, dash, true); + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.38f, 0.95f, 0.42f)), mat4_rotate_x(PI_F * 0.5f - 0.45f))); + for (int s = -1; s <= 1; s += 2) { /* the pillars beside the windscreen */ + first = b.vertex_count; + 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 */ + return Mesh_fromBuilder(&b); +} + +/* A crate of bottles, centred: beer (brown bottles, gold caps, a red crate) + or water (green glass, blue caps, a blue crate); the first `bottles` slots full. */ +Mesh Shapes_crate(int variant, int bottles) { + MeshBuilder b; + MeshBuilder_init(&b); + const float x = CRATE_SIZE_X, h = CRATE_SIZE_Y, z = CRATE_SIZE_Z, wall = 0.015f; + const Vec3 crate = variant == 0 ? (Vec3){ 0.62f, 0.16f, 0.12f } : (Vec3){ 0.22f, 0.42f, 0.72f }; + const Vec3 glass = variant == 0 ? (Vec3){ 0.38f, 0.22f, 0.08f } : (Vec3){ 0.55f, 0.75f, 0.62f }; + const Vec3 cap = variant == 0 ? (Vec3){ 0.85f, 0.70f, 0.25f } : (Vec3){ 0.20f, 0.35f, 0.75f }; + const Vec3 hole = vec3_scale(crate, 0.45f); + addBlock(&b, vec3(0.0f, -h * 0.5f + 0.01f, 0.0f), vec3(x, 0.02f, z), crate); + addBlock(&b, vec3(0.0f, 0.0f, z * 0.5f - wall * 0.5f), vec3(x, h, wall), crate); + addBlock(&b, vec3(0.0f, 0.0f, -z * 0.5f + wall * 0.5f), vec3(x, h, wall), crate); + addBlock(&b, vec3(x * 0.5f - wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate); + addBlock(&b, vec3(-x * 0.5f + wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate); + for (int s = -1; s <= 1; s += 2) /* the handle holes in the ends */ + addBlock(&b, vec3((float)s * (x * 0.5f + 0.001f), h * 0.32f, 0.0f), vec3(0.004f, 0.035f, 0.11f), hole); + const int cols = variant == 0 ? 5 : 4, rows = variant == 0 ? 4 : 3; + for (int k = 0; k < bottles && k < cols * rows; k++) { + float cx = -x * 0.5f + x * ((float)(k % cols) + 0.5f) / (float)cols, cz = -z * 0.5f + z * ((float)(k / cols) + 0.5f) / (float)rows; + size_t first = b.vertex_count; + shapes_addTube(&b, h * 0.5f - 0.07f, 0.024f, 0.012f, 0.1f, 6, glass, false); /* the neck out of the top */ + shapes_addTube(&b, h * 0.5f + 0.03f, 0.014f, 0.014f, 0.012f, 6, cap, true); + transformSince(&b, first, mat4_translate(vec3(cx, 0.0f, cz))); + } + return Mesh_fromBuilder(&b); +} + +/* Camping things, centred, lying along X (see shapes.h). */ +Mesh Shapes_gear(int kind, Vec3 color) { + MeshBuilder b; + MeshBuilder_init(&b); + const float len[] = { 0.42f, 0.6f, 0.5f, 0.6f }, rad[] = { 0.13f, 0.09f, 0.08f, 0.1f }; + float l = len[kind & 3], r = rad[kind & 3]; + Vec3 dark = vec3_scale(color, 0.65f); + size_t first = b.vertex_count; + shapes_addTube(&b, -l * 0.5f, r, r, l, 12, color, true); + if (kind == 1) { /* a foam mat rolled up: two straps */ + shapes_addTube(&b, -l * 0.3f, r * 1.04f, r * 1.04f, 0.03f, 12, dark, false); + shapes_addTube(&b, l * 0.27f, r * 1.04f, r * 1.04f, 0.03f, 12, dark, false); + } else { /* a stuff sack: the drawstring end gathered, a toggle */ + shapes_addTube(&b, l * 0.5f, r * 0.8f, r * 0.25f, 0.04f, 12, dark, true); + shapes_addTube(&b, l * 0.5f + 0.04f, 0.015f, 0.015f, 0.03f, 6, (Vec3){ 0.1f, 0.1f, 0.1f }, true); + } + transformSince(&b, first, mat4_rotate_z(-PI_F * 0.5f)); + return Mesh_fromBuilder(&b); +} + /* An old tin can as ashtray: the butts stick out of the ash, more and more, a little heap of them when it's full. */ static void buildAshtray(MeshBuilder *b, Vec3 size, int butts) { diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c new file mode 100644 index 0000000..9c7ce3f --- /dev/null +++ b/entropie/src/world/car.c @@ -0,0 +1,212 @@ +#include "world/car.h" +#include "world/site.h" +#include "render/shapes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define SEAT_X 0.38f /* seats this far left (+X: the driver's side) or right of the middle */ +#define SEAT_Z -0.95f /* the back seats this far back from the front ones */ +#define EYE_UP 1.18f /* the eyes this high, sitting */ +#define DOOR_OUT 0.6f /* out of the door: standing this far beside the car */ +#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 + +static const Vec3 CRATE_BOTTLE = { 0.07f, 0.25f, 0.0f }; + +/* Local (nose to +Z, the driver's side +X) to world, on the car's ground. */ +static Vec3 local(const Object *car, Vec3 p) { + return vec3_add(car->position, mat4_mul_dir(mat4_rotate_y(car->rotation.y), p)); +} + +Object *Car_create(Vec3 position, float yaw) { + Object *car = Object_create(OBJ_CAR, PALETTE_STANDARD, position, vec3(CAR_WIDTH, 1.45f, CAR_LENGTH), 1300.0f); + if (!car) return NULL; + car->load = calloc(1, sizeof(*car->load)); + snprintf(car->name, sizeof(car->name), "Car"); + Object_setRotation(car, vec3(0.0f, yaw, 0.0f)); + return car; +} + +bool Car_load(Object *car, CargoKind kind, Object *owner) { + CarLoad *l = car->load; + if (!l || l->n >= CAR_LOAD_MAX) return false; + l->kind[l->n] = kind; + l->owner[l->n] = owner; + l->n++; + return true; +} + +static const char *const CARGO_NAME[] = { + [CARGO_BEER] = "a crate of beer", [CARGO_WATER] = "a crate of water", [CARGO_SLEEPING_BAG] = "a sleeping bag", + [CARGO_MAT] = "a sleeping mat", [CARGO_TENT_SMALL] = "a tent", [CARGO_TENT_BIG] = "the big tent", +}; + +const char *Car_nextName(const Object *car) { + const CarLoad *l = car->load; + return l && l->n > 0 ? CARGO_NAME[l->kind[l->n - 1]] : NULL; +} + +bool Car_nextHeavy(const Object *car) { + const CarLoad *l = car->load; + return l && l->n > 0 && (l->kind[l->n - 1] == CARGO_BEER || l->kind[l->n - 1] == CARGO_WATER || l->kind[l->n - 1] == CARGO_TENT_BIG); +} + +/* Everybody's own colours for their things (by name: you, Volkram, Jana, Toni, anybody else). */ +static int whose(const Object *owner) { + if (!owner || owner->type == OBJ_PLAYER) return 0; + if (!strcmp(owner->name, "Volkram")) return 1; + if (!strcmp(owner->name, "Jana")) return 2; + if (!strcmp(owner->name, "Toni")) return 3; + return 4; +} + +static Vec3 gearColor(GearKind kind, const Object *owner) { + static const Vec3 BAGS[] = { { 0.70f, 0.18f, 0.16f }, { 0.20f, 0.36f, 0.22f }, { 0.48f, 0.26f, 0.58f }, { 0.88f, 0.48f, 0.12f }, { 0.4f, 0.4f, 0.45f } }; + static const Vec3 MATS[] = { { 0.20f, 0.40f, 0.75f }, { 0.85f, 0.75f, 0.20f }, { 0.25f, 0.65f, 0.60f }, { 0.75f, 0.30f, 0.45f }, { 0.5f, 0.5f, 0.5f } }; + switch (kind) { + case GEAR_SLEEPING_BAG: return BAGS[whose(owner)]; + case GEAR_MAT: return MATS[whose(owner)]; + case GEAR_TENT_SMALL: return vec3(0.30f, 0.45f, 0.25f); + case GEAR_TENT_BIG: return vec3(0.42f, 0.42f, 0.24f); + } + return vec3(0.5f, 0.5f, 0.5f); +} + +Object *Car_takeOut(World *world, Object *car) { + CarLoad *l = car->load; + if (!l || l->n == 0 || !car->boot_open) return NULL; + CargoKind kind = l->kind[l->n - 1]; + Object *owner = l->owner[l->n - 1]; + Vec3 at = local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - SET_DOWN)); + at.y = World_groundHeight(world, at.x, at.z, at.y + 1.0f) + 0.02f; + Object *item; + if (kind == CARGO_BEER || kind == CARGO_WATER) { + item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), kind == CARGO_WATER ? 1.0f : 0.0f); + } else { + GearKind g = kind == CARGO_SLEEPING_BAG ? GEAR_SLEEPING_BAG : kind == CARGO_MAT ? GEAR_MAT + : kind == CARGO_TENT_SMALL ? GEAR_TENT_SMALL : GEAR_TENT_BIG; + item = Object_create(OBJ_GEAR, PALETTE_STANDARD, at, vec3_zero(), (float)g); + if (item) { + item->owner = owner; + Mesh_destroy(&item->mesh); + item->mesh = Shapes_gear((int)g, gearColor(g, owner)); + } + } + if (!item) return NULL; + item->rotation.y = car->rotation.y + PI_F * 0.5f; /* (lying across the back of it) */ + Object_syncColliders(item); + if (!World_add(world, item)) return NULL; + l->n--; + return item; +} + +Vec3 Car_bootSpot(const Object *car) { + return local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - BOOT_BACK)); +} + +static Vec3 seatAt(int seat) { + return vec3(seat % 2 == 0 ? SEAT_X : -SEAT_X, 0.0f, seat >= 2 ? SEAT_Z : 0.0f); +} + +Vec3 Car_eye(const Object *car, int seat) { + Vec3 s = seatAt(seat); + s.y = EYE_UP; + return local(car, s); +} + +Vec3 Car_door(const Object *car, int seat) { + Vec3 s = seatAt(seat); + s.x = (s.x > 0.0f ? 1.0f : -1.0f) * (CAR_WIDTH * 0.5f + DOOR_OUT); + return local(car, s); +} + +void Car_place(World *world, Object *car, Vec3 at, float yaw) { + at.y = world->has_terrain ? Terrain_height(&world->terrain, at.x, at.z) : at.y; + car->position = at; + car->rotation = vec3(0.0f, yaw, 0.0f); + Object_syncColliders(car); + 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); + o->rotation.y = yaw; + o->velocity = vec3_zero(); + Object_syncColliders(o); + } +} + +void Car_getIn(Object *car, Object *who, int seat) { + who->riding = car; + who->ride_seat = seat; + who->velocity = vec3_zero(); + who->movement = vec3_zero(); + who->sitting = false; +} + +void Car_getOut(Object *who) { + Object *car = who->riding; + if (!car) return; + Vec3 door = Car_door(car, who->ride_seat); + who->riding = NULL; + 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) */ + who->velocity = vec3_zero(); + who->body.replant = true; + Object_syncColliders(who); +} + +void Car_openBoot(Object *car, bool open) { + if (car->boot_open == open) return; + car->boot_open = open; + Mesh_destroy(&car->mesh); + car->mesh = Shapes_car(vec3(0.24f, 0.34f, 0.52f), open); +} + +/* Where the camping things of this many go: by whose tent (you, Volkram and Jana, Toni). */ +static int campOf(const Object *owner) { + int w = whose(owner); + return w == 1 || w == 2 ? 1 : w == 3 ? 2 : 0; +} + +Vec3 Car_dropSpot(const World *world, const Object *item) { + if (item->type == OBJ_CRATE) { + Vec3 base = Site_drinksSpot(), along = Site_campAlong(); + int n = 0; + for (int i = 0; i < world->count; i++) { + const Object *o = world->objects[i]; + if (o != item && o->type == OBJ_CRATE && !o->is_held && vec3_length(vec3_sub(o->position, base)) < 1.6f) n++; + } + return vec3_add(base, vec3_scale(along, 0.5f * (float)n)); + } + Vec3 along = Site_campAlong(), across = vec3(-along.z, 0.0f, along.x); + Vec3 at = Site_campSpot(campOf(item->owner)); + float a = item->variant == GEAR_SLEEPING_BAG ? 0.0f : item->variant == GEAR_MAT ? 0.55f : 1.1f; + float b = whose(item->owner) == 2 ? 0.7f : 0.0f; /* (Jana's beside Volkram's) */ + return vec3_add(at, vec3_add(vec3_scale(along, a), vec3_scale(across, b))); +} + +Object *Crate_takeBottle(World *world, Object *crate) { + if (crate->type != OBJ_CRATE || crate->stored <= 0) return NULL; + Vec3 top = vec3_add(Object_colliderCenter(crate, 0), vec3(0.0f, CRATE_SIZE_Y * 0.5f + 0.16f, 0.0f)); + Object *b = Object_create(crate->variant ? OBJ_WATER : OBJ_BOTTLE, PALETTE_WARM, top, CRATE_BOTTLE, BOTTLE_KG); + if (!b || !World_add(world, b)) return NULL; + crate->stored--; + crate->weight = fmaxf(2.0f, crate->weight - (crate->variant ? 1.2f : 0.85f)); + return b; +} + +void Car_serve(World *world) { + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (!o->is_character || !o->ai.take_from) continue; + Object *from = o->ai.take_from; + o->ai.take_from = NULL; + Object *got = from->type == OBJ_CRATE ? Crate_takeBottle(world, from) : from->type == OBJ_CAR ? Car_takeOut(world, from) : NULL; + if (got) o->ai.target = got; + } +} diff --git a/entropie/src/world/car.h b/entropie/src/world/car.h new file mode 100644 index 0000000..a98bfb6 --- /dev/null +++ b/entropie/src/world/car.h @@ -0,0 +1,57 @@ +#ifndef WORLD_CAR_H +#define WORLD_CAR_H + +/* + * Your car (OBJ_CAR, an estate): it drives in at the start (moved by hand, + * see game_arrival.c), the ones in it riding (hidden, no physics, no AI). + * Its boot holds what you brought - crates of beer and water (OBJ_CRATE), + * sleeping bags, mats and tents (OBJ_GEAR, each somebody's) - taken out one + * by one: you click at the open boot, the others unload (ai.c unloadCar). + * Crates hand out bottles: you click one, the others ask (ai.take_from). + */ + +#include "world/world.h" + +#define CRATE_BEER 20 /* bottles in a crate of beer */ +#define CRATE_WATER 12 /* ... of water */ + +typedef enum { GEAR_SLEEPING_BAG, GEAR_MAT, GEAR_TENT_SMALL, GEAR_TENT_BIG } GearKind; +typedef enum { CARGO_BEER, CARGO_WATER, CARGO_SLEEPING_BAG, CARGO_MAT, CARGO_TENT_SMALL, CARGO_TENT_BIG } CargoKind; + +#define CAR_LOAD_MAX 20 +typedef struct CarLoad { + int n; + CargoKind kind[CAR_LOAD_MAX]; + Object *owner[CAR_LOAD_MAX]; +} CarLoad; + +/* A car with an empty boot (shut). */ +Object *Car_create(Vec3 position, float yaw); +bool Car_load(Object *car, CargoKind kind, Object *owner); +/* What comes out next ("A crate of beer"), NULL: the boot is empty. */ +const char *Car_nextName(const Object *car); +/* ... and whether it takes both hands. */ +bool Car_nextHeavy(const Object *car); +/* The next thing out of the (open) boot, lying at its edge: NULL if empty or shut. */ +Object *Car_takeOut(World *world, Object *car); +/* Where one stands to reach into the boot (behind it). */ +Vec3 Car_bootSpot(const Object *car); +/* Seat k (0 the driver, 1 beside, 2 and 3 behind): the eye, and where they stand once out. */ +Vec3 Car_eye(const Object *car, int seat); +Vec3 Car_door(const Object *car, int seat); +/* Moves it (and whoever rides in it) here, facing yaw (rotation.y), on the ground. */ +void Car_place(World *world, Object *car, Vec3 at, float yaw); +void Car_getIn(Object *car, Object *who, int seat); +void Car_getOut(Object *who); +void Car_openBoot(Object *car, bool open); + +/* Where an unloaded thing goes: crates by the fire, the camping things where its owner's tent will be. */ +Vec3 Car_dropSpot(const World *world, const Object *item); + +/* A bottle out of a crate (on top of it, for the taking): NULL if it's empty. */ +Object *Crate_takeBottle(World *world, Object *crate); +/* Every frame: whoever asked for a bottle from a crate or the next thing from + the boot (ai.take_from) gets it lying there for them (ai.target). */ +void Car_serve(World *world); + +#endif /* WORLD_CAR_H */ diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 1907b88..74f3036 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -1,4 +1,5 @@ #include "world/object.h" +#include "world/car.h" #include <stdio.h> #include <stdlib.h> @@ -218,8 +219,11 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size obj->grill_mesh = Shapes_grill(); obj->is_static = true; obj->pivot = vec3_zero(); - /* Low enough to step over; wood put in rests on the ashes */ - addCollider(obj, vec3(0.0f, 0.03f, 0.0f), vec3(size.x, 0.06f, size.x), vec3_zero()); + /* Low enough to step over; wood put in rests on the ashes. Inside the + ring of stones (its corners at the stones: a square as wide as the + ring stuck out past them, and people beside the fire stepped on and + off its corners, jittering) */ + addCollider(obj, vec3(0.0f, 0.03f, 0.0f), vec3(size.x * 0.6f, 0.06f, size.x * 0.6f), vec3_zero()); break; case OBJ_TRASH: @@ -347,6 +351,40 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size break; } + case OBJ_CAR: { /* (see car.c Car_create) */ + const float back = -size.z * 0.5f; + obj->mesh = Shapes_car(vec3(0.24f, 0.34f, 0.52f), false); + obj->inside = Shapes_carInside(vec3(0.24f, 0.34f, 0.52f)); + obj->is_static = true; + obj->weight = 1300.0f; + addCollider(obj, vec3(0.0f, 0.62f, 0.0f), vec3(size.x, 0.74f, size.z), vec3_zero()); + addCollider(obj, vec3(0.0f, 1.2f, (back + 0.12f + 0.45f) * 0.5f), vec3(size.x * 0.9f, 0.5f, 0.45f - back - 0.12f), vec3_zero()); + break; + } + + case OBJ_CRATE: + obj->variant = weight > 0.5f ? 1 : 0; + obj->stored = obj->variant ? CRATE_WATER : CRATE_BEER; + obj->size = vec3(CRATE_SIZE_X, CRATE_SIZE_Y, CRATE_SIZE_Z); + obj->weight = 2.0f + (float)obj->stored * (obj->variant ? 1.2f : 0.85f); + obj->mesh = Shapes_crate(obj->variant, obj->stored); + obj->shown = obj->stored; + obj->pivot = vec3(0.0f, CRATE_SIZE_Y * 0.5f, 0.0f); + addCollider(obj, vec3_zero(), obj->size, vec3_zero()); + break; + + case OBJ_GEAR: { /* kind = weight; its colour: see car.c */ + static const Vec3 SIZE[] = { { 0.42f, 0.26f, 0.26f }, { 0.6f, 0.18f, 0.18f }, { 0.5f, 0.16f, 0.16f }, { 0.6f, 0.2f, 0.2f } }; + static const float KG[] = { 1.6f, 0.6f, 2.4f, 4.5f }; + obj->variant = (int)weight & 3; + obj->size = SIZE[obj->variant]; + obj->weight = KG[obj->variant]; + obj->mesh = Shapes_gear(obj->variant, vec3(0.5f, 0.5f, 0.5f)); + obj->pivot = vec3(0.0f, obj->size.y * 0.5f, 0.0f); + addCollider(obj, vec3_zero(), obj->size, vec3_zero()); + break; + } + case OBJ_CLOTHES: /* (made in clothes.c, its mesh given) */ obj->is_static = true; break; @@ -495,6 +533,8 @@ void Object_destroy(Object *obj) { Mesh_destroy(&obj->mesh); Mesh_destroy(&obj->flames); Mesh_destroy(&obj->grill_mesh); + Mesh_destroy(&obj->inside); + free(obj->load); free(obj); } @@ -1219,6 +1259,21 @@ 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_CAR: + if (!obj->load || obj->load->n == 0) snprintf(out, size, "Your car - the boot is empty"); + else if (!obj->boot_open) snprintf(out, size, "Your car - full of your things"); + else snprintf(out, size, "Your car - left click at the boot: take out %s", Car_nextName(obj)); + break; + case OBJ_CRATE: + snprintf(out, size, "Crate of %s - %d bottle%s left%s", obj->variant ? "water" : "beer", obj->stored, obj->stored == 1 ? "" : "s", + obj->stored > 0 && !obj->is_held ? " (left click: take one)" : ""); + break; + case OBJ_GEAR: { + static const char *const WHAT[] = { "sleeping bag", "sleeping mat", "tent", "tent (the big one)" }; + if (obj->owner && obj->owner->type == OBJ_PLAYER) snprintf(out, size, "Your %s", WHAT[obj->variant & 3]); + else snprintf(out, size, "%s's %s", obj->owner ? obj->owner->name : "Somebody", WHAT[obj->variant & 3]); + 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"); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 718d281..9f05da7 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -82,6 +82,10 @@ typedef enum { 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 */ + OBJ_CAR, /* static (moved by hand while it drives): your estate car, size = (width, height, length), nose to +Z; + what's in its boot: load (see car.h) */ + OBJ_CRATE, /* a crate of bottles: weight 0 = beer (CRATE_BEER bottles), 1 = water (CRATE_WATER); stored = bottles in it */ + OBJ_GEAR, /* camping things: variant = GearKind (see car.h), owner = whose */ } ObjectType; /* What a body needs over the day. All 0..1; 0 is fine (stamina: 1 is fine). */ @@ -153,6 +157,12 @@ typedef struct Object { float nourish; /* hunger taken away per bite */ float refresh; /* stamina given back per sip / bite */ ObjectType dispenses; /* supplies: what you get from it */ + 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 */ + struct Object *owner; /* camping gear: whose */ + struct Object *riding; /* characters: sitting in this car (hidden, no physics, no AI: see car.c) ... */ + int ride_seat; /* ... on this seat (0 the driver) */ int branches; /* trees: branches left on the crown */ int stored; /* woodpiles: pieces stacked on it; firewood: 1 = lies on a woodpile */ float work; /* trees and logs: work done on the current job (see tools.h) */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index b901075..ad9f3cf 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -1076,7 +1076,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) return; /* moved by hand, or out of sight */ + if (obj->climbing || obj->on_grill || obj->toilet || obj->riding) 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/site.c b/entropie/src/world/site.c index 3b81b87..6fd60cf 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -525,6 +525,26 @@ Vec3 Site_groundColor(float x, float z, Vec3 meadow) { /* ---------- Building it ---------- */ +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); } + +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) */ + k = k < 0 ? 0 : k > 2 ? 2 : k; + return court(AT[k][0], AT[k][1]); +} + +Vec3 Site_campAlong(void) { return mapDir(COURT_ANGLE); } + +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) */ + *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) */ +} + Vec3 Site_yard(void) { return court(YARD_A, YARD_B); } diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h index 7a9c6c5..8d05d56 100644 --- a/entropie/src/world/site.h +++ b/entropie/src/world/site.h @@ -22,6 +22,16 @@ #define SITE_SCALE 0.426f /* meters per pixel of the traced map */ +/* 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. */ +Vec3 Site_drinksSpot(void); +Vec3 Site_campSpot(int k); +Vec3 Site_campAlong(void); +/* 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. */ Vec3 Site_yard(void); /* The main building of the courtyard: the first thing you look at. */ diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index f03aba6..d9e7133 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -1,6 +1,7 @@ #include "world/world.h" #include "world/camp.h" #include "world/clothes.h" +#include "world/car.h" #include "people/pockets.h" #include "people/condition.h" #include "people/dog.h" @@ -799,6 +800,11 @@ static float scootTo(const World *world, const Object *chair) { static void showProps(World *world) { for (int i = 0; i < world->count; i++) { Object *f = world->objects[i]; + if (f->type == OBJ_CRATE && f->shown != f->stored) { /* (a bottle taken: one fewer shows) */ + Mesh_destroy(&f->mesh); + f->mesh = Shapes_crate(f->variant, f->stored); + f->shown = f->stored; + } if (f->type != OBJ_FURNITURE) continue; if (f->variant == FURN_GATE) { if (f->gate_t != (f->gate_open ? 1.0f : 0.0f)) Object_swingGate(f, world->dt_props); continue; } if (f->variant == FURN_CHAIR) { if (f->chair_out != f->chair_goal && !chairWaits(world, f)) slideChair(world, f); continue; } @@ -1225,6 +1231,7 @@ void World_update(World *world, float dt, float game_dt) { Object_indexKinds(world->objects, world->count); world->dt_props = dt; Clothes_update(world); + Car_serve(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]; @@ -1241,7 +1248,7 @@ 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->type == OBJ_HUMAN) { Ai_useGrid(&world->grid); Ai_useWorld(world); Ai_update(o, world->objects, world->count, dt); } + 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); if (o->type == OBJ_HUMAN && o->body.stepped_off) { /* lost the balance up on something */ @@ -1378,10 +1385,17 @@ static void drawCharred(Renderer *renderer, const Object *o) { Renderer_drawMeshTinted(renderer, &o->mesh, model, vec3(dark, dark * 0.95f, dark * 0.9f), o->glow); } +/* You're sitting in this car. */ +static bool playerIn(const World *world, const Object *car) { + for (int i = 0; i < world->count; i++) + if (world->objects[i]->type == OBJ_PLAYER) return world->objects[i]->riding == car; + return false; +} + 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->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->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) { @@ -1435,7 +1449,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) continue; /* in the toilet */ + if (obj->is_character && (obj->toilet || obj->riding)) continue; /* in the toilet, in the car */ if (obj->is_character) { FigurePose pose = figurePose(obj); Renderer_drawFigure(renderer, &pose); @@ -1444,6 +1458,8 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { drawFlames(renderer, &world->flames, vec3_add(obj->position, vec3(0.0f, down ? 0.0f : 0.3f, 0.0f)), down ? 1.3f : 0.8f, down ? 0.9f : 2.0f, (float)i); } + } else if (obj->type == OBJ_CAR && !draw_player && playerIn(world, obj)) { /* (from the driver's seat: inside it) */ + Renderer_drawMesh(renderer, &obj->inside, Object_modelMatrix(obj)); } else if (obj->type == OBJ_DOG) { Dog_draw(renderer, obj); } else if (obj->type == OBJ_HOSE) {