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.7.0: tents
- 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
entropie/Makefile | 2 +- entropie/README.md | 17 ++- entropie/STATE.md | 12 ++- entropie/commit_message.txt | 24 ++--- entropie/src/game/game.c | 1 + entropie/src/game/game.h | 2 + entropie/src/game/game_actions.c | 97 +++++++++++++++++- entropie/src/people/ai.c | 128 +++++++++++++++++++++++ entropie/src/people/ai.h | 3 + entropie/src/people/condition.c | 6 +- entropie/src/render/shapes.h | 12 +++ entropie/src/render/shapes_props.c | 92 +++++++++++++++++ entropie/src/world/car.c | 13 ++- entropie/src/world/car.h | 5 +- entropie/src/world/object.c | 19 ++++ entropie/src/world/object.h | 9 +- entropie/src/world/physics.c | 3 +- entropie/src/world/tent.c | 205 +++++++++++++++++++++++++++++++++++++ entropie/src/world/tent.h | 39 +++++++ entropie/src/world/world.c | 6 +- 20 files changed, 667 insertions(+), 28 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index cb27b9d..9123b08 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.6.0 +VERSION := 2.7.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 0b674dc..9322bbd 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -36,6 +36,19 @@ Painted in the rotoscope style of *Waking Life* (2001): - Crates: left click takes a bottle out (the others help themselves too), E / Q carries the whole crate +## Tents + +- In the orchard, 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) +- Several working on one: quicker. The others come over and give you a hand +- Put your sleeping bag and mat down at the door: they go in +- Z at your tent's door: crawl in and sleep (better with your sleeping bag + in it: warm, and you're rested sooner). Z again: you crawl out +- Sleepy, the others go to bed in their tents + ## The site The map is Homburgshöhe at Geflügelhof Oderland, traced from the real map @@ -638,7 +651,9 @@ say no more often and don't come to you for a bit. | Left click on the fire, hand free | Take food off the grill | | P | Pee (next to the toilet: use it); Shift: further, mouse: aim | | K | Light a cigarette / put it out | -| Z | Sleep / get up | +| Left click at the car's boot | Take out the next thing | +| Left click on a tent | Spread it out; hold: put it up | +| Z | Sleep / get up (at your tent's door: in it) | | C | Sit down / get up; on fire: drop and roll; at a fire with a drink: pour it on | | Space (asleep, hold) | Sleep faster | | G | One more person helps you | diff --git a/entropie/STATE.md b/entropie/STATE.md index 7cb7c71..bf58c02 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -187,6 +187,14 @@ Round 2.6: - 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). +Round 2.7: +- Tents (OBJ_TENT, `tent.c`: small 1.4 x 2.1 x 1.0, big 2.2 x 2.4 x 1.3; `Shapes_tent` by stage: the ground sheet, the inner tent - a dome - rising with the poles, two poles corner to corner, the cover going over from the back, pegs with guy lines, the door; built again in 12 steps a stage). `Tent_unpack`: a tent bag where it lies, the door facing the given way (NPCs: `Car_campDoor`, towards the yard; you: towards you). Stages (`Tent_work`, one average person: x (0.75 + 0.5 wits), drunk slower): spread out 4 s, poles 16 / 24 s, cover 10 / 14 s, pegs 2.5 s each (a stone in hand: half), 6 / 10 pegs. Its box grows with it (nobody stands on a tent: `World_groundHeight` and step-over leave it out). +- People (`ai.c` pitchTents): after unloading, their own bag spread out and the tent pitched (at `Tent_workSpot`, the hands at `Tent_handsAt`: the edge, the poles, the cover, the next peg - hammering); somebody at work on one within TENT_HELP 25 m (you too): HELP_CHANCE 0.12/s x (0.5 + willing) they come over ("I'll give you a hand"), for up to 150 s; their tent up: their sleeping bag and mat carried to its door. `goToBed`: sleepiness 0.8+, their tent up within 70 m: to its door, `Tent_crawlIn` (lying back into it, the feet by the door, `in_tent`: no collisions with it), up again: out at the door (`Tent_update`). Far walks look for the way round first (`goToPoint`: `findWay` over 6 m - Toni stood behind the orchard's hedge bumping his shins). +- Beds: a sleeping bag or mat put down within 0.8 m of the door of its owner's tent (cover on) goes in (`tent_beds`). Asleep in a tent: sleep goes 1.15x, with their sleeping bag 1.4x, and they warm up (no chill). +- Unloading puts each tent bag on its camp spot and the sleeping bags and mats CAMP_BEDS_OUT 2.8 m out in front of where its door will be. +- 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. + 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). @@ -334,7 +342,7 @@ 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 -- Tents: Volkram and Jana share one, Toni and you a small one each; pitching together (poles, the cover, pegs); sleeping in them +- 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 **Tools for us** @@ -344,4 +352,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.6.0 (build 963c4fd)` +State of `entropie 2.7.0 (build ba5c3a3)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index ffdb5fa..7055ce2 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,14 +1,12 @@ -entropie 2.6.0: arriving by car +entropie 2.7.0: tents -- 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 +- 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 diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 0ab49bd..166ab32 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -285,6 +285,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { else if (stamina > WORN_OUT_UNTIL) game->worn_out = false; } Camera_rotate(&game->camera, input->look_yaw, input->look_pitch); + game->dt = dt; game_handleActions(game, input); game_talk(game, input, dt); grabLook(game, input, dt); diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index f217356..4366617 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -67,6 +67,8 @@ typedef struct { 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 */ + struct Object *tent; /* the tent you're working on (left button held) */ + float dt; /* this frame's step */ } 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 f9be548..4c2ce62 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -7,6 +7,7 @@ #include "people/pockets.h" #include "world/clothes.h" #include "world/car.h" +#include "world/tent.h" #include "people/climb.h" #include <stdlib.h> @@ -16,7 +17,7 @@ #define APPLE_REACH 1.6f /* from the trunk of an apple tree */ #define BAG_INTERVAL 0.35f /* holding the button: the next piece into the bag */ -enum { CLICK_NONE, CLICK_WORK, CLICK_DRINK, CLICK_BAG, CLICK_WATER }; +enum { CLICK_NONE, CLICK_WORK, CLICK_DRINK, CLICK_BAG, CLICK_WATER, CLICK_TENT }; #define BUSH_OVER 1.3f /* bushes lower than this (scaled): you try to get over, and land in it */ #define CLIMB_PUSH 0.3f /* walking against it this long starts the climb */ @@ -698,6 +699,61 @@ static bool takePullover(Game *game) { return true; } +/* ---------- Tents ---------- */ + +#define TENT_REACH 1.2f /* m: a tent this near to work on it */ + +/* A packed tent (its bag lying) or a tent not up yet, near and in front. */ +static Object *tentNear(const Game *game, bool packed) { + const Object *p = game->player; + Vec3 facing = Object_facing(p); + Object *best = NULL; + float best_gap = packed ? 0.9f : TENT_REACH; + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + bool fits = packed ? o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->is_held && !o->broken + : o->type == OBJ_TENT && o->tent_stage < TENT_UP; + if (!fits) continue; + Vec3 to = vec3_sub(o->position, p->position); + to.y = 0.0f; + float gap = Hands_gapFrom(p->position, p->look.height, o); + if (gap < best_gap && (gap < 0.3f || vec3_dot(vec3_normalize(to), facing) > 0.2f)) { best = o; best_gap = gap; } + } + return best; +} + +/* Left click: a packed tent in front spread out (the door towards you), or a tent going up: work on it (held). */ +static int tentClick(Game *game) { + Object *p = game->player, *bag = tentNear(game, true); + if (bag) { + Vec3 door = vec3_sub(p->position, bag->position); + Object *tent = Tent_unpack(&game->world, bag, door); + if (tent) { + Hands_touch(p, tent->position); + say(game, Tent_isTheirs(tent, p) ? "You spread your tent out - hold left click to put it up" + : "You spread the tent out - hold left click to put it up"); + } + return CLICK_NONE; + } + Object *tent = tentNear(game, false); + if (!tent) return -1; + if (p->hands.state[HAND_LEFT] != HAND_FREE && p->hands.state[HAND_RIGHT] != HAND_FREE) { say(game, "A hand free for that"); return CLICK_NONE; } + game->tent = tent; + return CLICK_TENT; +} + +/* Left button held at a tent: the next bit of it. */ +static bool workTent(Game *game) { + Object *p = game->player, *tent = game->tent; + if (!tent || tent->tent_stage >= TENT_UP || Hands_gapFrom(p->position, p->look.height, tent) > TENT_REACH + 0.6f) return false; + Hands_touch(p, Tent_handsAt(tent, p, tent->tent_work * 18.0f)); + int got = Tent_work(tent, p, game->dt); + static const char *const SAID[] = { "", "Spread out - now the poles in and up", "The poles are in - now pull the cover over it", + "The cover's on - now peg it down (a stone helps)", "All pegged - the tent stands" }; + if (got >= 0) say(game, SAID[got % 5]); + return tent->tent_stage < TENT_UP; +} + /* At the cigarette machine: a pack. */ static bool smokesMachine(Game *game) { Object *m = furnitureNear(game, FURN_SMOKES, 0.9f); @@ -799,6 +855,13 @@ static bool game_bootHint(Game *game, char *out, size_t size) { 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 && 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) { + snprintf(out, size, "Put it down at %s tent's door (right click there): it goes in", it->owner == p ? "your" : "their"); + 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"); @@ -812,6 +875,23 @@ static bool game_bootHint(Game *game, char *out, size_t size) { return true; } +/* At a tent: what to do with it now. */ +static bool tentHint(Game *game, char *out, size_t size) { + Object *p = game->player, *bag = tentNear(game, true), *tent = tentNear(game, false); + if (bag) { snprintf(out, size, "A tent, packed - left click: spread it out here (the door towards you) - E / Q: carry it"); return true; } + if (tent) { snprintf(out, size, "The tent: hold left click - %s%s", Tent_job(tent), tent->tent_stage == TENT_PEGS ? " (a stone in the other hand: quicker)" : ""); return true; } + Object *mine = Tent_of(game->world.objects, game->world.count, p); + if (!mine || mine->tent_stage < TENT_COVER) return false; + Vec3 door = Tent_door(mine); + if (hypotf(door.x - p->position.x, door.z - p->position.z) > 1.6f) return false; + if (Hands_carries(p, OBJ_GEAR)) { snprintf(out, size, "Your tent's door: right click - put it down here, it goes in"); return true; } + if (mine->tent_stage >= TENT_UP) { + snprintf(out, size, "Your tent%s - Z: crawl in and sleep", Tent_hasBed(mine, p) ? "" : " (your sleeping bag isn't in it: put it down at the door)"); + return true; + } + return false; +} + /* 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; @@ -841,6 +921,7 @@ const char *Game_hint(Game *game) { 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 (tentHint(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) @@ -1002,6 +1083,8 @@ static int leftClick(Game *game) { Talk_showHere(&game->world, p, shown); return CLICK_NONE; } + int tent = tentClick(game); + if (tent >= 0) return tent; 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; @@ -1050,6 +1133,9 @@ static void keepClicking(Game *game) { case CLICK_WATER: p->spraying = true; /* see the world: the water arc */ break; + case CLICK_TENT: + if (!workTent(game)) game->click_action = CLICK_NONE; + break; default: break; } @@ -1080,6 +1166,15 @@ static void sleepOrWake(Game *game) { } if (Body_isBusy(b)) return; if (p->needs.sleepiness < 0.3f) { say(game, "Not tired"); return; } + Object *tent = Tent_of(game->world.objects, game->world.count, p); + if (tent && tent->tent_stage >= TENT_UP) { + Vec3 door = Tent_door(tent); + if (hypotf(door.x - p->position.x, door.z - p->position.z) < 1.5f) { + Tent_crawlIn(tent, p); + say(game, Tent_hasBed(tent, p) ? "You crawl into your tent and your sleeping bag" : "You crawl into your tent (no sleeping bag in it: a cold night)"); + return; + } + } Body_lieDown(b); } diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index fc83009..9391d63 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -12,6 +12,7 @@ #include "world/world.h" #include "world/clothes.h" #include "world/car.h" +#include "world/tent.h" #include "render/shapes.h" /* ---------- Small helpers ---------- */ @@ -1692,6 +1693,7 @@ static Object *carriedCargo(const Object *self) { 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; } + if (!self->ai.path_len && d > 6.0f) findWay(self, p); /* (further: the way round from the start - hedges, walls) */ 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); @@ -1754,6 +1756,128 @@ static bool unloadCar(Object *self, Object *const *objects, int count, float dt) return true; } +/* Tents: their own spread out where its bag lies and pitched; somebody at + work on one near (you too): a hand lent, the willing; their own up: the + sleeping bag and mat in at its door. Sleepy, their tent up: to bed in it. */ +#define TENT_WALK 70.0f /* m: their tent (or its bag) this near */ +#define TENT_HELP 25.0f /* m: somebody pitching one this near ... */ +#define HELP_CHANCE 0.12f /* ... gets a hand this often a second (x willing) */ +#define HELP_LONG 150.0f/* s: helping, then they've had enough */ +#define BED_WALK 12.0f /* m: their sleeping bag or mat lying this near their tent: brought in */ + +static Object *ownGear(Object *self, Object *const *objects, int count, bool tent_bag) { + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o->type != OBJ_GEAR || o->owner != self || o->broken) continue; + if (tent_bag == (o->variant >= GEAR_TENT_SMALL)) return o; + } + return NULL; +} + +static Object *bedThing(Object *self, Object *const *objects, int count, const Object *tent) { + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o->type == OBJ_GEAR && o->owner == self && !o->broken && o->variant <= GEAR_MAT && + (!o->is_held || Hands_carries(self, OBJ_GEAR)) && flatDistance(o->position, tent->position) < BED_WALK) return o; + } + return NULL; +} + +static void workOnTent(Object *self, Object *tent, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + float side = Tent_isTheirs(tent, self) ? (tent->owner2 == self ? -1.0f : 1.0f) : (ai->side != 0.0f ? -ai->side : -1.0f); + Vec3 spot = Tent_workSpot(tent, side); + if (tent->tent_stage == TENT_PEGS && !Tent_isTheirs(tent, self)) { /* (a helper: the next peg round from the owner's) */ + Vec3 out = vec3_sub(spot, tent->position); + spot = vec3_add(spot, vec3_scale(vec3_normalize(vec3(-out.z, 0.0f, out.x)), 0.6f)); + } + if (!goToPoint(self, spot, 0.35f, objects, count, dt)) return; + freeHands(self, NULL); + Vec3 hands = Tent_handsAt(tent, self, tent->tent_work * 18.0f); + turnTowards(self, vec3_sub(hands, self->position), dt); + Hands_touch(self, hands); + int got = Tent_work(tent, self, dt); + if (got >= 0 && random01(ai) < 0.6f) { + static const char *const SAY[] = { "", "Poles in - up it goes.", "Now the cover over it.", "Pegs next - who's got a stone?", "There - it stands!" }; + Ai_say(self, SAY[got % 5]); + } +} + +static bool pitchTents(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (ai->unload || self->body.asleep || self->in_tent) return false; + Object *tent = Tent_of(objects, count, self); + /* their own tent, not up yet */ + if (tent && tent->tent_stage < TENT_UP && flatDistance(tent->position, self->position) < TENT_WALK) { + workOnTent(self, tent, objects, count, dt); + return true; + } + if (!tent) { /* still in its bag: spread it out where it lies */ + Object *bag = ownGear(self, objects, count, true); + if (bag && !bag->is_held && flatDistance(bag->position, self->position) < TENT_WALK) { + if (self->sitting) self->sitting = false; + if (!goToPoint(self, bag->position, 0.6f, objects, count, dt)) return true; + freeHands(self, NULL); + Hands_touch(self, bag->position); + if (Tent_unpack(g_world, bag, Car_campDoor()) && random01(ai) < 0.7f) Ai_say(self, "Right - let's get the tent up."); + return true; + } + } + /* somebody pitching one near: a hand */ + Object *job = ai->tent_job; + if (job && (job->tent_stage >= TENT_UP || (ai->tent_time += dt) > HELP_LONG || job->tent_idle > 20.0f)) job = ai->tent_job = NULL; + if (!job && (ai->mode == AI_IDLE || ai->mode == AI_WANDER)) { + for (int i = 0; i < count && !job; i++) { + Object *t = objects[i]; + 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; + } + if (job) { + ai->tent_job = job; + ai->tent_time = 0.0f; + if (random01(ai) < 0.8f) Ai_say(self, "Wait - I'll give you a hand with that."); + } + } + if (job) { workOnTent(self, job, objects, count, dt); return true; } + /* their own up: the sleeping bag and the mat in at the door */ + if (tent && tent->tent_stage >= TENT_COVER) { + Object *thing = bedThing(self, objects, count, tent); + if (!thing) return false; + if (thing->is_held) { + Vec3 door = Tent_door(tent); + if (!goToPoint(self, door, 0.3f, objects, count, dt)) return true; + for (int h = 0; h < 2; h++) if (self->hands.item[h] == thing) Hands_drop(self, h); + thing->position.x = door.x; /* (in at the door) */ + thing->position.z = door.z; + Object_syncColliders(thing); + return true; + } + if (Hands_gapFrom(self->position, self->look.height, thing) < PICKUP_REACH) { + stand(self); + if (!Hands_pickUpObject(self, Hands_freeHand(self) >= 0 ? Hands_freeHand(self) : self->main_hand, thing)) freeHands(self, NULL); + return true; + } + goToPoint(self, thing->position, 0.3f, objects, count, dt); + return true; + } + return false; +} + +/* 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; + Object *tent = Tent_of(objects, count, self); + if (!tent || tent->tent_stage < TENT_UP || flatDistance(tent->position, self->position) > TENT_WALK) return false; + if (self->sitting) self->sitting = false; + if (!goToPoint(self, Tent_door(tent), 0.4f, objects, count, dt)) return true; + freeHands(self, NULL); + if (random01(&self->ai) < 0.5f) Ai_say(self, "Night, everybody."); + Tent_crawlIn(tent, self); + rest(&self->ai, 2.0f, 3.0f); + 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 @@ -1890,6 +2014,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; + /* 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 */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && pitchTents(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 2d68ede..c98879e 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -177,6 +177,9 @@ 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) */ + 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) ... */ struct Object *unload_car; float unload_time; /* ... on this trip this long */ diff --git a/entropie/src/people/condition.c b/entropie/src/people/condition.c index d7d67be..22ef003 100644 --- a/entropie/src/people/condition.c +++ b/entropie/src/people/condition.c @@ -1,3 +1,4 @@ +#include "world/tent.h" #include "people/condition.h" #include "world/object.h" #include "world/grid.h" @@ -21,6 +22,8 @@ #define BLADDER_PER_HOUR 0.04f #define AWAKE_PER_HOUR 0.065f /* sleepiness while awake: about 16 hours a day */ #define SLEEP_PER_HOUR 0.16f /* ... taken off while asleep: a good night's sleep */ +#define TENT_SLEEP 1.15f /* ... in a tent, quicker */ +#define TENT_BED_SLEEP 1.4f /* ... and in a sleeping bag in it, quicker still */ #define WORK_THIRST 2.0f /* hard work makes you this much thirstier / hungrier */ /* Stamina, per real second */ #define TIRE_RUNNING 0.018f @@ -119,7 +122,8 @@ static void needs(Object *self, float dt, float game_dt) { n->hunger = fminf(1.0f, n->hunger + HUNGER_PER_HOUR * hours * hard * slow); n->thirst = fminf(1.0f, n->thirst + THIRST_PER_HOUR * hours * hard * slow); n->bladder = fminf(1.0f, n->bladder + BLADDER_PER_HOUR * hours / Object_bladder(self)); - n->sleepiness = clampf(n->sleepiness + (asleep ? -SLEEP_PER_HOUR : AWAKE_PER_HOUR) * hours, 0.0f, 1.0f); + float bed = asleep && self->in_tent ? (Tent_hasBed(self->in_tent, self) ? TENT_BED_SLEEP : TENT_SLEEP) : 1.0f; /* (a tent, a sleeping bag: better sleep) */ + n->sleepiness = clampf(n->sleepiness + (asleep ? -SLEEP_PER_HOUR * bed : AWAKE_PER_HOUR) * hours, 0.0f, 1.0f); /* Can't keep the eyes open any more: sleep right here */ bool settled = b->pose == POSE_UP || b->pose == POSE_DOWN; diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index e68c8ed..dadc590 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -141,6 +141,18 @@ 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 dome tent standing on y = 0, the door to +Z: big 0 small (two people + squeezed), 1 the big one. How far it's up: stage 0 laid out flat, 1 the + poles going in and up (progress 0..1), 2 the cover going over it (from the + back), 3 being pegged (pegs in), 4 standing. */ +#define TENT_STAGES 5 +#define TENT_W(big) ((big) ? 2.2f : 1.4f) +#define TENT_L(big) ((big) ? 2.4f : 2.1f) +#define TENT_H(big) ((big) ? 1.3f : 1.0f) +#define TENT_PEGS(big) ((big) ? 10 : 6) +Mesh Shapes_tent(int big, int stage, float progress, int pegs, Vec3 inner, Vec3 fly); +/* Where peg k goes (local, the tent's middle at 0, 0). */ +void Shapes_tentPeg(int big, int k, float *x, float *z); /* 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 e72d0e5..0866087 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -777,6 +777,98 @@ 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))); } +/* A thin stick from a to c (a pole, a guy line, a peg). */ +static void addStick(MeshBuilder *b, Vec3 a, Vec3 c, float thick, Vec3 color) { + Vec3 d = vec3_sub(c, a); + float len = vec3_length(d); + if (len < 1e-4f) return; + Vec3 y = vec3_scale(d, 1.0f / len); + Vec3 x = vec3_normalize(fabsf(y.y) < 0.9f ? vec3_cross(y, vec3(0.0f, 1.0f, 0.0f)) : vec3_cross(y, vec3(1.0f, 0.0f, 0.0f))); + Vec3 z = vec3_cross(x, y); + Mat4 m = mat4_identity(); + m.m[0] = x.x; m.m[1] = x.y; m.m[2] = x.z; + m.m[4] = y.x; m.m[5] = y.y; m.m[6] = y.z; + m.m[8] = z.x; m.m[9] = z.y; m.m[10] = z.z; + Vec3 mid = vec3_scale(vec3_add(a, c), 0.5f); + m.m[12] = mid.x; m.m[13] = mid.y; m.m[14] = mid.z; + size_t first = b->vertex_count; + addBlock(b, vec3_zero(), vec3(thick, len, thick), color); + transformSince(b, first, m); +} + +/* The dome over a hw x hl footprint, h high (rows along z from the back; only + the rows up to `upto` - the cover going over), a little wrinkled when low. */ +#define DOME_N 10 +static void addDome(MeshBuilder *b, float hw, float hl, float h, Vec3 color, float upto) { + GLuint idx[DOME_N + 1][DOME_N + 1]; + for (int j = 0; j <= DOME_N; j++) + for (int i = 0; i <= DOME_N; i++) { + float u = -1.0f + 2.0f * (float)i / DOME_N, v = -1.0f + 2.0f * (float)j / DOME_N; + float fu = powf(fmaxf(0.0f, 1.0f - u * u), 0.55f), fv = powf(fmaxf(0.0f, 1.0f - v * v), 0.55f); + float y = h * fu * fv + (h < 0.15f ? 0.02f * sinf(7.0f * u + 3.0f * v) * fu * fv : 0.0f) + 0.012f; + Vec3 n = vec3_normalize(vec3(u * h / hw, 1.0f, v * h / hl)); + Vec3 shade = vec3_scale(color, 0.9f + 0.1f * fu * fv); + idx[j][i] = MeshBuilder_vertex(b, vec3(u * hw, y, v * hl), n, shade); + } + for (int j = 0; j < DOME_N; j++) { + if ((float)(j + 1) / DOME_N > upto + 1e-3f) break; + for (int i = 0; i < DOME_N; i++) MeshBuilder_quad(b, idx[j][i], idx[j][i + 1], idx[j + 1][i + 1], idx[j + 1][i]); + } +} + +void Shapes_tentPeg(int big, int k, float *x, float *z) { + const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f; + static const float AT[10][2] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { 1.0f, 1.0f }, { -1.0f, 1.0f }, /* the corners */ + { 0.0f, -1.45f }, { 0.0f, 1.45f }, /* back and front (the door) */ + { -1.35f, 0.0f }, { 1.35f, 0.0f }, { -0.55f, 1.4f }, { 0.55f, 1.4f } }; + k = k < 0 ? 0 : k > 9 ? 9 : k; + *x = AT[k][0] * (hw + (k < 4 ? 0.12f : 0.0f)); + *z = AT[k][1] * (hl + (k < 4 ? 0.12f : 0.0f)); +} + +Mesh Shapes_tent(int big, int stage, float progress, int pegs, Vec3 inner, Vec3 fly) { + MeshBuilder b; + MeshBuilder_init(&b); + const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f, h = TENT_H(big); + const Vec3 sheet = { 0.22f, 0.24f, 0.25f }, pole = { 0.62f, 0.64f, 0.66f }, peg = { 0.7f, 0.7f, 0.72f }, door = vec3_scale(fly, 0.45f); + addBlock(&b, vec3(0.0f, 0.005f, 0.0f), vec3(hw * 2.0f + 0.06f, 0.01f, hl * 2.0f + 0.06f), sheet); /* the ground sheet */ + float up = stage == 0 ? 0.04f : stage == 1 ? fmaxf(0.04f, h * 0.95f * progress) : h * 0.95f; + addDome(&b, hw, hl, up, inner, 1.0f); + if (stage >= 1) { /* the poles, corner to corner, crossing at the top */ + float ph = stage == 1 ? fmaxf(0.06f, h * progress) : h; + for (int s = -1; s <= 1; s += 2) { + Vec3 last = vec3((float)s * -hw, 0.0f, -hl); + for (int k = 1; k <= 10; k++) { + float t = (float)k / 10.0f; + Vec3 p = vec3((float)s * (-hw + 2.0f * hw * t), ph * sinf(PI_F * t), -hl + 2.0f * hl * t); + addStick(&b, last, p, 0.022f, pole); + last = p; + } + } + } + if (stage >= 2) /* the cover, from the back over the top to the door */ + addDome(&b, hw * 1.08f, hl * 1.14f, h * 1.05f, fly, stage == 2 ? progress : 1.0f); + if (stage >= 3) { /* the pegs in so far, with their lines up to the cover */ + int n = stage == 3 ? pegs : TENT_PEGS(big); + for (int k = 0; k < n && k < TENT_PEGS(big); k++) { + float x, z; + Shapes_tentPeg(big, k, &x, &z); + addStick(&b, vec3(x, -0.08f, z), vec3(x, 0.05f, z), 0.02f, peg); + if (k >= 4) { /* a guy line from the cover's edge up there */ + float ex = x * 0.55f, ez = z * 0.62f, u = ex / (hw * 1.08f), v = ez / (hl * 1.14f); + float ey = h * 1.05f * powf(fmaxf(0.0f, 1.0f - u * u), 0.55f) * powf(fmaxf(0.0f, 1.0f - v * v), 0.55f); + addStick(&b, vec3(x, 0.05f, z), vec3(ex, ey, ez), 0.008f, (Vec3){ 0.85f, 0.4f, 0.15f }); + } + } + } + if (stage >= 4) { /* the door: zipped shut, a darker arch in the front */ + size_t first = b.vertex_count; + addBlock(&b, vec3(0.0f, h * 0.32f, 0.0f), vec3(hw * 0.75f, h * 0.62f, 0.01f), door); + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, hl * 1.02f)), mat4_rotate_x(-0.55f))); + } + return Mesh_fromBuilder(&b); +} + /* Your estate car (see shapes.h). */ Mesh Shapes_car(Vec3 color, bool boot_open) { MeshBuilder b; diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c index 9c7ce3f..df0b1ec 100644 --- a/entropie/src/world/car.c +++ b/entropie/src/world/car.c @@ -183,11 +183,16 @@ Vec3 Car_dropSpot(const World *world, const Object *item) { } return vec3_add(base, vec3_scale(along, 0.5f * (float)n)); } - Vec3 along = Site_campAlong(), across = vec3(-along.z, 0.0f, along.x); + Vec3 along = Site_campAlong(), across = Car_campDoor(); 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))); + if (item->variant >= GEAR_TENT_SMALL) return at; /* (the tent where it'll stand, the rest in front of its door) */ + float a = (item->variant == GEAR_MAT ? 0.55f : 0.0f) + (whose(item->owner) == 2 ? 1.2f : 0.0f) - 0.6f; /* (Jana's beside Volkram's) */ + 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); } Object *Crate_takeBottle(World *world, Object *crate) { diff --git a/entropie/src/world/car.h b/entropie/src/world/car.h index a98bfb6..83a6ac6 100644 --- a/entropie/src/world/car.h +++ b/entropie/src/world/car.h @@ -45,7 +45,10 @@ 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. */ +/* Where an unloaded thing goes: crates by the fire, the camping things where its owner's tent will be + (the tent bag on the spot, sleeping bags and mats CAMP_BEDS_OUT in front of its door). */ +#define CAMP_BEDS_OUT 2.8f +Vec3 Car_campDoor(void); 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. */ diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 74f3036..77b11c8 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -385,6 +385,17 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size break; } + case OBJ_TENT: { /* (see tent.c: its mesh by how far it's up) */ + obj->variant = weight > 0.5f ? 1 : 0; + obj->weight = 50.0f; + obj->is_static = true; + obj->shown = -1; + obj->size = vec3(TENT_W(obj->variant), TENT_H(obj->variant), TENT_L(obj->variant)); + obj->mesh = Shapes_tent(obj->variant, 0, 0.0f, 0, vec3(0.9f, 0.86f, 0.62f), vec3(0.3f, 0.5f, 0.3f)); + addCollider(obj, vec3(0.0f, 0.02f, 0.0f), vec3(obj->size.x, 0.04f, obj->size.z), vec3_zero()); + break; + } + case OBJ_CLOTHES: /* (made in clothes.c, its mesh given) */ obj->is_static = true; break; @@ -1268,6 +1279,14 @@ void Object_describe(const Object *obj, char *out, size_t size) { 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_TENT: { + static const char *const STAGE[] = { "spread out - left click: put the poles in", "the poles going in - keep clicking", + "the cover going over it - keep clicking", "pegging it down - keep clicking", "up" }; + const char *whose = obj->owner && obj->owner->type == OBJ_PLAYER ? "Your" : NULL; + if (whose) snprintf(out, size, "Your tent - %s", STAGE[obj->tent_stage % 5]); + else snprintf(out, size, "%s's tent - %s", obj->owner ? obj->owner->name : "Somebody", STAGE[obj->tent_stage % 5]); + 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]); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 9f05da7..ae2a6c4 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -86,6 +86,7 @@ typedef enum { 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 */ + OBJ_TENT, /* static: a dome tent, weight 0 small / 1 big (see tent.h); stored = pegs in */ } ObjectType; /* What a body needs over the day. All 0..1; 0 is fine (stamina: 1 is fine). */ @@ -160,7 +161,13 @@ 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 */ - struct Object *owner; /* camping gear: whose */ + struct Object *owner; /* camping gear, tents: whose ... */ + struct Object *owner2; /* ... tents: and who shares it */ + int tent_stage; /* tents: TentStage ... */ + float tent_work; /* ... how far the stage is (0..1) ... */ + float tent_idle; /* ... seconds since anybody worked on it */ + int tent_beds; /* ... sleeping bags (bit 0 owner, 2 owner2) and mats (1, 3) put in */ + struct Object *in_tent; /* characters: sleeping in this tent (no collisions with it) */ 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 */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index ad9f3cf..29bac22 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -99,7 +99,7 @@ static float halfAcross(const Collider *c) { static bool neverOver(const Object *o) { switch (o->type) { case OBJ_HOUSE: case OBJ_BARN: case OBJ_BUSH: case OBJ_FIREPLACE: case OBJ_DOG: - case OBJ_HUMAN: case OBJ_PLAYER: case OBJ_DECAL: case OBJ_PUDDLE: case OBJ_STINK: case OBJ_WALL: case OBJ_STRUCTURE: + case OBJ_HUMAN: case OBJ_PLAYER: case OBJ_DECAL: case OBJ_PUDDLE: case OBJ_STINK: case OBJ_WALL: case OBJ_STRUCTURE: case OBJ_TENT: return true; case OBJ_TREE: return !(o->toppled && o->topple >= 1.0f); /* (one lying on the ground: over it, or onto it) */ @@ -501,6 +501,7 @@ static void resolveObjects(Object *obj, Object *const *objects, int count) { if (obj->hands.grabbed == other || other->hands.grabbed == obj) continue; /* Sitting on it */ if (obj->seat == other || other->seat == obj) continue; /* (sitting, or just getting up off it) */ + if (obj->in_tent == other || other->in_tent == obj) continue; /* (lying in it) */ /* Stepping over it */ if (Physics_stepsOver(obj, other) || Physics_stepsOver(other, obj)) continue; diff --git a/entropie/src/world/tent.c b/entropie/src/world/tent.c new file mode 100644 index 0000000..8664d47 --- /dev/null +++ b/entropie/src/world/tent.c @@ -0,0 +1,205 @@ +#include "world/tent.h" +#include "world/car.h" +#include "people/hands.h" +#include "render/shapes.h" + +#include <stdio.h> +#include <string.h> + +#define LAY_TIME 4.0f /* s (one person, average): spreading it out, the poles through their sleeves */ +#define POLES_SMALL 16.0f /* ... the poles in and up */ +#define POLES_BIG 24.0f +#define COVER_SMALL 10.0f /* ... the cover over it */ +#define COVER_BIG 14.0f +#define PEG_TIME 2.5f /* ... each peg (with a stone to hit it: twice as quick) */ +#define DOOR_OUT 0.45f /* in front of the door: this far out from the cover */ +#define BED_REACH 0.8f /* a sleeping bag or mat put down this near the door goes in */ + +static Vec3 local(const Object *tent, Vec3 p) { + return vec3_add(tent->position, mat4_mul_dir(mat4_rotate_y(tent->rotation.y), p)); +} + +static Vec3 toLocal(const Object *tent, Vec3 w) { + return mat4_mul_dir_transposed(mat4_rotate_y(tent->rotation.y), vec3_sub(w, tent->position)); +} + +/* Everybody's own cover colour (by name, like their other things). */ +static Vec3 flyColor(const Object *tent) { + const Object *o = tent->owner; + if (tent->variant) return vec3(0.40f, 0.44f, 0.24f); /* the big one: olive */ + if (!o || o->type == OBJ_PLAYER) return vec3(0.22f, 0.36f, 0.56f); /* yours: blue */ + if (!strcmp(o->name, "Toni")) return vec3(0.78f, 0.42f, 0.16f); /* Toni's: orange */ + return vec3(0.30f, 0.50f, 0.30f); +} + +/* Its look and its box for how far it's up (built again only when that shows). */ +static void reshape(Object *tent) { + int big = tent->variant, steps = tent->tent_stage == TENT_POLES || tent->tent_stage == TENT_COVER ? (int)(tent->tent_work * 12.0f) : 0; + int shown = tent->tent_stage * 1000 + steps * 10 + tent->stored; + if (shown == tent->shown) return; + tent->shown = shown; + Mesh_destroy(&tent->mesh); + tent->mesh = Shapes_tent(big, tent->tent_stage, (float)steps / 12.0f, tent->stored, vec3(0.90f, 0.86f, 0.62f), flyColor(tent)); + float h = tent->tent_stage == TENT_LAID ? 0.04f : tent->tent_stage == TENT_POLES ? fmaxf(0.04f, TENT_H(big) * tent->tent_work) : TENT_H(big); + tent->colliders[0].center = vec3(0.0f, h * 0.5f, 0.0f); + tent->colliders[0].half_extents = vec3(TENT_W(big) * 0.5f, h * 0.5f, TENT_L(big) * 0.5f); + Object_syncColliders(tent); +} + +Object *Tent_unpack(World *world, Object *bag, Vec3 door_dir) { + if (!bag || bag->type != OBJ_GEAR || bag->variant < GEAR_TENT_SMALL || bag->is_held || bag->broken) return NULL; + int big = bag->variant == GEAR_TENT_BIG; + Vec3 at = bag->position; + at.y = World_groundHeight(world, at.x, at.z, at.y + 0.3f); + Object *tent = Object_create(OBJ_TENT, PALETTE_STANDARD, at, vec3_zero(), big ? 1.0f : 0.0f); + if (!tent) return NULL; + tent->owner = bag->owner; + if (big && bag->owner && !strcmp(bag->owner->name, "Volkram")) /* (the big one is Volkram's and Jana's) */ + for (int i = 0; i < world->count; i++) + if (world->objects[i]->type == OBJ_HUMAN && !strcmp(world->objects[i]->name, "Jana")) tent->owner2 = world->objects[i]; + snprintf(tent->name, sizeof(tent->name), "Tent"); + door_dir = vec3_normalize(vec3(door_dir.x, 0.0f, door_dir.z)); + Object_setRotation(tent, vec3(0.0f, yaw_facing(door_dir), 0.0f)); + reshape(tent); + if (!World_add(world, tent)) return NULL; + Object_useUp(bag); + return tent; +} + +static bool hasStone(const Object *who) { + for (int h = 0; h < 2; h++) + if (who->hands.item[h] && who->hands.state[h] == HAND_HOLDING && who->hands.item[h]->type == OBJ_STONE) return true; + return false; +} + +int Tent_work(Object *tent, Object *who, float dt) { + if (tent->type != OBJ_TENT || tent->tent_stage >= TENT_UP) return -1; + int big = tent->variant; + float rate = (0.75f + 0.5f * who->wits) * (1.0f - 0.5f * fminf(who->drunk, 1.0f)); + float time = tent->tent_stage == TENT_LAID ? LAY_TIME + : tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL) + : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME / (hasStone(who) ? 2.0f : 1.0f); + tent->tent_work += dt * rate / time; + tent->tent_idle = 0.0f; + int done = -1; + if (tent->tent_work >= 1.0f) { + tent->tent_work = 0.0f; + if (tent->tent_stage == TENT_PEGS) { + if (++tent->stored >= TENT_PEGS(big)) { tent->tent_stage = TENT_UP; done = TENT_UP; } + } else { + tent->tent_stage++; + done = tent->tent_stage; + } + } + reshape(tent); + return done; +} + +const char *Tent_job(const Object *tent) { + switch (tent->tent_stage) { + case TENT_LAID: return "spreading it out"; + case TENT_POLES: return "putting the poles in"; + case TENT_COVER: return "pulling the cover over"; + case TENT_PEGS: return "hammering the pegs in"; + default: return "done"; + } +} + +static Vec3 nextPeg(const Object *tent) { + float x, z; + Shapes_tentPeg(tent->variant, tent->stored, &x, &z); + return vec3(x, 0.0f, z); +} + +Vec3 Tent_workSpot(const Object *tent, float side) { + const float hw = TENT_W(tent->variant) * 0.5f, hl = TENT_L(tent->variant) * 0.5f; + float s = side < 0.0f ? -1.0f : 1.0f; + switch (tent->tent_stage) { + case TENT_COVER: return local(tent, vec3(s * 0.5f * fabsf(side), 0.0f, -hl * 1.14f - 0.55f)); /* at the back, pulling it over */ + case TENT_PEGS: { + Vec3 p = nextPeg(tent), out = vec3_normalize(vec3(p.x, 0.0f, p.z)); + return local(tent, vec3_add(p, vec3_scale(out, 0.45f))); + } + default: return local(tent, vec3(s * (hw + 0.55f), 0.0f, 0.0f)); /* beside it */ + } +} + +Vec3 Tent_handsAt(const Object *tent, const Object *who, float clock) { + const int big = tent->variant; + const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f, h = TENT_H(big); + Vec3 me = toLocal(tent, who->position); + float s = me.x < 0.0f ? -1.0f : 1.0f; + switch (tent->tent_stage) { + case TENT_LAID: return local(tent, vec3(s * hw, 0.08f, clampf(me.z, -hl, hl))); + case TENT_POLES: return local(tent, vec3(s * hw * 0.7f, fmaxf(0.1f, 0.55f * h * tent->tent_work), clampf(me.z, -hl * 0.5f, hl * 0.5f))); + case TENT_COVER: return local(tent, vec3(s * 0.3f, h * 0.8f, -hl + 2.0f * hl * tent->tent_work)); + case TENT_PEGS: { + Vec3 p = nextPeg(tent); + p.y = 0.06f + 0.14f * fabsf(sinf(clock * 7.0f)); /* (hitting it in) */ + return local(tent, p); + } + default: return Tent_door(tent); + } +} + +Vec3 Tent_door(const Object *tent) { + return local(tent, vec3(0.0f, 0.0f, TENT_L(tent->variant) * 0.5f * 1.14f + DOOR_OUT)); +} + +bool Tent_isTheirs(const Object *tent, const Object *who) { + return tent && tent->type == OBJ_TENT && who && (tent->owner == who || tent->owner2 == who); +} + +Object *Tent_of(Object *const *objects, int count, const Object *who) { + for (int i = 0; i < count; i++) + if (objects[i]->type == OBJ_TENT && Tent_isTheirs(objects[i], who)) return objects[i]; + return NULL; +} + +static int slotOf(const Object *tent, const Object *who) { return tent->owner2 == who ? 1 : 0; } + +bool Tent_hasBed(const Object *tent, const Object *who) { + return tent && (tent->tent_beds & (1 << (2 * slotOf(tent, who)))) != 0; +} + +void Tent_crawlIn(Object *tent, Object *who) { + const int big = tent->variant; + float x = big ? (slotOf(tent, who) ? 0.45f : -0.45f) : 0.0f; + Vec3 at = local(tent, vec3(x, 0.0f, TENT_L(big) * 0.5f - 0.95f)); /* (lying back into it, the feet towards the door) */ + who->in_tent = tent; + who->sitting = false; + who->position = vec3(at.x, tent->position.y, at.z); + who->rotation.y = tent->rotation.y; /* (facing the door) */ + who->velocity = vec3_zero(); + who->movement = vec3_zero(); + Object_syncColliders(who); + who->body.last_facing = Object_facing(who); /* (lies back from where they face now: the head to the back of the tent) */ + Body_lieDown(&who->body); +} + +void Tent_crawlOut(Object *who) { + Object *tent = who->in_tent; + if (!tent) return; + Vec3 door = Tent_door(tent); + who->in_tent = NULL; + who->position = vec3(door.x, tent->position.y, door.z); + who->rotation.y = tent->rotation.y; + who->body.replant = true; + Object_syncColliders(who); +} + +void Tent_update(World *world, float dt) { + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (o->type == OBJ_TENT) { o->tent_idle += dt; continue; } + if (o->is_character && o->in_tent && !o->body.asleep && o->body.pose == POSE_UP) { Tent_crawlOut(o); continue; } + /* a sleeping bag or a mat put down at the door of its owner's tent: in it goes */ + if (o->type != OBJ_GEAR || o->variant > GEAR_MAT || o->is_held || o->broken || !o->owner) continue; + Object *tent = Tent_of(world->objects, world->count, o->owner); + if (!tent || tent->tent_stage < TENT_COVER) continue; + Vec3 door = Tent_door(tent); + if (hypotf(o->position.x - door.x, o->position.z - door.z) > BED_REACH) continue; + tent->tent_beds |= 1 << (2 * slotOf(tent, o->owner) + (o->variant == GEAR_MAT ? 1 : 0)); + Object_useUp(o); + } +} diff --git a/entropie/src/world/tent.h b/entropie/src/world/tent.h new file mode 100644 index 0000000..34cf5b9 --- /dev/null +++ b/entropie/src/world/tent.h @@ -0,0 +1,39 @@ +#ifndef WORLD_TENT_H +#define WORLD_TENT_H + +/* + * Tents (OBJ_TENT, variant 0 small, 1 big): spread out from their bag where + * it lies, then pitched in stages - the poles in and up, the cover over it, + * the pegs hammered in - by whoever works on it (several at once: quicker). + * Sleeping bags and mats put down at the door go in; their owners sleep in + * it (in_tent: lying inside, the tent round them). + */ + +#include "world/world.h" + +typedef enum { TENT_LAID, TENT_POLES, TENT_COVER, TENT_PEGS, TENT_UP } TentStage; + +/* A tent bag (OBJ_GEAR) spread out as a tent where it lies, the door facing door_dir: the tent, or NULL. */ +Object *Tent_unpack(World *world, Object *bag, Vec3 door_dir); +/* Somebody working on it this frame (both hands free, at it): the next bit + done. Returns the stage it got to when one was finished just now, else -1. */ +int Tent_work(Object *tent, Object *who, float dt); +/* What working on it means now ("putting the poles in") and where the hands go. */ +const char *Tent_job(const Object *tent); +Vec3 Tent_workSpot(const Object *tent, float side); /* where to stand (side -1 / 0 / +1) */ +Vec3 Tent_handsAt(const Object *tent, const Object *who, float clock); +/* In front of the door, and its tent's sleepers' places inside. */ +Vec3 Tent_door(const Object *tent); +/* Whose: the owner, or (the big one) who shares it. */ +bool Tent_isTheirs(const Object *tent, const Object *who); +/* Somebody's tent (spread out or pitched), or NULL. */ +Object *Tent_of(Object *const *objects, int count, const Object *who); +/* Their sleeping bag in it. */ +bool Tent_hasBed(const Object *tent, const Object *who); +/* Into it to sleep (lying in their place) / out again in front of the door. */ +void Tent_crawlIn(Object *tent, Object *who); +void Tent_crawlOut(Object *who); +/* Every frame: sleeping bags and mats put down at the door go in; whoever woke up in a tent comes out. */ +void Tent_update(World *world, float dt); + +#endif /* WORLD_TENT_H */ diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index d9e7133..363cf5b 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -2,6 +2,7 @@ #include "world/camp.h" #include "world/clothes.h" #include "world/car.h" +#include "world/tent.h" #include "people/pockets.h" #include "people/condition.h" #include "people/dog.h" @@ -101,7 +102,7 @@ float World_groundHeight(const World *world, float x, float z, float from_y) { int near[NEAR_MAX], nn = nearby(world, x, z, 0.0f, near); for (int q = 0; q < (nn < 0 ? world->count : nn); q++) { const Object *o = world->objects[nn < 0 ? q : near[q]]; - if (o->is_character || o->is_held) continue; + if (o->is_character || o->is_held || o->type == OBJ_TENT) continue; /* (nobody stands on a tent: it'd give) */ for (int c = 0; c < o->collider_count; c++) { const Collider *col = &o->colliders[c]; Vec3 center = Object_colliderCenter(o, c); @@ -732,7 +733,7 @@ static void dressForNight(World *world, Object *o, float dt) { if (o->type == OBJ_DOG) return; bool night = world->hour >= 21.0f || world->hour < 7.0f; bool by_fire = Camp_fireNear(world->objects, world->count, o->position, 4.0f, true) != NULL; - float cold = !night ? -2.0f : by_fire ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f); + float cold = !night ? -2.0f : by_fire || (o->in_tent && Tent_hasBed(o->in_tent, o)) ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f); o->chill = clampf(o->chill + cold * dt / 60.0f, 0.0f, 1.0f); /* (about a minute to feel it) */ bool player = o->type == OBJ_PLAYER; if (o->look.top != TOP_JACKET && o->chill > 0.6f && !(player && o->jacket_told)) { @@ -1232,6 +1233,7 @@ void World_update(World *world, float dt, float game_dt) { world->dt_props = dt; Clothes_update(world); Car_serve(world); + Tent_update(world, dt); 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];