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.36.0: rain together, steady view on slopes
- No more shaking view walking down steep slopes (the bank by your garages) - The pullover tied round the waist no longer goes through the legs - Umbrellas held properly: open, up in front of the shoulder with the shaft leaning over the head; folded, carried by the middle, tip down - People without an umbrella ask to come under someone's (yours too) and walk along side by side; the umbrella leans over both - In hard rain the guests sit it out in your garages' kitchen (chairs, a new log bench, or the floor) and get up when it stops
commit_message.txt | 14 ++-- entropie/Makefile | 2 +- entropie/README.md | 2 +- entropie/STATE.md | 22 ++++-- entropie/assets/shaders/figure.frag | 24 +++++- entropie/commit_message.txt | 14 ++-- entropie/src/people/ai.c | 147 ++++++++++++++++++++++++++++++++++-- entropie/src/people/ai.h | 3 + entropie/src/people/hands.c | 30 ++++++-- entropie/src/world/object.h | 2 + entropie/src/world/physics.c | 27 ++++--- entropie/src/world/site.c | 6 ++ entropie/src/world/site.h | 2 + entropie/src/world/world.c | 20 ++++- 14 files changed, 269 insertions(+), 46 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index 6a95ab0..cbec05c 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,6 +1,10 @@ -entropie 2.35.0: Alice pants +entropie 2.36.0: rain together, steady view on slopes -- Alice pants after a run (and in the midday sun): quick breaths you hear - close by, her chest heaving; it eases off over half a minute -- She no longer runs on the spot against the car when called: held up by - something, she goes round it (legs move by how fast she really goes) +- No more shaking view walking down steep slopes (the bank by your garages) +- The pullover tied round the waist no longer goes through the legs +- Umbrellas held properly: open, up in front of the shoulder with the shaft + leaning over the head; folded, carried by the middle, tip down +- People without an umbrella ask to come under someone's (yours too) and + walk along side by side; the umbrella leans over both +- In hard rain the guests sit it out in your garages' kitchen (chairs, a + new log bench, or the floor) and get up when it stops diff --git a/entropie/Makefile b/entropie/Makefile index b94de0f..537b19e 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.35.0 +VERSION := 2.36.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 4e59a3d..5baee17 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -124,5 +124,5 @@ Dependencies point downward: `main → game → world/people → render → core ## Version -`make version` prints e.g. `entropie 2.35.0 (build 9450b3c)`. The build id is +`make version` prints e.g. `entropie 2.36.0 (build 25ed4f6)`. The build id is an md5 of all sources and assets. diff --git a/entropie/STATE.md b/entropie/STATE.md index 07f3204..125b23a 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -284,10 +284,15 @@ Painted in the rotoscope style of *Waking Life* (2001): - Two umbrellas (by the main building's door, in your garages' kitchen): carried, one opens in the rain and keeps you dry. The others fetch one when it starts to rain (if they're not busy working) and stay out under it; the - careful put it back after + careful put it back after. Open, it's held up in front of the shoulder, the + shaft leaning in over the head; folded, carried by the middle, tip down +- No umbrella of their own: somebody near under one gets asked ("Room under + there for me?") and they walk along side by side under it (yours too); it + leans over the two of them - People get out of it: Kurti and his sons into the house, the others into - their tent (at night, or when it's nearer) or your garages' kitchen; out - again when it stops + their tent (at night, or when it's nearer) or your garages' kitchen. Raining + hard, they sit it out in there (the two chairs, a log bench by the back + wall, else the floor); out again when it stops - Out in the rain your clothes get wet (darker; Tab: Wet) and you get cold. They dry by a fire quickest, in the day slower, at night slowest. Wet through, the others go and sit by a burning fire @@ -1206,6 +1211,13 @@ Round 2.35 (Alice pants, goes round things): - Panting (`dog.c` pant): `dog.pant` 0..1 rises running (PANT_RISE 0.1/s flat out, from 1.8 m/s), eases resting (PANT_EASE 0.035/s, slower in the sun); the midday sun alone (10:30-19, no rain) keeps it at PANT_SUN 0.35. Over 0.15 she breathes 2-4.5 times a second (`pant_t`): NOISE_PANT each breath (a breath out, a quicker one in, `sound.c`), the chest heaving with it (Dog_draw); not with something in her mouth. - Her real speed (`dog.was` / `speed`): legs and panting go by how fast she really moves (it was the speed she wanted: running into the car she trotted on the spot for ever, never arriving when called). Held up for DETOUR_AFTER 1 s, she goes off to one side for DETOUR_TIME 1.2 s (`detour`), then on. Checked: called from 25 m behind the car - round it, arrived, panting 1.0 easing to 0.4 in 30 s, about 4 breaths a second. +Round 2.36 (rain together, view on the slope): +- The view shook walking down a steep slope (the bank behind your garages): people stood on the highest ground under their footprint, but walking down they were kept on the ground under their middle - one frame up onto the uphill corner, the next down again (8-25 cm). Both now the same ground (`physics.c` footprintGround). Checked: tents to the main building's door walking (1 jump of 3 cm left, was 50+ of up to 30 cm), running and from behind. +- The jacket tied round the waist (`figure.frag`): the back hangs in two halves, each pushed out behind by its thigh going back; the sleeve ends in front pushed out by a thigh coming up (MAX_PARTS 42). No leg through it any more (side and back views, walking and running). +- Umbrellas (`hands.c`): open, the hand in front of its shoulder (UMBRELLA_HAND_OUT 0.16, UP 1.16, AHEAD 0.17), the shaft leaning in to UMBRELLA_TOP 2.05 m over the head's middle (it went straight up in front of the face); folded, by the middle, tip ahead and down (it was held sideways like a bat). +- Sharing one (`ai.c` umbrellaToShare / walkUnder): out in the rain without one, somebody within SHARE_SEE 12 m holding one up with nobody under it: asked, then along at their side (SHARE_GAP 0.53 m, the umbrella side), keeping pace (close: stepping straight onto the spot, no circling), for up to SHARE_MAX 4 min; the holder's `umbrella_guest` leans the canopy over both; `world.c` underUmbrella counts a head within 0.9 x its radius under anybody's open umbrella as dry. Checked: under yours, standing and walking: 0.6 m beside you, dry. +- Sitting out heavy rain (`ai.c` rainRoom, `Site_rainRoom`): the guests go to the middle of your garages' kitchen; from RAIN_SIT 0.45 they sit down on a free chair or the new log bench by its back wall (else the floor) and stay till the rain is nearly over (half of SHELTER_RAIN). Checked: two on the chairs at the table for 100 s of hard rain, up again when it stopped. + ## Open tasks Most important first. @@ -1228,7 +1240,7 @@ Most important first. - "No shirt no service" policy (decision open) - Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers) - Tents, still to do: NPCs taking theirs down on their own (leaving) -- Weather, still to do: hats (nobody wears one yet) blown off; umbrellas while working (now only idle people fetch one); an umbrella blown inside out +- Weather, still to do: hats (nobody wears one yet) blown off; umbrellas while working (now only idle people fetch one); an umbrella blown inside out; more roofed places to sit out the rain together (outdoors: a shelter, a pavilion - `Site_rainRoom` is the one place now) - Sound, still to do: the house's doors (they have no doors that open yet) **Saves, still to do** @@ -1241,4 +1253,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.35.0 (build 9450b3c)` +State of `entropie 2.36.0 (build 25ed4f6)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index 7859d55..3debd18 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -73,7 +73,7 @@ const float SELF_SHADOW_SKIP = 0.10; // meters // ---------- Body parts ---------- -const int MAX_PARTS = 41; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream) +const int MAX_PARTS = 42; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream) vec3 partA[MAX_PARTS], partB[MAX_PARTS]; float partR1[MAX_PARTS], partR2[MAX_PARTS]; float partBlend[MAX_PARTS]; // smooth-union radius with the rest of the body @@ -158,10 +158,26 @@ void buildBody() { vec3 knot = belt + chestForward * (garment >= 2 ? 0.168 : 0.125); // (over a skirt's top) addPart(knot, knot, 0.026, 0.026, 0.01, 1.0, M_JACKET); vec3 across = normalize(joints[J_HIP_R] - joints[J_HIP_L]); - addPart(knot, knot - spineDir * 0.17 + chestForward * 0.02 - across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET); // (the sleeves' ends) - addPart(knot, knot - spineDir * 0.14 + chestForward * 0.02 + across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET); + for (int s = 0; s < 2; s++) { // (the sleeves' ends, pushed out in front by a thigh coming up) + float len = s == 0 ? 0.17 : 0.14; + vec3 end = knot - spineDir * len + chestForward * 0.02 + across * (s == 0 ? -0.03 : 0.03); + vec3 hip = joints[J_HIP_L + s * 4], knee = joints[J_KNEE_L + s * 4], leg = knee - hip; + float t = clamp(dot(end - hip, leg) / max(dot(leg, leg), 1e-4), 0.0, 1.0); + float ahead = dot(mix(hip, knee, t) - end, chestForward) + mix(0.085, 0.058, t) + 0.02; + if (ahead > 0.0) end = knot + normalize(end + chestForward * ahead - knot) * len; + addPart(knot, end, 0.022, 0.018, 0.01, 1.0, M_JACKET); + } + // the back hangs in two halves, each pushed out behind by its thigh going back (no leg through it) vec3 back = belt - chestForward * 0.085; - addPart(back, back - spineDir * 0.27 - chestForward * 0.02, 0.125, 0.135, 0.015, 0.25, M_JACKET); + for (int s = 0; s < 2; s++) { + vec3 top = back + across * (s == 0 ? -0.055 : 0.055); + vec3 hang = top - spineDir * 0.27 - chestForward * 0.02; + vec3 hip = joints[J_HIP_L + s * 4], knee = joints[J_KNEE_L + s * 4], leg = knee - hip; + float t = clamp(dot(hang - hip, leg) / max(dot(leg, leg), 1e-4), 0.0, 1.0); + float behind = dot(mix(hip, knee, t) - hang, -chestForward) + mix(0.085, 0.058, t) + 0.03; + if (behind > 0.0) hang = top + normalize(hang - chestForward * behind - top) * 0.27; + addPart(top, hang, 0.072, 0.078, 0.015, 0.25, M_JACKET); + } } // What's on the legs: trousers, shorts, a skirt or a dress (squatting to pee: diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 6a95ab0..cbec05c 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,6 +1,10 @@ -entropie 2.35.0: Alice pants +entropie 2.36.0: rain together, steady view on slopes -- Alice pants after a run (and in the midday sun): quick breaths you hear - close by, her chest heaving; it eases off over half a minute -- She no longer runs on the spot against the car when called: held up by - something, she goes round it (legs move by how fast she really goes) +- No more shaking view walking down steep slopes (the bank by your garages) +- The pullover tied round the waist no longer goes through the legs +- Umbrellas held properly: open, up in front of the shoulder with the shaft + leaning over the head; folded, carried by the middle, tip down +- People without an umbrella ask to come under someone's (yours too) and + walk along side by side; the umbrella leans over both +- In hard rain the guests sit it out in your garages' kitchen (chairs, a + new log bench, or the floor) and get up when it stops diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 1fa504f..2b0ca3f 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -2066,9 +2066,126 @@ static bool holdsUmbrella(const Object *self) { return false; } +/* The open umbrella this one holds up, if any. */ +static const Object *openUmbrella(const Object *o) { + for (int h = 0; h < 2; h++) { + const Object *u = o->hands.item[h]; + if (u && u->type == OBJ_UMBRELLA && u->umbrella_open && o->hands.state[h] == HAND_HOLDING) return u; + } + return NULL; +} + +#define SHARE_SEE 12.0f /* m: somebody under an umbrella this near, nobody with them: "can I come under?" */ +#define SHARE_MAX 240.0f /* s at most along with them (then off under a roof after all) */ +#define SHARE_GAP 0.53f /* m beside them (shoulder to shoulder: their bodies just clear) */ + +/* Somebody near holding an umbrella up, nobody under it with them yet. */ +static Object *umbrellaToShare(const Object *self, Object *const *objects, int count) { + Object *best = NULL; + float best_d = SHARE_SEE; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o == self || !o->is_character || o->type == OBJ_DOG || !openUmbrella(o) || Body_isDown(&o->body)) continue; + if (o->umbrella_guest && o->umbrella_guest != self && o->umbrella_guest_t > 0.0f) continue; + float d = flatDistance(o->position, self->position); + if (d < best_d) { best_d = d; best = o; } + } + return best; +} + +/* Under somebody's umbrella: at their side (the side they hold it on), keeping up with them. */ +static bool walkUnder(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + Object *h = ai->share; + const Object *u = h ? openUmbrella(h) : NULL; + if (!h || !u || !g_world || g_world->rain < SHELTER_RAIN * 0.5f || (ai->share_time += dt) > SHARE_MAX || self->indoors > 0.0f || + self->in_tent || Body_isDown(&self->body) || Body_isDown(&h->body)) { + if (h && h->umbrella_guest == self) h->umbrella_guest = NULL; + ai->share = NULL; + return false; + } + h->umbrella_guest = self; + h->umbrella_guest_t = 0.5f; + if (self->sitting) stand(self); + Vec3 facing = Object_facing(h), right = vec3_normalize(vec3_cross(facing, UP)); + float side = h->hands.item[1] == u ? 1.0f : -1.0f; /* (the umbrella between them) */ + Vec3 spot = vec3_add(h->position, vec3_add(vec3_scale(right, side * SHARE_GAP * h->look.height / 1.75f), vec3_scale(facing, -0.05f))); + float d = flatDistance(spot, self->position); + float with = vec3_length(vec3(h->body.velocity.x, 0.0f, h->body.velocity.z)); /* (keeping up with them) */ + if (d < (with > 0.3f ? 0.12f : 0.3f)) { stand(self); turnTowards(self, facing, dt); return true; } /* (they stand: so do we - no circling round the spot) */ + float speed = fminf(d > 3.0f ? runSpeed(self) * 0.8f : WALK_SPEED * 1.3f, with + d * 1.5f + 0.1f); + if (d < 1.5f) { /* close: straight onto the spot, a step aside or along, facing the way they go (no circling round it) */ + Vec3 to = vec3_scale(flat(vec3_sub(spot, self->position)), 1.0f / d); + self->movement = vec3_scale(to, speed); + turnTowards(self, with > 0.3f ? flat(h->body.velocity) : facing, dt); + Vec3 f = Object_facing(self), r = vec3_normalize(vec3_cross(f, UP)); + self->move_local = vec3(vec3_dot(to, r), 0.0f, vec3_dot(to, f)); + ai->walk_dir = vec3_zero(); + return true; + } + Vec3 dir = ai_steer(self, h, objects, count, spot); + if (vec3_length(dir) < 0.5f) { stand(self); return true; } + ai_walk(self, dir, speed, dt); + return true; +} + +#define RAIN_SIT 0.45f /* rain this hard: down on a seat in there to wait it out (lighter: they stand about) */ +#define SEAT_LOOK 6.5f /* m round the middle of the room: the seats in it */ + +/* A chair nobody sits on, or a bench with room on it, in the room: the nearest. */ +static Object *freeSeatIn(const Object *self, Object *const *objects, int count, Vec3 room) { + Object *best = NULL; + float best_d = 1e9f; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o->type != OBJ_FURNITURE || (o->variant != FURN_CHAIR && o->variant != FURN_BENCH) || flatDistance(o->position, room) > SEAT_LOOK) continue; + if (o->variant == FURN_CHAIR) { + bool taken = false; + for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->is_character && objects[j]->seat == o; + if (taken) continue; + } else if (Object_benchRoom(self, o, self->position, false) == ROOM_NONE) continue; + float d = flatDistance(o->position, self->position); + if (d < best_d) { best_d = d; best = o; } + } + return best; +} + +/* In under the roof of your garages' kitchen; raining hard, down on a free seat (or on the floor) till it's over. */ +static bool rainRoom(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + if (self->sitting && ai->rain_seated) { rest(ai, 3.0f, 6.0f); return true; } /* (sitting it out) */ + ai->rain_seated = false; + if (self->sitting) self->sitting = false; + Vec3 room = Site_rainRoom(); + Vec3 spot = vec3_add(room, vec3(0.8f * ai->side, 0.0f, 0.8f * ai->side)); + if (flatDistance(room, self->position) > 3.5f || g_world->rain < RAIN_SIT) { + if (goToPoint(self, spot, 0.7f, objects, count, dt)) rest(ai, 3.0f, 6.0f); + return true; + } + Object *seat = freeSeatIn(self, objects, count, room); + if (seat) { + float near = 0.6f + 0.5f * seat->size.x; + if (flatDistance(seat->position, self->position) > near && !goToPoint(self, seat->position, near, objects, count, dt)) return true; + stand(self); + if (Object_sit(self, seat, room)) { + ai->rain_seated = true; + if (random01(ai) < 0.3f) Ai_say(self, random01(ai) < 0.5f ? "We'll sit this one out." : "Might as well sit down."); + rest(ai, 3.0f, 6.0f); + return true; + } + } + /* no seat free: down on the floor where they stand (near the middle) */ + if (!goToPoint(self, spot, 0.7f, objects, count, dt)) return true; + Object_sit(self, NULL, room); + ai->rain_seated = true; + rest(ai, 3.0f, 6.0f); + return true; +} + static bool shelter(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; - if (holdsUmbrella(self)) { ai->sheltering = false; ai->umbrella = NULL; return false; } /* (out in it under that) */ + if (holdsUmbrella(self)) { ai->sheltering = false; ai->umbrella = NULL; ai->share = NULL; return false; } /* (out in it under that) */ + if (ai->share && walkUnder(self, objects, count, dt)) return true; Object *u = ai->umbrella; if (u && (u->is_held || !g_world || g_world->rain < SHELTER_RAIN * 0.5f || Hands_freeHand(self) < 0)) u = ai->umbrella = NULL; if (u) { /* over to it and up with it */ @@ -2084,12 +2201,30 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) { goToPoint(self, u->position, 0.3f, objects, count, dt); return true; } + if (ai->rain_seated && self->sitting && g_world && g_world->rain >= SHELTER_RAIN * 0.5f) { rest(ai, 3.0f, 6.0f); return true; } /* (sat down: till it's nearly over) */ if (!g_world || g_world->rain < SHELTER_RAIN || self->indoors > 0.0f || self->in_tent || self->body.asleep || self->riding) { - if (!g_world || g_world->rain < SHELTER_RAIN * 0.5f) ai->sheltering = false; + if (!g_world || g_world->rain < SHELTER_RAIN * 0.5f) { + ai->sheltering = false; + if (ai->rain_seated) { /* (over: up again) */ + ai->rain_seated = false; + if (self->sitting && random01(ai) < 0.4f) Ai_say(self, random01(ai) < 0.5f ? "It's stopped raining." : "Right, the rain's over."); + self->sitting = false; + } + } return false; } if (!ai->sheltering) { if (!self->rain_out) return false; + Object *holder = umbrellaToShare(self, objects, count); + if (holder && !self->lives_here && !Body_isDown(&self->body)) { /* "can I come under?" - "sure, come here" */ + ai->share = holder; + ai->share_time = 0.0f; + holder->umbrella_guest = self; + holder->umbrella_guest_t = 2.0f; + Ai_say(self, random01(ai) < 0.5f ? "Room under there for me?" : "Can I come under your umbrella?"); + if (holder->type != OBJ_PLAYER && holder->ai.speech_time <= 0.0f) Ai_say(holder, random01(ai) < 0.5f ? "Sure, come here." : "Come on, squeeze in."); + return walkUnder(self, objects, count, dt); + } if (Hands_freeHand(self) >= 0 && ai->mode != AI_WORK && !self->lives_here) { /* an umbrella near: that instead */ Object *best = NULL; float best_d = UMBRELLA_FETCH; @@ -2110,12 +2245,12 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) { if (it && (it->type == OBJ_CRATE || it->type == OBJ_GEAR)) Hands_drop(self, h); } } - if (self->sitting) self->sitting = false; + if (self->sitting && !ai->rain_seated) self->sitting = false; if (!self->lives_here) { /* (the house is Kurti's: you lot wait it out in your tent, or in your garages' kitchen) */ Object *tent = Tent_of(objects, count, self); bool night = g_world->hour >= 21.0f || g_world->hour < 7.0f; if (tent && tent->tent_stage >= TENT_UP && self->body.pose == POSE_UP - && (night || flatDistance(tent->position, self->position) < flatDistance(Site_drinksSpot(), self->position))) { + && (night || flatDistance(tent->position, self->position) < flatDistance(Site_rainRoom(), self->position))) { if (!goToPoint(self, Tent_door(tent), 0.4f, objects, count, dt)) return true; freeHands(self, NULL); Tent_crawlIn(tent, self); @@ -2123,9 +2258,7 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) { rest(ai, 2.0f, 3.0f); return true; } - Vec3 spot = vec3_add(Site_drinksSpot(), vec3(0.7f * ai->side, 0.0f, 0.7f * ai->side)); - if (goToPoint(self, spot, 0.7f, objects, count, dt)) rest(ai, 3.0f, 6.0f); - return true; + return rainRoom(self, objects, count, dt); } if (!goToPoint(self, Site_houseDoor(), 0.6f, objects, count, dt)) return true; self->indoors = 30.0f; /* (kept while it rains: see world.c) */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index 0d20c8c..553e18f 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -182,6 +182,7 @@ typedef struct { 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 sheltering; /* in out of the rain (ai.c shelter) */ + bool rain_seated; /* ... sat down to wait it out (in the garages' kitchen) */ bool greet; /* just arrived: over to the fire, saying hello, sitting there till the work starts ... */ bool greet_stand; /* ... no room on the benches: standing with the others here instead ... */ Vec3 greet_at; @@ -191,6 +192,8 @@ typedef struct { Vec3 dodge_to; /* warned off (a tree coming down): out to here ... */ float dodge_t; /* ... for this long */ struct Object *umbrella; /* rain: off to get this umbrella instead of going under a roof (ai.c shelter) */ + struct Object *share; /* rain: walking under this one's umbrella, at their side ... */ + float share_time; /* ... this long so far */ struct Object *blown; /* the wind took this out of their hand: after it (ai.c fetchBlown) ... */ float blown_t; /* ... this long already */ struct Object *hang_on; /* warm, the pullover round the waist: over this (a railing, a chair) it goes ... */ diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 55b6170..f24ec7a 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -3,6 +3,11 @@ #include "world/ladder.h" #include "world/physics.h" +#define UMBRELLA_HAND_OUT 0.16f /* an open umbrella: the hand this far out to its side ... */ +#define UMBRELLA_HAND_UP 1.16f /* ... this high ... */ +#define UMBRELLA_HAND_AHEAD 0.17f /* ... and in front (all at 1.75 m tall) */ +#define UMBRELLA_TOP 2.05f /* the shaft points at this high over the head's middle */ + #define ONE_HAND_MAX_WEIGHT 10.0f #define ONE_HAND_MAX_SIZE 0.6f #define TWO_HAND_MAX_WEIGHT 35.0f @@ -1020,8 +1025,8 @@ void Hands_update(Object *who, float dt) { b->hand_target[1 - i] = vec3_add(Ladder_raisedGrip(item, 1.0f), vec3_add(rail, vec3_scale(right, -sign * 0.02f))); } } - if (item->type == OBJ_UMBRELLA && item->umbrella_open) /* held up over the head, the hand in front of the shoulder */ - local = vec3(sign * 0.10f * s, 1.18f * s, 0.24f * s); + if (item->type == OBJ_UMBRELLA && item->umbrella_open) /* the hand up in front of its shoulder, the forearm up (the shaft leans in over the head) */ + local = vec3(sign * UMBRELLA_HAND_OUT * s, UMBRELLA_HAND_UP * s, UMBRELLA_HAND_AHEAD * s); if (item->type == OBJ_CAN) { /* watering: the arm comes up and forward */ Vec3 lifted = vec3(sign * 0.13f * s, 1.02f * s, 0.40f * s); local = vec3_lerp(local, lifted, smoothstepf(0.0f, 1.0f, who->pour)); @@ -1199,10 +1204,25 @@ void Hands_placeHeld(Object *who) { item->rotation.x = item->rotation.z = 0.0f; Object_syncColliders(item); } - if (item->type == OBJ_UMBRELLA && item->umbrella_open) { /* upright, the handle in the fist */ - item->rotation = vec3(0.0f, item->rotation.y, 0.0f); + if (item->type == OBJ_UMBRELLA && item->umbrella_open) { /* the handle in the fist, the shaft leaning in so the canopy is over the head (not the shaft in the face) */ + const float s = who->look.height / 1.75f; + Vec3 over = Figure_toWorld(who->position, facing, vec3(0.0f, UMBRELLA_TOP * s, 0.03f * s)); + const Object *g = who->umbrella_guest; + if (g && who->umbrella_guest_t > 0.0f && hypotf(g->position.x - who->position.x, g->position.z - who->position.z) < 1.2f) { /* somebody under it too: over the two of them */ + over.x = 0.5f * (over.x + g->position.x); + over.z = 0.5f * (over.z + g->position.z); + } + Vec3 up = vec3_normalize(vec3_sub(over, hand)); + Vec3 side = vec3_cross(up, facing); + Vec3 ahead = vec3_length(side) > 1e-3f ? vec3_normalize(vec3_cross(vec3_normalize(side), up)) : facing; + item->rotation = euler_yxz_from_axes(up, ahead); + Object_syncColliders(item); + center = vec3_add(hand, vec3_scale(up, UMBRELLA_LENGTH * 0.5f - 0.07f)); + } else if (item->type == OBJ_UMBRELLA && h->drink_hand != i) { /* folded: in the fist by the middle, the tip ahead and down, the crook behind */ + Vec3 along = vec3_normalize(vec3_add(vec3_scale(facing, 0.9f), vec3(0.0f, -0.42f, 0.0f))); + item->rotation = euler_yxz_from_axes(along, vec3_normalize(vec3_cross(vec3_cross(along, facing), along))); Object_syncColliders(item); - center = vec3_add(hand, vec3(0.0f, UMBRELLA_LENGTH * 0.5f - 0.07f, 0.0f)); + center = vec3_add(hand, vec3_scale(along, 0.06f)); } else if (Object_isBottle(item)) { /* The hand around the bottle where it grips it */ center = vec3_sub(hand, vec3_scale(item->colliders[0].axes[1], bottleGrip(who, i, item))); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index dabb0da..e6e58cd 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -187,6 +187,8 @@ typedef struct Object { struct Object *owner2; /* ... tents: and who shares it */ int crate_empties; /* crates: of the bottles in it (stored), this many are empties put back ... */ unsigned char crate_slot[CRATE_SLOTS]; + struct Object *umbrella_guest; /* people: somebody under their umbrella with them (the canopy leans their way) ... */ + float umbrella_guest_t; /* ... kept up by them this long */ bool umbrella_open; /* umbrellas: open (held up in the rain) */ /* ... and what's in each slot (CRATE_SLOT: kind and sips; see car.c Crate_*) */ int tent_stage; /* tents: TentStage ... */ float tent_work; /* ... how far the stage is (0..1) ... */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index e451e55..ff75df4 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -575,6 +575,20 @@ static void corners(const Object *obj, int i, Vec3 out[8]) { } } +/* People: the highest ground under the middle and the corners of their footprint + (also where they're kept down to walking down a slope: the same ground, or on + a steep one they'd be pushed up onto the uphill corner one frame and snapped + down to the middle the next - the view shook). */ +static float footprintGround(const Collider *c, Vec3 center, const Terrain *terrain) { + float ex = projectRadius(c, vec3(1, 0, 0)) * 0.7f; + float ez = projectRadius(c, vec3(0, 0, 1)) * 0.7f; + float ground = Terrain_height(terrain, center.x, center.z); + ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z - ez)); + ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z - ez)); + ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z + ez)); + return fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z + ez)); +} + /* Keeps every collider above the terrain surface. Things: each corner over the ground right under it (they rest on their real contact points, not on the highest ground anywhere under them). People: their feet do the rest. */ @@ -607,16 +621,7 @@ static void resolveTerrain(Object *obj, const Terrain *terrain) { const Collider *c = &obj->colliders[i]; Vec3 center = Object_colliderCenter(obj, i); float bottom = center.y - projectRadius(c, WORLD_UP); - - /* Highest ground under the center and the footprint corners */ - float ex = projectRadius(c, vec3(1, 0, 0)) * 0.7f; - float ez = projectRadius(c, vec3(0, 0, 1)) * 0.7f; - float ground = Terrain_height(terrain, center.x, center.z); - ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z - ez)); - ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z - ez)); - ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z + ez)); - ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z + ez)); - + float ground = footprintGround(c, center, terrain); if (bottom < ground) { land(obj, ground - bottom); obj->on_terrain = true; @@ -690,7 +695,7 @@ static void snapToTerrain(Object *obj, const Terrain *terrain) { const Collider *c = &obj->colliders[0]; Vec3 center = Object_colliderCenter(obj, 0); float bottom = center.y - projectRadius(c, WORLD_UP); - float gap = bottom - Terrain_height(terrain, center.x, center.z); + float gap = bottom - footprintGround(c, center, terrain); if (gap > 0.0f && gap < GROUND_SNAP) { land(obj, -gap); obj->on_terrain = true; diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index 5ffc5ac..f00574a 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -573,6 +573,11 @@ Vec3 Site_drinksSpot(void) { /* in the kitchen of your garages, along its front return placed(vec3(20.1f, 0.0f, 6.0f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); } +Vec3 Site_rainRoom(void) { /* the middle of the kitchen in your garages: where the guests wait out the rain */ + if (!g_garages.set) return Site_drinksSpot(); + return placed(vec3(23.6f, 0.0f, 1.2f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); +} + Vec3 Site_houseDoor(void) { return court(MANOR_A + 6.0f, MANOR_B - 7.6f); } /* (out in front of the main building's left door) */ bool Site_inHouse(float x, float z) { @@ -787,6 +792,7 @@ static const RoomPiece ROOMS[] = { { FURN_STOVE, { 26.65f, 0, 3.2f }, { 0.6f, 0.9f, 0.6f }, 0.0f }, { FURN_SHELF, { 26.75f, 0, 7.8f }, { 1.2f, 1.8f, 0.4f }, 0.0f }, { FURN_TABLE, { 23.3f, 0, 4.0f }, { 1.6f, 0.75f, 0.9f }, 0.0f }, + { FURN_BENCH, { 26.7f, 0, -1.6f }, { 2.2f, SEAT_HEIGHT, 0.36f }, 0.0f }, /* (along the back wall: waiting out the rain) */ { FURN_CHAIR, { 22.5f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, PI_F }, { FURN_CHAIR, { 24.1f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, 0.0f }, /* 3: car repairs (the wide door) */ diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h index 0dab2af..e6f4af5 100644 --- a/entropie/src/world/site.h +++ b/entropie/src/world/site.h @@ -27,6 +27,8 @@ along: the way things are laid out side by side there; door: the way the tents' doors face. */ Vec3 Site_drinksSpot(void); +/* The middle of the kitchen in your garages: a roof to wait out the rain under, seats round it. */ +Vec3 Site_rainRoom(void); Vec3 Site_campSpot(int k); Vec3 Site_campAlong(void); Vec3 Site_campDoor(void); diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 0736334..425f2a6 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -768,8 +768,11 @@ static void clothesWet(World *world, Object *o, bool by_fire, bool night) { the waist). Put down somewhere: people go and get it, you are told where it is. You: put on for you once a cold spell (then it's up to you, B). */ /* An umbrella in hand: open while it rains (out in it), folded otherwise. Is one open over them? */ -static bool underUmbrella(World *world, Object *o) { +#define UMBRELLA_COVERS 0.9f /* of its radius: a head this near under the middle of an open umbrella stays dry */ + +static bool underUmbrella(World *world, Object *o, float dt) { bool over = false; + if ((o->umbrella_guest_t -= dt) <= 0.0f) { o->umbrella_guest = NULL; o->umbrella_guest_t = 0.0f; } /* (nobody with them any more) */ for (int h = 0; h < 2; h++) { Object *u = o->hands.item[h]; if (!u || u->type != OBJ_UMBRELLA || o->hands.state[h] != HAND_HOLDING) continue; @@ -777,12 +780,25 @@ static bool underUmbrella(World *world, Object *o) { !Weather_covered(world, vec3_add(o->position, vec3(0.0f, o->look.height + 0.3f, 0.0f))); over = over || u->umbrella_open; } + if (over || o->is_held) return over; + /* under somebody else's (walking along with them) */ + for (int i = 0; i < world->count && !over; i++) { + const Object *p = world->objects[i]; + if (p == o || !p->is_character) continue; + for (int h = 0; h < 2 && !over; h++) { + const Object *u = p->hands.item[h]; + if (!u || u->type != OBJ_UMBRELLA || !u->umbrella_open || p->hands.state[h] != HAND_HOLDING) continue; + Vec3 top = vec3_add(Object_colliderCenter(u, 0), vec3_scale(u->colliders[0].axes[1], UMBRELLA_LENGTH * 0.5f)); + float dx = top.x - o->position.x, dz = top.z - o->position.z; + over = dx * dx + dz * dz < (UMBRELLA_COVERS * UMBRELLA_RADIUS) * (UMBRELLA_COVERS * UMBRELLA_RADIUS) && top.y > o->position.y + o->look.height; + } + } return over; } static void dressForNight(World *world, Object *o, float dt) { if (o->type == OBJ_DOG) return; - bool umbrella = underUmbrella(world, o); + bool umbrella = underUmbrella(world, o, dt); 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 || (o->in_tent && Tent_hasBed(o->in_tent, o)) ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f);