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 1.77.0: steady or wobbly ladders, holding them, the work place
- Ladders: carried nearly upright when out; stood up on their own they fall over; leaned, how steady they stand depends on angle, ground, feet and what holds them; a wobbly one wobbles, then slips and falls with you - Somebody holds it (hands on the rails, a foot on the bottom rung): helpers (G) when you climb, helpful people when it looks wobbly - The work place: the tidy clear away what lies where they'd stand; no room to swing, they take the piece somewhere with room (carried, or dragged with somebody called to help) - The sporty pull up and climb ladders much quicker Includes 1.76.0 (if not committed separately): - Smoking: packs and tobacco in the pockets (used up); smokers get a craving and light up in a quiet moment - rolling one first, the lighter to it, drags (the tip glows), smoke breathed out, the butt flicked away - Shared: "Got a light?", bumming a cigarette (or a no); talk to ask for one or offer one; K lights yours / puts it out; Tab shows your pockets - Shadows: soft, smooth edges instead of stair steps Includes 1.75.0 (if not committed separately): - Urinellas: one-time use, up to 4 in the pocket; a box by the toilet refills it (the others fetch more when they run out, you click it) - Peeing: what's in the hands is put down first (the others take it up again); standing, the fly is open or the skirt or dress lifted - Lighter: needed to light the fire, runs empty; borrowed when there's none, given back (or not), owners ask for it; talk: borrow, give back, ask for yours back - Working hard in the midday sun the shirt comes off (women keep a top) - Dropping from high up: knees pulled up, legs reach for the ground, deep into the knees, a roll or a crash - Ladders: one-handed climbing (you may slip), a lying ladder is stepped over, kicked it hardly moves and your toe hurts - Dev menu: teleport to the parkour / yard; the player spawns 1 m up Includes 1.74.0 (if not committed separately): - Fix: legs showed through a skirt or dress standing normally; the thighs under it are cloth now and it's wider over the hips - Mila's Urinellas come in a pack of 3 (then she squats); now and then a mishap (on the shoes); used ones go into the bag, are dropped or flung away (drunk) as trash Includes 1.73.0 (if not committed separately): - Clothes: trousers, shorts, skirt, dress; squatting to pee trousers come down, a skirt or dress is gathered up Includes 1.72.0 (if not committed separately): - Fix: walking off the other way from something half stepped over left one foot planted behind, sliding along Includes 1.71.0 (if not committed separately): - Two-handed fidgets: rubbing the hands, hands behind the head, both hands on the hips Includes 1.70.0 (if not committed separately): - More one-hand gestures (neck, nose, ear, hair, thumb in the belt, hand on the chest, shading the eyes); mood and personal habits pick them Includes 1.69.0 (if not committed separately): - Faster with a crowd: 750 people 22 -> 14 ms a frame (kind index, cell grid for company, no foot checks far from you) Includes 1.68.0 (if not committed separately): - The walking map is updated in place when fixed things change (0.3 ms instead of 10 ms) Includes 1.67.0 (if not committed separately): - Pour your drink on the fire (C): a small fire goes out, a big one hisses, vodka flares up Includes 1.66.0 (if not committed separately): - Careful people sort the woodpile by length (front even) and put the branches lying about onto a heap at the end of the woodpile Includes 1.65.0 (if not committed separately): - Smooth off the ladder (a step back, one foot after the other) and taking it down (the hand reaches for the rail at chest height) Includes 1.64.0 (if not committed separately): - Smooth ladder and climbs: getting on, over the edge at the top, feet from where they are; the easy 1 m climb no longer drops you off again Includes 1.63.0 (if not committed separately): - The hose melts in the fire (leaks, burns through); duct tape and the shears mend it Includes 1.62.0 (if not committed separately): - Smooth pull-ups: no hop at the end, feet and knees ease over the edge, elbows and hands taking hold blend in (audit walk: joint jumps 161 -> 58) Includes 1.61.0 (if not committed separately): - A step ladder in the tool room: stands on its own, at a wall with its back to it; climb onto the wall from its top Includes 1.60.0 (if not committed separately): - A folding ladder (two 2.5 m halves on a hinge, 5 m open) beside the telescopic one; one click at a wall unfolds it and leans it there Includes 1.59.0 (if not committed separately): - Stepping up onto blocks above knee high; set moves and jumps only for things standing on the ground in front of you Includes 1.58.0 (if not committed separately): - Walking over knee-high things is one smooth set move that always finishes; at a run: jump up onto blocks, catch climbable edges
entropie/Makefile | 2 +- entropie/README.md | 24 +++-- entropie/STATE.md | 16 +++- entropie/commit_message.txt | 15 ++- entropie/src/game/game_actions.c | 41 +++++++- entropie/src/people/ai.c | 2 + entropie/src/people/ai.h | 8 ++ entropie/src/people/ai_help.c | 59 ++++++++++++ entropie/src/people/ai_internal.h | 3 + entropie/src/people/ai_work.c | 192 ++++++++++++++++++++++++++++++++++++++ entropie/src/world/ladder.c | 163 +++++++++++++++++++++++++++++--- entropie/src/world/ladder.h | 16 +++- entropie/src/world/object.h | 6 ++ 13 files changed, 518 insertions(+), 29 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index c2cd9f4..191e610 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.76.0 +VERSION := 1.77.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 4c80286..bfff08a 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -165,6 +165,7 @@ and satellite pictures (about 340 x 340 m). - Dropping from high up: the knees come up to the belly, the legs reach down just before the ground. Over about 1.3 m you go deep into the knees; over about 2.5 m the fit roll it off, the others crash (energy, dirt) +- The sporty pull up and climb ladders much quicker - Running into something sober: low, you trip or go headlong over it; high, you stagger back or go down to the side you came in from. How badly depends on speed, fitness, energy and clumsiness @@ -345,13 +346,19 @@ A hand that can't reach its grip lets go and takes hold again. slide it out and tip it onto the wall (about a second); slid out by hand first, it keeps its length. Elsewhere a click slides it out or together. Too close to something beside it, it says so instead of going through it -- Slid out, you hold it near its foot and it reaches up into the air in front - of you (steeper when you look up). A click leans it where you look: at the - top of a wall it rests on the edge, lower down it leans against the face - there. Look down and click to slide it together -- It leans on anything that holds it: walls, the car, a tree (not a bush), - with level ground and room for its feet. The top rests on the edge (the - gutter of a roof); too short for that, it leans against the face +- Slid out, you hold it near its foot, nearly upright (any flatter is too + heavy to carry). A click leans it where you look: at the top of a wall it + rests on the edge, lower down it leans against the face there. Look down + and click to slide it together. Nothing to lean on: you stand it up on its + own - and it falls over +- It leans on whatever is there. How steady it stands depends on the angle + (about 75 degrees is best), soft or muddy ground, its feet not level and + what holds it (a bush, something light). The message says when it's wobbly +- An unsteady ladder may slip when you climb it: a wobble first, then it + falls over and you with it (the higher, the harder you land) +- Somebody holding it (hands on the rails, a foot on the bottom rung) keeps + it steady: helpers (G) hold it when you climb, helpful people come over + on their own when it looks wobbly - Click a leaning ladder to climb: W / S up and down, at the top W gets you over the edge, Space jumps off. From up there, click its top to climb down - One hand free is enough (something up to 5 kg in the other), but you may @@ -389,6 +396,9 @@ say no more often and don't come to you for a bit. may answer "In a minute!" - "The firewood goes somewhere else now": show them a new place; the old stack gets carried over to the new woodpile +- The work place: things lying where they'd stand to split or cut, the tidy + clear away first; no room to swing (a wall, the car), they take the piece + somewhere with room - carried, or dragged with somebody called to help - Firewood: they fetch tools, cut, split and stack it on a woodpile (gaps first, where pieces were taken off; it shows how many really lie on it). Not everyone knows how: "Can you show me?" They watch you until they get it. diff --git a/entropie/STATE.md b/entropie/STATE.md index 97d70ff..0ecdaa4 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -105,6 +105,16 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask ## Things +Ladder steadiness (`ladder.c`): set when it's leaned (`stability`): 1 - 0.7 x (degrees off 75: flatter / 15, steeper / 9), x (1 - 0.6 mud at the foot), x (1 - 0.6 x feet off level / 12 cm), a bush x 0.2, something loose and light x weight / 60 kg, only against a face x 0.85; step ladder 1. `Ladder_steadiness`: 1 while somebody holds it (`held_by`, standing within 0.5 m of the hold spot 0.6 m out from the foot), else `stability`. Climbing (`game_actions.c` ladderSlips): risk a second while moving 0.9 x (1 - steady)^1.5 x (0.3 + height / 3 m) x (1 + drunk); first a wobble (remembered 30 s), then `Ladder_topple`: sideways, or back off the wall when steeper than 80 deg; you come off (a fall above 0.6 m; the landing does the rest). Placed: under 0.5 "very wobbly", under 0.85 "a bit wobbly". + +Sporty is quicker: pull-ups x 1 / (0.6 + 0.9 fitness) (hard ones; easy ones the square root of that), the climb over the top of a ladder too, up and down a ladder x (0.7 + 0.6 fitness) (hands and feet to the rungs as quick). Stepping over stays as it was (quicker made the legs jumpy in the audit). + +Toppling (`ladder.c` fallOver): about its foot like a rod (theta'' = 1.5 g / length x sin theta), then it lies flat, loose. Stood up with nothing to lean on (`standFree`, a held-up ladder clicked at nothing): 0.6 m in front, tilted a little away, falls over once it's up. Carried slid out: nearly upright (1.36 rad, 1.15-1.52 with the view). + +Holding (`ai_help.c` ai_holdLadder): helpers (G) hold the ladder their leader climbs; idle / wandering people with willing over 0.6 come within 12 m when somebody is on a ladder under 0.85 steady. They put things down, stand 0.6 m out from the foot facing it, both hands on the rails at 1.15 m along, the right foot on the bottom rung (`Ladder_poseHands` / `Ladder_poseFeet`), and let go 3 s after nobody is on it ("There you go."). + +The work place (`ai_work.c` prepareSpot, before splitting or cutting a piece, once per piece, last 4 remembered): loose things up to 25 kg (not wood, tools, the woodpile) within 0.9 m of where they'd stand = messy; something fixed or 60 kg+ (not wood, trees) within 0.35 m of it = cramped. Tidy (likes trash over 0.5, or stacking care 0.65+) and only messy: they clear it ("Let me clear this away first"): each thing carried 2.2 m to the side. Cramped (or messy and not tidy) and an open spot 2.5-4.9 m away (fixed things 1.3 m off, loose 0.9 m): the piece goes there - carried (the carried piece doesn't go to the pile meanwhile), else grabbed and dragged; over 30 kg somebody idle and willing within 20 m is called ("X, give me a hand moving this?", they help like a G helper). Gives up after 45 s. + Ladder kicks (`physics.c` push / noteBump): a lying ladder is stepped over (only a leaning one is never), walked into it moves 0.15 of what a loose thing would, at most 0.25 m/s; walking into it counts as a bump: "Ow..." / "Ouch! My toe!" (drunk: a stagger). One-handed ladder (`game_actions.c` oneHandSlip): climbing with something up to 5 kg in the other hand. Slip risk a second while moving: 0.16 x (1.3 - fitness) x (0.6 + clumsy) x (1 + 3 drunk) x (1.5 - energy) x (1 - 0.9 practice); `ladder_skill` grows 0.01 a second of it. First slip a wobble (3 s), the next one a fall (on the bottom rung: you just slip off). @@ -169,8 +179,8 @@ Most important first. - More gestures and feelings to pick personalities from: the lists in Body / Fidgets and Gestures can grow **People** -- The work place: a cramped or badly fitting spot - take the work (the log, the tools) somewhere with room, call somebody over to help move heavy things; or (tidy people) clear the place first -- Ladders: an opened ladder carried nearly upright (else too hard to carry); put it down anywhere, physics decides if it stands; an unsteady one may fall when climbed; somebody holds it (asked, or helpful people on their own when they see you on it), or a foot on the bottom rung +- The work place, still to do: dragging a heavy piece with a called helper is untested in play; other jobs than split / cut (felling room, trash); the player asked to help move it +- Ladders, still to do: the ladder visibly shaking on a wobble; a holder for a ladder standing free; NPCs climbing - Smoking, still to do: buying more (a kiosk), the mood (nerves) from a craving, an ashtray - Peeing, still to do: some women don't like the Urinella; a Urinella in the hand (not drawn); the player-woman picks up her things again herself - Shirt off for you (now only the others); "no shirt no service" policy (decision open) @@ -184,4 +194,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 1.76.0 (build 7028602)` +State of `entropie 1.77.0 (build 208a5af)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 662ff21..a66b65d 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,5 +1,16 @@ -entropie 1.76.0: smoking, smooth shadow edges - +entropie 1.77.0: steady or wobbly ladders, holding them, the work place + +- Ladders: carried nearly upright when out; stood up on their own they + fall over; leaned, how steady they stand depends on angle, ground, feet + and what holds them; a wobbly one wobbles, then slips and falls with you +- Somebody holds it (hands on the rails, a foot on the bottom rung): + helpers (G) when you climb, helpful people when it looks wobbly +- The work place: the tidy clear away what lies where they'd stand; no room + to swing, they take the piece somewhere with room (carried, or dragged + with somebody called to help) +- The sporty pull up and climb ladders much quicker + +Includes 1.76.0 (if not committed separately): - Smoking: packs and tobacco in the pockets (used up); smokers get a craving and light up in a quiet moment - rolling one first, the lighter to it, drags (the tip glows), smoke breathed out, the butt flicked away diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index 539e29a..74e9c9e 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -127,7 +127,9 @@ static bool startClimb(Game *game, float top, Vec3 probe) { float grip = game->climb_face + 0.1f * s; /* the hands on it, just over the edge */ game->climb_edge = vec3(p->position.x + f.x * grip, top + 0.01f, p->position.z + f.z * grip); float vigor = Object_vigor(p) * (1.0f - 0.3f * fminf(p->drunk, 1.0f)); - game->climb_time = (hard ? 1.3f + 0.7f * fmaxf(rise / s - CLIMB_EASY, 0.0f) : 0.6f + rise * 0.6f) / fmaxf(vigor, 0.4f); + float sport = 0.6f + 0.9f * p->fitness; /* (the sporty pull up much quicker, the hard ones most of all) */ + game->climb_time = (hard ? 1.3f + 0.7f * fmaxf(rise / s - CLIMB_EASY, 0.0f) : 0.6f + rise * 0.6f) / fmaxf(vigor, 0.4f) + / (hard ? sport : sqrtf(sport)); game->climb_t = 0.0f; game->climb_push = 0.0f; noteFeet(game, p); @@ -369,6 +371,32 @@ static void smokeOrStop(Game *game) { /* Climbing a ladder with one hand (something in the other): the unpractised, clumsy, unfit, tired or drunk may slip - a wobble first, then off they go. Every climb like that is practice. */ +static float upAlongY(const Object *l) { return l->colliders[0].axes[1].y; } /* (sin of how steep it stands) */ + +/* An unsteady ladder (see Ladder_steadiness) may slip when you climb it: a wobble + first, then over it goes - sideways, or back off the wall when it stands too steep. + The higher up, the worse. Somebody holding it: steady. */ +static bool ladderSlips(Game *game, Object *p, Object *l, bool moving, float dt) { + float steady = Ladder_steadiness(l); + if (!moving || steady >= 0.98f || p->ladder_off > 0.0f) return false; + float up = clampf(p->ladder_at / 3.0f, 0.0f, 1.0f); + float risk = 0.9f * powf(1.0f - steady, 1.5f) * (0.3f + up) * (1.0f + fminf(p->drunk, 1.0f)); + if ((float)rand() / (float)RAND_MAX >= risk * dt) return false; + if (l->ladder_wobble <= 0.0f) { + l->ladder_wobble = 30.0f; /* (the next one is no wobble) */ + say(game, "The ladder wobbles under you! (somebody to hold it: G)"); + return false; + } + Vec3 n = l->lean_normal, across = vec3_cross(n, vec3(0.0f, 1.0f, 0.0f)); + bool steep = upAlongY(l) > 0.985f; + Vec3 dir = steep ? n : vec3_scale(across, rand() % 2 ? 1.0f : -1.0f); + Ladder_topple(&game->world, l, dir); + p->velocity = vec3_add(vec3_scale(dir, 1.2f), vec3(0.0f, 0.5f, 0.0f)); + if (p->ladder_at > 0.6f) Body_fall(&p->body, dir); + say(game, "The ladder slips - down you go!"); + return true; +} + static bool oneHandSlip(Game *game, Object *p, Object *l, bool moving, float dt) { p->ladder_wobble = fmaxf(0.0f, p->ladder_wobble - dt); bool one_hand = (p->hands.state[HAND_LEFT] == HAND_HOLDING) != (p->hands.state[HAND_RIGHT] == HAND_HOLDING); @@ -402,6 +430,8 @@ void game_climbLadder(Game *game, const Input *in, float dt) { say(game, "You jump off the ladder"); return; } + l->ladder_wobble = fmaxf(0.0f, l->ladder_wobble - dt); + if (ladderSlips(game, p, l, in->move_forward != 0.0f, dt)) return; if (oneHandSlip(game, p, l, in->move_forward != 0.0f, dt)) return; if (Ladder_climb(p, in->move_forward, dt) != CLIMB_AT_EDGE) return; /* Over the edge onto whatever it leans on, if there's room to stand */ @@ -430,7 +460,7 @@ void game_climbLadder(Game *game, const Input *in, float dt) { Vec3 to_edge = vec3_sub(edge, p->position); game->climb_face = fmaxf(0.05f, -(to_edge.x * n.x + to_edge.z * n.z)); } - game->climb_time = 1.6f; /* (pulled up over it like a high ledge: from the ladder the feet start well below) */ + game->climb_time = 1.6f / (0.6f + 0.9f * p->fitness); /* (pulled up over it like a high ledge: from the ladder the feet start well below; quicker sporty) */ game->climb_t = 0.0f; say(game, "You climb over the edge"); } @@ -459,11 +489,16 @@ static bool useLadder(Game *game) { if (lean == LEAN_DONE && ladder->variant == LADDER_STEP) say(game, ladder->lean_top > 0.0f ? "You stand the step ladder there - climb it to get up there" : "You stand the step ladder up - click it to climb"); - else if (lean == LEAN_DONE) + else if (lean == LEAN_DONE && ladder->place_top < 0.0f) + say(game, "You stand it up on its own - nothing holds it..."); + else if (lean == LEAN_DONE) { say(game, ladder->lean_top > 0.0f ? (was_in ? (fold ? "You unfold the ladder and lean it there - click it to climb" : "You slide the ladder out and lean it there - click it to climb") : "The ladder leans there - click it to climb") : "It leans there, but doesn't reach the top"); + if (ladder->stability < 0.5f) tellMore(game, "very wobbly: get somebody to hold it (G)"); + else if (ladder->stability < 0.85f) tellMore(game, "a bit wobbly"); + } else if (lean == LEAN_NO) say(game, why); else { Ladder_slide(ladder); diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index d359ac0..62b1d14 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1292,6 +1292,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { stand(self); } } + /* Somebody on a ladder: hold it steady (helpers, the helpful) */ + 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; /* Tidy people pick things up on the way */ diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index 907e9ff..a76d7c9 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -168,6 +168,14 @@ typedef struct { float lighter_time; /* until they next think about lighters (give one back, ask for theirs) */ float lighter_ask; /* until they ask anybody for a lighter again */ struct Object *set_aside[2]; /* put down to pee: picked up again after */ + /* Getting the work place ready (ai_work.c prepareSpot) */ + struct Object *prep_for; /* the piece it was looked at for ... */ + int prep_stage; /* ... PREP_* */ + Vec3 prep_spot; /* ... where the work goes (moving it) */ + struct Object *prep_item; /* ... what's being cleared away */ + struct Object *prep_helper; /* ... who was called over to help move it */ + float prep_time; + float hold_idle; /* holding a ladder nobody is on: seconds of that (then they let go) */ float sun_heat; /* seconds of hard work in the midday sun (the shirt comes off) */ float sniff_cooldown; /* until the next remark about a smell */ AiMode before; /* mode to go back to after being shown something */ diff --git a/entropie/src/people/ai_help.c b/entropie/src/people/ai_help.c index de18de0..d3a4bc5 100644 --- a/entropie/src/people/ai_help.c +++ b/entropie/src/people/ai_help.c @@ -5,6 +5,7 @@ */ #include "people/ai_internal.h" +#include "world/ladder.h" /* ---------- Helping ---------- */ @@ -569,3 +570,61 @@ void ai_updateHelp(Object *self, Object *const *objects, int count, float dt) { self->body.look_weight = 0.8f; } } + +/* ---------- Holding a ladder ---------- */ + +#define HOLD_SEES 12.0f /* m: the helpful notice somebody on a wobbly ladder this far off */ +#define HOLD_LINGER 3.0f /* s: nobody on it any more - they let go */ + +static Object *climberOn(Object *const *objects, int count, const Object *ladder) { + for (int i = 0; i < count; i++) + if (objects[i]->on_ladder == ladder) return objects[i]; + return NULL; +} + +bool ai_holdLadder(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + Object *l = self->holding_ladder; + if (!l) { + if (self->drunk > 0.6f || Hands_isGrabbing(self) || ai->talking) return false; + bool helping = ai->mode == AI_HELP && ai->leader; + bool free_to = ai->mode == AI_IDLE || ai->mode == AI_WANDER; + if (!helping && !free_to) return false; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o == self || !o->on_ladder || o->on_ladder->held_by || !o->on_ladder->leaning) continue; + Object *ladder = o->on_ladder; + bool mine = helping && ai->leader == o; /* (the one we help is going up) */ + bool sees = free_to && ai->character.willing > 0.6f && Ladder_steadiness(ladder) < 0.85f && + flatDistance(ladder->position, self->position) < HOLD_SEES; + if (!mine && !sees) continue; + freeHands(self, NULL); + if (self->hands.item[HAND_LEFT] || self->hands.item[HAND_RIGHT]) return false; /* (can't put it down) */ + Ladder_hold(self, ladder); + ai->hold_idle = 0.0f; + Ai_say(self, mine ? "I'll hold the ladder." : "Wait - I'll hold the ladder for you!"); + l = ladder; + break; + } + if (!l) return false; + } + /* Nobody on it any more (or it's gone): let go */ + Object *climber = climberOn(objects, count, l); + ai->hold_idle = climber ? 0.0f : ai->hold_idle + dt; + if (!l->leaning || l->held_by != self || ai->hold_idle > HOLD_LINGER || Body_isDown(&self->body)) { + if (l->held_by == self && l->leaning && ai->hold_idle > HOLD_LINGER) Ai_say(self, "There you go."); + Ladder_hold(self, NULL); + return false; + } + Vec3 facing, spot = Ladder_holdSpot(l, &facing); + float d = flatDistance(spot, self->position); + if (d > 0.35f) { + Vec3 dir = ai_steer(self, NULL, objects, count, spot); + if (vec3_length(dir) < 0.5f) stand(self); + else ai_walk(self, dir, fminf(WALK_SPEED, d * 2.5f + 0.3f), dt); + return true; + } + stand(self); + turnTowards(self, facing, dt * 2.0f); + return true; +} diff --git a/entropie/src/people/ai_internal.h b/entropie/src/people/ai_internal.h index 9b32056..fa19c43 100644 --- a/entropie/src/people/ai_internal.h +++ b/entropie/src/people/ai_internal.h @@ -91,6 +91,9 @@ void ai_updateFollow(Object *self, Object *const *objects, int count, float dt); bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt); bool ai_putAway(Object *self, Object *const *objects, int count, float dt); void ai_updateHelp(Object *self, Object *const *objects, int count, float dt); +/* Holding a ladder steady for somebody on it: helpers for their leader, the + helpful on their own when it looks wobbly. True while they're at it. */ +bool ai_holdLadder(Object *self, Object *const *objects, int count, float dt); void ai_ensureReady(Object *self); static inline float random01(Ai *ai) { diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index eedccae..8074c58 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -918,6 +918,192 @@ static bool thinkItOver(Object *self, const Object *target, float dt) { return true; } +/* ---------- The work place ---------- */ + +/* Before splitting or cutting: is there room? Things lying in the way: the + tidy clear them away first. Cramped (a wall, a car, a pile of stuff right + there): the others take the work somewhere with room - carried if it can + be, else dragged, with somebody called over to help when it's heavy. */ +enum { PREP_NONE, PREP_CLEAR, PREP_CARRY, PREP_DONE }; +#define PREP_CLUTTER 0.9f /* m round where they stand: loose things here are in the way */ +#define PREP_WALL 0.35f /* gap to something fixed (or heavy) from where they stand: too close to swing */ +#define PREP_CARRY_KG 25.0f /* things in the way up to this are carried off */ +#define PREP_AWAY 2.2f /* ... this far */ +#define PREP_TIMEOUT 45.0f +#define PREP_HELP 20.0f /* m: somebody this near is called to help drag it */ + +static bool ignoredHere(const Object *o) { + return o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK || o->collider_count == 0 || o->is_held; +} + +static bool inTheWay(const Object *self, const Object *o, const WoodTask *t) { + if (o == self || o == t->target || o->is_character || o->is_static || o->stored || ignoredHere(o)) return false; + if (o->weight > PREP_CARRY_KG || o->type == OBJ_WOODPILE || toolOf(o) != TOOL_NONE) return false; /* (tools lying there are for this) */ + if (Tools_splittable(o) || Tools_cuttable(o) || o->type == OBJ_FIREWOOD) return false; /* (the wood is the work) */ + return flatDistance(Object_colliderCenter(o, 0), t->spot) < PREP_CLUTTER; +} + +static Object *clutterAt(Object *self, Object *const *objects, int count, const WoodTask *t) { + Object *best = NULL; + float best_d = 1e9f; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (!inTheWay(self, o, t) || ai_skipped(&self->ai, o)) continue; + float d = flatDistance(o->position, self->position); + if (d < best_d) { best_d = d; best = o; } + } + return best; +} + +/* Something fixed (or too heavy to shift) right where they'd stand to swing. */ +static bool cramped(const Object *self, Object *const *objects, int count, Vec3 spot, const Object *target) { + for (int i = 0; i < count; i++) { + const Object *o = objects[i]; + if (o == self || o == target || o->is_character || ignoredHere(o) || o->type == OBJ_TREE) continue; + if ((!o->is_static && o->weight < 60.0f) || Tools_cuttable(o) || Tools_splittable(o)) continue; /* (other wood: more work, not a wall) */ + if (Hands_gapFrom(spot, self->look.height, o) < PREP_WALL) return true; + } + return false; +} + +/* An open spot near the piece: nothing fixed within a swing, nothing lying about. */ +static bool roomyPlace(const Object *self, Object *const *objects, int count, const Object *target, Vec3 *out) { + Vec3 from = Object_colliderCenter(target, 0); + for (int ring = 0; ring < 3; ring++) { + float r = 2.5f + 1.2f * (float)ring; + for (int k = 0; k < 8; k++) { + float a = (float)k * PI_F / 4.0f + 0.3f * (float)ring; + Vec3 p = vec3(from.x + r * cosf(a), self->position.y, from.z + r * sinf(a)); + bool ok = true; + for (int i = 0; i < count && ok; i++) { + const Object *o = objects[i]; + if (o == target || o == self || ignoredHere(o)) continue; + if (Hands_gapFrom(p, self->look.height, o) < (o->is_static || o->weight >= 60.0f ? 1.3f : 0.9f)) ok = false; + } + if (ok) { *out = p; return true; } + } + } + return false; +} + +/* Somebody idle and willing near, to help drag it. */ +static Object *helperFor(const Object *self, Object *const *objects, int count) { + Object *best = NULL; + float best_d = PREP_HELP; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o == self || o->type != OBJ_HUMAN || o->ai.talking || o->drunk > 0.6f || Body_isBusy(&o->body)) continue; + if ((o->ai.mode != AI_IDLE && o->ai.mode != AI_WANDER) || o->ai.character.willing < 0.4f) continue; + float d = flatDistance(o->position, self->position); + if (d < best_d) { best_d = d; best = o; } + } + return best; +} + +static void prepDone(Object *self) { + Ai *ai = &self->ai; + if (Hands_isGrabbing(self)) Hands_release(self); + if (ai->prep_helper && ai->prep_helper->ai.mode == AI_HELP && ai->prep_helper->ai.leader == self) { + Ai_stopHelping(ai->prep_helper); + Ai_say(ai->prep_helper, "No problem."); + } + ai->prep_helper = NULL; + ai->prep_item = NULL; + ai->prep_stage = PREP_DONE; +} + +/* True while busy getting the place ready (t: what they want to work on). */ +static bool prepareSpot(Object *self, Object *const *objects, int count, const WoodTask *t, float dt) { + Ai *ai = &self->ai; + if (t->job != JOB_SPLIT && t->job != JOB_CUT) return false; + if (ai->prep_for != t->target) { /* a new piece: look at the place (once, whoever does) */ + if (t->target->spot_checked) return false; + t->target->spot_checked = true; + ai->prep_for = t->target; + ai->prep_time = 0.0f; + ai->prep_item = ai->prep_helper = NULL; + bool tidy = ai->character.likes[ORDER_TRASH] > 0.5f || ai_stackCare(self) >= 0.65f; /* (who likes things tidy) */ + bool messy = clutterAt(self, objects, count, t) != NULL; + bool tight = cramped(self, objects, count, t->spot, t->target); + /* (no room: elsewhere with it; things lying about: the tidy clear them, the others don't mind) */ + ai->prep_stage = tight && roomyPlace(self, objects, count, t->target, &ai->prep_spot) ? PREP_CARRY + : tidy && messy ? PREP_CLEAR : PREP_DONE; + if (ai->prep_stage == PREP_CLEAR) Ai_say(self, "Let me clear this away first."); + else if (ai->prep_stage == PREP_CARRY) Ai_say(self, "No room to swing here - over there's better."); + } + if (ai->prep_stage == PREP_DONE || ai->prep_stage == PREP_NONE) return false; + if ((ai->prep_time += dt) > PREP_TIMEOUT) { /* (not getting anywhere: here then) */ + for (int h = 0; h < 2; h++) if (self->hands.item[h] == t->target || self->hands.item[h] == ai->prep_item) Hands_drop(self, h); + prepDone(self); + return false; + } + if (self->hands.state[0] == HAND_REACHING || self->hands.state[1] == HAND_REACHING) { stand(self); return true; } + + if (ai->prep_stage == PREP_CLEAR) { + Object *item = ai->prep_item; + bool carrying = item && (self->hands.item[0] == item || self->hands.item[1] == item); + if (carrying) { /* off to the side with it, away from the work */ + Vec3 away = flat(vec3_sub(t->spot, t->aim)); + away = vec3_length(away) > 0.05f ? vec3_normalize(away) : vec3_scale(Object_facing(self), -1.0f); + Vec3 side = vec3(-away.z, 0.0f, away.x); + Vec3 drop = vec3_add(t->spot, vec3_add(vec3_scale(away, 0.6f), vec3_scale(side, PREP_AWAY))); + if (flatDistance(drop, self->position) > 0.4f) { + Vec3 dir = ai_steer(self, NULL, objects, count, drop); + if (vec3_length(dir) < 0.5f) stand(self); + else ai_walk(self, dir, WALK_SPEED * 0.8f, dt); + return true; + } + for (int h = 0; h < 2; h++) if (self->hands.item[h] == item) Hands_drop(self, h); + ai->prep_item = NULL; + return true; + } + item = ai->prep_item = clutterAt(self, objects, count, t); + if (!item) { prepDone(self); return false; } /* (clear: to work) */ + if (!goTo(self, item, objects, count, PICKUP_REACH, dt)) return true; + int hand = Hands_freeHand(self); + if (hand < 0 || !Hands_pickUpObject(self, hand, item)) { + freeHands(self, NULL); /* (the tool down a moment: fetched again after) */ + hand = Hands_freeHand(self); + if (hand < 0 || !Hands_pickUpObject(self, hand, item)) { skipTarget(ai, item); ai->prep_item = NULL; } + } + return true; + } + + /* Moving the work: carried if it can be, else dragged (with help if it's heavy and somebody's there) */ + Object *piece = t->target; + bool carrying = self->hands.item[0] == piece || self->hands.item[1] == piece; + bool dragging = self->hands.grabbed == piece; + if (carrying || dragging) { + if (flatDistance(Object_colliderCenter(piece, 0), ai->prep_spot) < 0.7f) { /* there: down with it, and to work */ + for (int h = 0; h < 2; h++) if (self->hands.item[h] == piece) Hands_drop(self, h); + if (dragging) Ai_say(self, ai->prep_helper ? "Thanks! That's better." : "There."); + prepDone(self); + return true; + } + Vec3 dir = ai_steer(self, piece, objects, count, ai->prep_spot); + if (vec3_length(dir) < 0.5f) { stand(self); return true; } + ai_walk(self, dir, dragging ? WALK_SPEED * 0.45f : WALK_SPEED * 0.7f, dt); + return true; + } + if (!goTo(self, piece, objects, count, PICKUP_REACH, dt)) return true; + freeHands(self, NULL); + if (Hands_pickUpObject(self, HAND_RIGHT, piece) || Hands_pickUpObject(self, HAND_LEFT, piece)) return true; + if (Hands_grabObject(self, piece)) { /* too big to lift: drag it, and call somebody to help */ + Object *helper = piece->weight > 30.0f ? helperFor(self, objects, count) : NULL; + if (helper) { + static char line[AI_SPEECH_MAX]; + snprintf(line, sizeof line, "%s, give me a hand moving this?", helper->name); + Ai_say(self, line); + Ai_say(helper, "Sure."); + Ai_help(helper, self); + ai->prep_helper = helper; + } + return true; + } + prepDone(self); /* (can't move it: here then) */ + return false; +} + void ai_updateWork(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; /* Worn out: a breather first */ @@ -940,12 +1126,18 @@ void ai_updateWork(Object *self, Object *const *objects, int count, float dt) { return; } + Object *moving = ai->prep_for; + if (ai->prep_stage == PREP_CARRY && moving && (self->hands.item[0] == moving || self->hands.item[1] == moving || self->hands.grabbed == moving)) { + WoodTask mt = ai_taskFor(self, moving, Tools_splittable(moving) ? JOB_SPLIT : JOB_CUT); /* (taking the work elsewhere: not to the pile) */ + if (prepareSpot(self, objects, count, &mt, dt)) return; + } if (firewoodInHands(self) > 0 && carryFirewood(self, objects, count, dt)) return; if (branchesInHands(self) > 0 && tidyBranches(self, objects, count, dt)) return; WoodTask t; if (ai_nextWoodTask(self, objects, count, ai->work_area, ai->order == ORDER_TREES, &t)) { if (thinkItOver(self, t.target, dt)) return; if (t.job == JOB_FELL) ai->work_area = Object_colliderCenter(t.target, 0); + if (prepareSpot(self, objects, count, &t, dt)) return; ai_workOn(self, objects, count, &t, dt); return; } diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c index 2dc9bab..f83fb7a 100644 --- a/entropie/src/world/ladder.c +++ b/entropie/src/world/ladder.c @@ -1,6 +1,7 @@ #include "world/ladder.h" #include <stdio.h> #include "world/physics.h" +#include "world/wetness.h" #define SLIDE_SPEED 0.7f /* of the way out per second */ #define LEAN_ANGLE 75.0f /* degrees from the ground: the 1-in-4 rule */ @@ -16,10 +17,10 @@ #define MOUNT_REACH 1.2f #define LADDER_ONTO 1.6f /* m/s the body moves over onto it, getting on */ #define STEP_OFF_TIME 0.6f /* seconds to step off at the bottom */ -#define RAISE_ANGLE 1.05f /* a raised ladder: this steep (rad) looking straight ahead ... */ -#define RAISE_FOLLOW 0.9f /* ... steeper looking up, flatter looking down ... */ -#define RAISE_LOW 0.75f -#define RAISE_HIGH 1.45f +#define RAISE_ANGLE 1.36f /* a raised ladder: nearly upright (rad) looking straight ahead (any flatter is too heavy to hold) ... */ +#define RAISE_FOLLOW 0.45f /* ... a bit steeper looking up, flatter looking down ... */ +#define RAISE_LOW 1.15f +#define RAISE_HIGH 1.52f #define RAISE_SPEED 1.6f /* rad/s it tips up or down */ #define RAISE_GRIP 0.45f /* held this far up from its foot */ #define FLATTEST 55.0f /* degrees: no flatter than this (it would slide out) */ @@ -33,6 +34,8 @@ #define STEP_AT_WALL 0.6f /* ... a wall this close past its back feet: it stands with its back to it */ #define STEP_WALL_GAP 0.08f /* ... its back feet this far from that wall */ #define STEP_OFF_UP 0.9f /* ... from its top you get onto that if it's no higher than this over the top */ +#define FREE_AHEAD 0.6f /* stood up on its own (nothing to lean on): this far in front of you */ +#define HOLD_OUT 0.6f /* somebody holding it stands this far out from its foot */ static const Vec3 UP = { 0.0f, 1.0f, 0.0f }; @@ -105,15 +108,88 @@ static void putUp(Object *l, float dt) { if (t >= 1.0f) { /* up: it leans there now */ l->placing = 0.0f; l->leaning = true; - l->lean_top = l->place_top; + l->lean_top = fmaxf(l->place_top, 0.0f); l->rotation = euler_yxz_from_axes(l->place_axis[1], l->place_face[1]); l->position = l->place_foot[1]; Object_syncColliders(l); + if (l->place_top < 0.0f) { /* (stood up on its own: nothing holds it - over it goes) */ + l->leaning = false; + l->tip = 0.0f; + l->tip_v = 0.25f; + l->tip_from = upAlong(l); + l->place_face[0] = l->colliders[0].axes[2]; + } + } +} + +/* Falling over about its foot, faster and faster, then it lies there. */ +static void fallOver(Object *l, float dt) { + float len = fmaxf(lengthOf(l), 1.0f); + float from = acosf(clampf(vec3_dot(l->tip_from, UP), -1.0f, 1.0f)); /* (how far it leaned already) */ + float theta = from + l->tip; + l->tip_v += 1.5f * 9.81f / len * sinf(fmaxf(theta, 0.05f)) * dt; /* (a rod tipping over its end) */ + l->tip += l->tip_v * dt; + theta = from + l->tip; + Vec3 target = vec3_add(vec3_scale(UP, cosf(theta)), vec3_scale(l->tip_dir, sinf(theta))); + Vec3 axis = vec3_normalize(vec3_lerp(l->tip_from, target, smoothstepf(0.0f, 0.25f, l->tip))); + if (theta >= PI_F * 0.5f - 0.04f) { /* down: lying flat, loose again */ + l->tip = l->tip_v = 0.0f; + l->rotation = euler_yxz_from_axes(l->tip_dir, UP); + l->position.y += 0.06f; + l->is_static = false; + l->velocity = l->angular_velocity = vec3_zero(); + if (l->held_by) l->held_by->holding_ladder = NULL; + l->held_by = NULL; + Object_syncColliders(l); + return; + } + Vec3 face = vec3_sub(l->place_face[0], vec3_scale(axis, vec3_dot(l->place_face[0], axis))); + face = vec3_length(face) > 1e-3f ? vec3_normalize(face) : vec3_normalize(vec3_cross(axis, vec3(axis.z, 0.0f, -axis.x))); + l->rotation = euler_yxz_from_axes(axis, face); + Object_syncColliders(l); +} + +void Ladder_topple(World *world, Object *l, Vec3 dir) { + if (l->tip_v > 0.0f || l->placing > 0.0f) return; + for (int i = 0; i < world->count; i++) { /* anybody on it comes off */ + Object *o = world->objects[i]; + if (o->on_ladder == l) Ladder_getOff(o); + } + dir = flatv(dir); + l->tip_dir = vec3_length(dir) > 1e-3f ? vec3_normalize(dir) : vec3(1.0f, 0.0f, 0.0f); + l->tip_from = upAlong(l); + l->place_face[0] = l->colliders[0].axes[2]; + l->tip = 0.0f; + l->tip_v = 0.3f; + l->leaning = false; + l->lean_top = 0.0f; +} + +float Ladder_steadiness(const Object *l) { + const Object *h = l->held_by; + if (h && h->holding_ladder == l && !Body_isDown(&h->body)) { + Vec3 facing; + if (flatDist(h->position, Ladder_holdSpot(l, &facing)) < 0.5f) return 1.0f; } + return l->stability; +} + +void Ladder_hold(Object *who, Object *l) { + if (who->holding_ladder && who->holding_ladder->held_by == who) who->holding_ladder->held_by = NULL; + who->holding_ladder = l; + if (l) l->held_by = who; +} + +Vec3 Ladder_holdSpot(const Object *l, Vec3 *facing) { + *facing = vec3_scale(l->lean_normal, -1.0f); + Vec3 at = vec3_add(l->position, vec3_scale(l->lean_normal, HOLD_OUT)); + at.y = l->position.y; + return at; } void Ladder_update(Object *l, float dt) { if (l->placing > 0.0f) { putUp(l, dt); return; } + if (l->tip_v > 0.0f) { fallOver(l, dt); return; } if (l->raised > 0.0f) l->raised += clampf(l->raise_goal - l->raised, -RAISE_SPEED * dt, RAISE_SPEED * dt); if (l->extend == l->extend_goal) return; float step = (stepLadder(l) ? STEP_FOLD_SPEED : SLIDE_SPEED) * dt; @@ -321,6 +397,23 @@ static int standStep(World *world, Object *who, Object *l, const char **why) { Vec3 z = vec3_normalize(vec3_sub(back, vec3_scale(up, vec3_dot(back, up)))); if (!roomFor(world, who, l, NULL, foot, up, z, 1.0f)) { *why = "No room for the step ladder there"; return LEAN_NO; } startPutUp(who, l, foot, up, z, back, 1.0f, top); + l->stability = 1.0f; /* (it stands on its own legs) */ + return LEAN_DONE; +} + +/* Nothing to lean it on: stood up on its own in front of you (out all the way), + and gravity does the rest - it falls over (see putUp, fallOver). */ +static int standFree(World *world, Object *who, Object *l, const char **why) { + Vec3 f = Object_facing(who); + Vec3 foot = vec3_add(who->position, vec3_scale(f, FREE_AHEAD)); + foot.y = World_groundHeight(world, foot.x, foot.z, who->position.y + 0.8f); + if (fabsf(foot.y - who->position.y) > 0.4f) { *why = "No ground to stand it on there"; return LEAN_NO; } + Vec3 tilt = vec3_normalize(vec3_add(UP, vec3_scale(f, 0.07f))); /* (never quite upright) */ + Vec3 z = vec3_normalize(vec3_sub(vec3_scale(f, -1.0f), vec3_scale(tilt, vec3_dot(vec3_scale(f, -1.0f), tilt)))); + startPutUp(who, l, foot, tilt, z, vec3_scale(f, -1.0f), folding(l) ? 1.0f : l->extend, 0.0f); + l->place_top = -1.0f; /* (free: it falls when it's up) */ + l->tip_dir = f; + l->stability = 0.0f; return LEAN_DONE; } @@ -333,11 +426,9 @@ int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why if (raised && who->type == OBJ_PLAYER && who->body.look_dir.y < LOOK_DOWN) return LEAN_NOTHING; Object *thing = raised ? aimedAt(world, who, l, &face, &n, &aim_y) : NULL; if (!thing) thing = faceAhead(world, who, l, &face, &n); - if (!thing && raised) { *why = "Look at where its top should go (a wall, a tree) and click - look down to slide it together"; return LEAN_NO; } + if (!thing && raised) return standFree(world, who, l, why); if (!thing) return LEAN_NOTHING; if (fabsf(n.y) > 0.35f) { *why = "No upright side to lean it on there"; return LEAN_NO; } - if (thing->type == OBJ_BUSH) { *why = "The bush won't hold a ladder"; return LEAN_NO; } - if (!thing->is_static && thing->weight < HOLDS_WEIGHT) { *why = "That would just tip over"; return LEAN_NO; } n = vec3_normalize(flatv(n)); /* How high it goes just behind that face, and the ground in front of it */ @@ -395,6 +486,22 @@ int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why if (!roomFor(world, who, l, thing, foot, up, z, extend_to)) { *why = "No room for it there - something's in the way"; return LEAN_NO; } startPutUp(who, l, foot, up, z, n, extend_to, reaches ? vec3_length(vec3_sub(rest, foot)) : 0.0f); + /* How steady it will stand: the angle (75 deg is best: flatter its foot slides, + steeper it tips back), soft or muddy ground, its feet not level, what holds it + (a bush, something light that gives), resting on an edge or only against a face */ + float deg = lean * 180.0f / PI_F, off = deg < LEAN_ANGLE ? (LEAN_ANGLE - deg) / 15.0f : (deg - LEAN_ANGLE) / 9.0f; + float st = 1.0f - 0.7f * clampf(off, 0.0f, 1.0f); + st *= 1.0f - 0.6f * clampf(Wetness_mud(&world->terrain.wet, foot.x, foot.z), 0.0f, 1.0f); + float feet_off = 0.0f; + for (int s2 = -1; s2 <= 1; s2 += 2) { + Vec3 p = vec3_add(foot, vec3_scale(across, (float)s2 * LADDER_WIDTH * 0.5f)); + feet_off = fmaxf(feet_off, fabsf(World_groundHeight(world, p.x, p.z, foot.y + 0.3f) - foot.y)); + } + st *= 1.0f - 0.6f * clampf(feet_off / 0.12f, 0.0f, 1.0f); + if (thing->type == OBJ_BUSH) st *= 0.2f; + else if (!thing->is_static && thing->weight < HOLDS_WEIGHT) st *= clampf(thing->weight / HOLDS_WEIGHT, 0.1f, 1.0f); + if (!reaches) st *= 0.85f; + l->stability = clampf(st, 0.0f, 1.0f); return LEAN_DONE; } @@ -429,6 +536,8 @@ Object *Ladder_near(const World *world, const Object *who, bool *at_top) { bool Ladder_take(Object *who, Object *l) { int hand = Hands_freeHand(who); if (hand < 0 || !l->leaning) return false; + if (l->held_by) l->held_by->holding_ladder = NULL; + l->held_by = NULL; l->leaning = false; l->is_static = false; l->lean_top = 0.0f; @@ -503,7 +612,7 @@ int Ladder_climb(Object *who, float input, float dt) { who->body.velocity = vec3_zero(); /* (the feet are down there already: they stay) */ return CLIMB_OFF_BOTTOM; } - who->ladder_at += input * CLIMB_SPEED * Object_vigor(who) * dt; + who->ladder_at += input * CLIMB_SPEED * Object_vigor(who) * (0.7f + 0.6f * who->fitness) * dt; /* (the sporty quicker) */ { /* just got on: the body eases over onto it */ float left = vec3_length(who->ladder_shift), step = LADDER_ONTO * dt; who->ladder_shift = left > step ? vec3_scale(who->ladder_shift, (left - step) / left) : vec3_zero(); @@ -560,8 +669,27 @@ static Vec3 limbTo(Object *who, int k, Vec3 goal, float time, float dt, float *o #define HAND_TO_RUNG 0.3f /* seconds a hand takes to its next rung */ #define FOOT_TO_RUNG 0.34f /* ... and a foot */ +/* Holding it: at the spot behind its foot, facing it. */ +static bool atHold(const Object *who) { + const Object *l = who->holding_ladder; + Vec3 facing; + return l && l->held_by == who && !l->placing && l->tip_v <= 0.0f && flatDist(who->position, Ladder_holdSpot(l, &facing)) < 0.5f; +} + void Ladder_poseHands(Object *who, float dt) { const Object *l = who->on_ladder; + if (!l && atHold(who)) { /* holding it steady: both hands on the rails at chest height */ + const Object *hl = who->holding_ladder; + Vec3 across = vec3_normalize(vec3_cross(hl->lean_normal, UP)); + float s = who->look.height / 1.75f; + for (int h = 0; h < 2; h++) { + float side = h == HAND_LEFT ? 1.0f : -1.0f; + Vec3 at = pointAt(hl, 1.15f * s); + who->body.hand_active[h] = true; + who->body.hand_target[h] = vec3_add(at, vec3_add(vec3_scale(across, side * LADDER_WIDTH * 0.5f), vec3_scale(hl->lean_normal, 0.03f))); + } + return; + } if (!l) return; const float s = who->look.height / 1.75f; Vec3 n = l->lean_normal, across = vec3_normalize(vec3_cross(n, UP)); @@ -575,7 +703,7 @@ void Ladder_poseHands(Object *who, float dt) { float side = h == HAND_LEFT ? 1.0f : -1.0f; /* facing the wall, its left is along +across */ Vec3 goal = vec3_add(pointAt(l, rung(l, kh)), vec3_add(vec3_scale(across, side * 0.17f), vec3_scale(n, 0.04f))); float away; - Vec3 at = limbTo(who, 2 + h, goal, HAND_TO_RUNG / fmaxf(Object_vigor(who), 0.5f), dt, &away); + Vec3 at = limbTo(who, 2 + h, goal, HAND_TO_RUNG / fmaxf(Object_vigor(who) * (0.7f + 0.6f * who->fitness), 0.5f), dt, &away); who->body.hand_active[h] = true; who->body.hand_target[h] = vec3_add(at, vec3_scale(n, 0.08f * away * s)); } @@ -585,6 +713,19 @@ void Ladder_poseHands(Object *who, float dt) { void Ladder_poseFeet(Object *who, float dt) { const Object *l = who->on_ladder; + if (!l && atHold(who) && who->body.pose == POSE_UP) { /* holding it: a foot on the bottom rung */ + const Object *hl = who->holding_ladder; + float s = who->look.height / 1.75f; + Foot *foot = &who->body.feet[HAND_RIGHT]; + Vec3 across = vec3_normalize(vec3_cross(hl->lean_normal, UP)); + Vec3 goal = vec3_add(pointAt(hl, rung(hl, 0)), vec3_add(vec3_scale(hl->lean_normal, 0.06f * s), vec3_scale(across, -0.06f * s))); + goal.y += 0.06f * s; /* (ankle over the rung) */ + float k = fminf(1.0f, 6.0f * dt); + foot->ankle = vec3_lerp(foot->ankle, goal, k); + foot->dir = vec3_scale(hl->lean_normal, -1.0f); + foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(foot->dir, 0.14f * s), vec3(0.0f, -0.05f * s, 0.0f))); + return; + } if (!l) return; const float s = who->look.height / 1.75f; Vec3 n = l->lean_normal, across = vec3_normalize(vec3_cross(n, UP)); @@ -615,7 +756,7 @@ void Ladder_poseFeet(Object *who, float dt) { vec3_add(vec3_scale(n, 0.07f), vec3(0.0f, 0.075f * s, 0.0f)))); /* from rung to rung: lifted off, out a little (the toes clear the rungs) and set down */ float away; - Vec3 at = limbTo(who, i, goal, FOOT_TO_RUNG / fmaxf(Object_vigor(who), 0.5f), dt, &away); + Vec3 at = limbTo(who, i, goal, FOOT_TO_RUNG / fmaxf(Object_vigor(who) * (0.7f + 0.6f * who->fitness), 0.5f), dt, &away); foot->ankle = vec3_add(at, vec3_scale(n, 0.1f * away * s)); foot->dir = vec3_scale(n, -1.0f); foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(n, -0.14f * s), vec3(0.0f, -0.05f * (1.0f + away) * s, 0.0f))); diff --git a/entropie/src/world/ladder.h b/entropie/src/world/ladder.h index d0d429e..5913ae6 100644 --- a/entropie/src/world/ladder.h +++ b/entropie/src/world/ladder.h @@ -9,7 +9,9 @@ * (a wall, the car, a tree, not a bush), high enough, with level ground and * room for its feet. The top rests on the edge (the gutter of a roof); too * short for that, it leans against the face and you can't get off at the top. - * A leaning ladder stands still; people climb it rung by rung. + * A leaning ladder stands still; people climb it rung by rung. How steady it + * stands (angle, ground, what it leans on) decides if it slips when climbed; + * somebody holding it keeps it steady. Stood up on its own it falls over. */ #include "world/world.h" @@ -43,7 +45,17 @@ void Ladder_getOff(Object *who); /* Where the top rests (on the edge), and the way over it (horizontal, away from the ladder). */ Vec3 Ladder_edge(const Object *ladder); -/* Climbing: hands on the rungs (after Hands_update) and feet on them (after Body_update). */ +/* How steady it stands now (0..1): its own stability, or 1 with somebody holding it. */ +float Ladder_steadiness(const Object *ladder); +/* It falls over (away the way dir points, horizontal); anybody on it comes off. */ +void Ladder_topple(World *world, Object *ladder, Vec3 dir); +/* Holding it steady at its foot (both hands on the rails, a foot on the bottom rung), or not (NULL). */ +void Ladder_hold(Object *who, Object *ladder); +/* Where somebody stands to hold it (behind its foot), and which way they face. */ +Vec3 Ladder_holdSpot(const Object *ladder, Vec3 *facing); + +/* Climbing: hands on the rungs (after Hands_update) and feet on them (after Body_update); + holding it: hands on the rails, a foot on the bottom rung. */ void Ladder_poseHands(Object *who, float dt); void Ladder_poseFeet(Object *who, float dt); /* Slid out and in hand: held near its foot, reaching up in front (steeper looking up). */ diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index be758df..51ddfea 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -236,6 +236,12 @@ typedef struct Object { float ladder_skill; /* characters: practice climbing ladders one-handed (0..1) ... */ float ladder_wobble; /* ... nearly slipped: the next slip is a fall (seconds this counts) */ Vec3 lean_normal; /* ladders: away from what it leans on */ + bool spot_checked; /* wood: somebody looked whether there's room to work on it (once) */ + float stability; /* ladders: how steady it stands, leaning (0..1; somebody holding it: steady) */ + struct Object *held_by; /* ladders: somebody holding it steady at its foot ... */ + struct Object *holding_ladder; /* characters: ... this one */ + float tip, tip_v; /* ladders: falling over: tipped this far from where it stood (rad), this fast ... */ + Vec3 tip_from, tip_dir; /* ... from that axis, towards that (horizontal) way */ float lean_top; /* ladders: where along it the edge is (0 = against a face, no getting off) */ float placing; /* ladders: being put up against something: 0 = not, else how far (0..1) ... */ Vec3 place_foot[2], place_axis[2], place_face[2]; /* ... from (carried) to (leaning) */