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.86.0: onto the log, and the ladder on the beam
- Something solid that holds you (a log, a fallen tree, a heavy crate) you
step onto and stand on, instead of straddling it; walking at the end of
something long, you go round its end
- Fix: the ladder couldn't be leaned on a beam of the unfinished building
(aiming missed the thin beam; stood up free it fell through it). Aiming
is forgiving now, and a falling ladder rests on what it falls against
- A ladder held up tips down flat to get under a beam or doorway and goes
up again after (before, you got stuck)
Includes 1.85.0 (if not committed separately):
- Somebody standing in front of a pushed load leans against it (it goes
slower) and side-steps out of its way; the slowest just complain
- Fix: a person a pushed crate ran into got bulldozed along with it (their
feet stalled against it)
Includes 1.84.0 (if not committed separately):
- A helper standing where the pushed or pulled load wants to go notices it,
lets go and comes round to another side; quick-witted ones soon, slow
ones (Ben) not at all ("Why won't it move?")
- New trait: wits (0 slow .. 1 sharp), also in the dev menu
Includes 1.83.0 (if not committed separately):
- Peeing standing: one hand holds it (the Urinella to the body), the other
stays at the belt
- A full ashtray (12 butts) gets emptied by somebody tidy: into a scrap of
paper, off to the bin
- Fix: a tidy person could stand still for minutes, reaching again and
again for trash just out of reach
Includes 1.82.0 (if not committed separately):
- Walking styles: feet wider or narrower, toes out more or less, bouncier
or flatter (livelier when lively), hips swaying more or less
- The ashtray stands on the cool box by the fire (on the ground it was in
everybody's way)
Includes 1.81.0 (if not committed separately):
- Fix: following someone on a chat break (and walking up to things) went
walk-stop-walk every frame, the whole body shook; now they slow down
coming up and stay with them
- Fix: an elbow could flip over when a hand went up past the shoulder
(scratching the head); it turns round the arm now
Includes 1.80.0 (if not committed separately):
- An old tin by the fire as ashtray: you and the tidy put butts in there
- The Urinella is drawn while it's used; the dress is lifted higher
- After peeing you take up again what you put down
- The others can slip off a wobbly ladder too
Includes 1.79.0 (if not committed separately):
- Helpers (G) follow you up a leaning ladder onto a roof or wall and down
again; without a ladder they pull themselves up after you (if they reach)
and jump down when you're down
- Pulling up is a module for anybody now (people/climb.c), not only you
Includes 1.78.0 (if not committed separately):
- Cold nights: away from the fire a jacket goes on (each their own colour)
- Working hard in the midday sun your shirt comes off too
- Women who never tried a Urinella may try one: some like it, some don't
- Dying for a smoke makes restless; a wobbling ladder rocks
- Moving heavy wood: tested with a called helper; the player is asked when
nobody else is around
Includes 1.77.0 (if not committed separately):
- 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 edgesentropie/Makefile | 2 +- entropie/README.md | 10 +++++- entropie/STATE.md | 10 ++++-- entropie/commit_message.txt | 20 ++++++++++-- entropie/src/game/game.c | 8 +++++ entropie/src/people/ai.c | 36 ++++++++++++++++++++++ entropie/src/people/ai.h | 3 ++ entropie/src/people/hands.c | 37 ++++++++++++++++++++++ entropie/src/world/ladder.c | 73 +++++++++++++++++++++++++++++++++++++++----- entropie/src/world/ladder.h | 2 +- entropie/src/world/object.h | 4 +++ entropie/src/world/physics.c | 72 ++++++++++++++++++++++++++++++++++++++----- entropie/src/world/world.c | 25 ++++++++++++++- 13 files changed, 279 insertions(+), 23 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 5eb61c0..57f525c 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.84.0 +VERSION := 1.86.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 8e29525..02e41b3 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -150,6 +150,9 @@ and satellite pictures (about 340 x 340 m). stepping over a fence) or the foot comes up; sprinting, you leap - Stepping over, you lean into it and the leg swings round a little, out to the side and back in to land, while the momentum carries you on +- Something solid that holds you (a log, a fallen tree, a heavy crate, from + about knee-low up) you step onto instead: you stand up there and walk on +- Walking at the end of something long, you go round the end instead - The sporty fit their steps to what's coming, so a foot lands at a good take-off spot - Jogging or faster, low things (up to about the knee) are jumped on their @@ -227,6 +230,8 @@ and satellite pictures (about 340 x 340 m). at what you push and pushes along. Press again for more helpers, H sends them off - A helper standing where the load wants to go notices it, lets go and comes round to another side (the quick-witted soon, the slow never) +- Anybody else in the way leans against the load (it goes slower) and side-steps + out of it; the slowest just say "Hey! Watch it!" - Not sure what you're up to, helpers copy you: they take something like what you carry, walk along at your side and do the same with it (put it down next to yours, into the fire, into the bin) @@ -354,7 +359,10 @@ A hand that can't reach its grip lets go and takes hold again. 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 + own - and it falls over, or against something (a beam): there it rests +- A thin beam counts even if you look just past it +- Held up, it runs into a beam or a doorway: you tip it down flat, carry it + under and it goes up again once there's room - 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 diff --git a/entropie/STATE.md b/entropie/STATE.md index 55341dd..04dc9bd 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -69,6 +69,12 @@ Shadows: 3x3 PCF with each tap blended bilinearly between its 4 texels (`common. Making room (`ai_help.c` makeRoom, every frame in AI_HELP): the leader wants to go (`movement` 0.2+ m/s) and the helper stands in the load's way (`inLoadsWay`: in front of it along that direction, within its width + 0.35 m). After 0.8 s + 2.5 x (1 - wits) the load's progress is checked (from `jam_from`): holding on and it goes 35 %+ of the speed: fine; else (or not holding on: shoved along) wits under 0.35 (minus 0.6 x drunk): "Why won't it move?" and nothing; else let go, "Wait - I'm in the way", and for 6 s (`clear_time`, `clear_dir`) spots in the load's way are no good spots. `wits` (object.h, 0.5 by default; table in scene.c; dev menu row). +Bystanders (`hands.c` inTheWay, in Hands_solveGrabs): people not holding a pushed load that it would go into (moved a step ahead) count against it with their weight (BYSTANDER_HOLD 1.0) and get `shoved` (the way it goes, `shove_load` its middle; cleared every frame). `ai.c` stepAside (not helpers, not sitting): after 0.25 + 1.5 x (1 - wits) s a quick side step to the side they're already off to (0.8 s at 1.6 m/s, no turning first); wits under 0.2: "Hey! Watch it!" and 4 s not minding it. While shoved, a standing leg against something doesn't hold the body back (`physics.c` steppingOver: their feet stalled against the load before, they got bulldozed along). + +Onto, not over (`physics.c`): `standsOn` (static or ONTO_HOLDS 40 kg+, a side facing straight up - boxes, logs and fallen trees lying; not bottles, furniture, bins, supplies, ladders, the fire): the player steps up onto it (startStepOver `up`, STAND_UP_TIME 0.4 s + 0.9 s/m) when it's STAND_LOWEST 0.22+ high with STAND_ROOM 0.28 m on top, instead of straddling it; lands in its middle. A lying tree (toppled, topple 1) is no longer neverOver. `pastTheEnd` (all characters): something 1.4 m+ long across your way that you'd only catch with up to 0.55 m of you, within 1.6 m: the walk bends past its end. NPCs still step over (and round) as before. + +Ladders and things in the way (`ladder.c`): aiming with the view also tries 0.05 / 0.1 rad above and below (thin beams); hitting a beam from below takes the side towards you. Falling over (`fallOver`, now with the world: `Ladder_update(world, l, dt)`) it stops against anything solid it goes into (`caught`: leaning there, CAUGHT_STEADY 0.45, `lean_top` if its top is over the thing; `just_caught` -> the game says so). Carried held up (`world.c` dipLadder): running into something -> `dipped` (raise goal RAISE_DIP 0.12, nearly flat); down and clear, up again once it would stand up at 1.36 without going into anything. + Climbing for anybody (`people/climb.c`, moved out of the game): `Climb_ledgeAhead`, `Climb_start` (says why not: hands, strength, drunk), `Climb_update` (the pull up, its feet and hands), `Climb_overLadderTop`; the move lives in the person (`climb_move`). Helpers (`ai_help.c` ai_followByLadder): the leader 1.2 m higher (standing, not climbing): a leaning ladder within 25 m from their level to the leader's (top within 1.3 m) - to its foot, up (`ai_climbing`: on a ladder or pulling over its top this runs first, whatever the mode), over the edge; none, and the leader within reach (CLIMB_REACH): walk at it and pull up where the edge ahead is at the leader's level ("Hup!"). The leader 1.2 m lower: to the top of a ladder from up there and down it; none: they just walk off (the landing does the rest). A ladder leading nowhere (no top to get off at): down again after 8 s. `Ai_useWorld` gives the AI the world for this. Walking styles (`Body.style[4]`, -1..1 per person, made from the quirks - no extra dice, so nothing else changes): feet apart x (1 + 0.15 style0), toes out + 0.04 rad x style1, the bob x (1 + 0.35 style2 + 0.25 lively), the hips swaying over the standing foot x (1 + 0.35 style3). (The audit swings both ways with it - it changes where everybody goes; overall the player's numbers are lower.) @@ -187,7 +193,7 @@ Most important first. - Joint jumps left in the audit: jog+stop 171, sprint 297 (worst 26 cm) - running into things: a hurried catch step swings through in a few frames (step 0.07 -> 0.34, the ankle up 20 cm). Capping the swing speed helped sprint (278) but made jumps, drunk walk and the NPCs worse - tried and reverted in 1.83; ladder 45: toes on the sloping roof (a landing step's toes snap onto the slope, 16 cm; blending them in made all walking worse), toes turning onto the rungs getting on from the top (14 cm) **Body and moving** -- Obstacle approach, still to do: prefer a step in between on top of something stable (or unstable, if clumsy) over a long stride; NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things +- Obstacle approach, still to do: NPCs stepping onto solid things too (now only you); balancing on a log (arms out, a wobble); a step in between on top of something unstable (if clumsy); NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things - Climbing rework: hands first (one reaches first, shoulder ahead), a knee up, the other foot next to it; or sit on it and lift the legs; or belly first and roll; by height, surface and person - Balance: hips over the feet, look for stable footing, play with balance - Personal movement styles, still to do: gesture variants; stepping over and climbing their own way; sporty ones more elegant (walking styles are in) @@ -207,4 +213,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.84.0 (build 0f5b1e3)` +State of `entropie 1.86.0 (build 92de65a)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 1c8e7a3..9888792 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,5 +1,21 @@ -entropie 1.84.0: helpers get out of the way - +entropie 1.86.0: onto the log, and the ladder on the beam + +- Something solid that holds you (a log, a fallen tree, a heavy crate) you + step onto and stand on, instead of straddling it; walking at the end of + something long, you go round its end +- Fix: the ladder couldn't be leaned on a beam of the unfinished building + (aiming missed the thin beam; stood up free it fell through it). Aiming + is forgiving now, and a falling ladder rests on what it falls against +- A ladder held up tips down flat to get under a beam or doorway and goes + up again after (before, you got stuck) + +Includes 1.85.0 (if not committed separately): +- Somebody standing in front of a pushed load leans against it (it goes + slower) and side-steps out of its way; the slowest just complain +- Fix: a person a pushed crate ran into got bulldozed along with it (their + feet stalled against it) + +Includes 1.84.0 (if not committed separately): - A helper standing where the pushed or pulled load wants to go notices it, lets go and comes round to another side; quick-witted ones soon, slow ones (Ben) not at all ("Why won't it move?") diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index d330724..e608a3e 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -249,6 +249,14 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game_climbLadder(game, input, dt); game_climb(game, input, dt); World_update(&game->world, dt, dt * game->daycycle.time_scale * speed); + for (int i = 0; i < game->world.count; i++) { /* a ladder that fell against something near you */ + Object *l = game->world.objects[i]; + if (l->type != OBJ_LADDER || !l->just_caught) continue; + l->just_caught = false; + if (vec3_length(vec3_sub(l->position, game->player->position)) < 15.0f) + say(game, l->lean_top > 0.0f ? "It falls against it and rests there - not too steady (G: somebody holds it)" + : "It falls against it and stays leaning there - not too steady"); + } if (game->player->carry_blocked) { /* what you carry hit something: the view doesn't turn on either */ game->camera.yaw = (game->player->rotation.y - PI_F / 2.0f) * 180.0f / PI_F; Camera_rotate(&game->camera, 0.0f, 0.0f); diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index b99f9ee..54540f3 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1248,6 +1248,41 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt } } +/* A pushed load shoving us along: out of its way to one side, after a moment + to see what's going on (the slower, the longer; the slowest just get shoved). + Helpers sort it out their own way (ai_help.c makeRoom). */ +#define DODGE_TIME 0.8f +#define DODGE_SPEED 1.6f /* m/s */ + +static bool stepAside(Object *self, float dt) { + Ai *ai = &self->ai; + if (ai->dodge > 0.0f) { + ai->dodge -= dt; /* (a quick side step, no turning round first) */ + self->movement = vec3_scale(ai->dodge_dir, DODGE_SPEED * Object_vigor(self)); + Vec3 f = Object_facing(self), r = vec3_normalize(vec3_cross(f, UP)); + self->move_local = vec3(vec3_dot(ai->dodge_dir, r), 0.0f, vec3_dot(ai->dodge_dir, f)); + ai->walk_dir = vec3_zero(); + return true; + } + if (ai->shove_time < 0.0f) { ai->shove_time = fminf(0.0f, ai->shove_time + dt); return false; } + if (vec3_length(self->shoved) < 0.5f || ai->mode == AI_HELP || self->sitting) { ai->shove_time = 0.0f; return false; } + float wits = clampf(self->wits - 0.6f * fminf(self->drunk, 1.0f), 0.0f, 1.0f); + if ((ai->shove_time += dt) < 0.25f + 1.5f * (1.0f - wits)) return false; + if (wits < 0.2f) { /* doesn't get it: stands against it */ + Ai_say(self, "Hey! Watch it!"); + ai->shove_time = -4.0f; + return false; + } + Vec3 across = vec3_cross(self->shoved, UP); + float off = vec3_dot(vec3_sub(self->position, self->shove_load), across); /* (off to the side it's nearer) */ + float side = fabsf(off) > 0.05f ? (off > 0.0f ? 1.0f : -1.0f) : (random01(ai) < 0.5f ? -1.0f : 1.0f); + ai->dodge_dir = vec3_scale(across, side); + ai->dodge = DODGE_TIME; + ai->shove_time = 0.0f; + if (random01(ai) < 0.6f) Ai_say(self, random01(ai) < 0.5f ? "Whoa - careful!" : "Oops, I'm in the way."); + return true; +} + void Ai_update(Object *self, Object *const *objects, int count, float dt) { ai_ensureReady(self); Ai *ai = &self->ai; @@ -1283,6 +1318,7 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { lighterManners(self, objects, count, dt); smokeBreak(self, objects, count, dt); if (ai_climbing(self, dt)) return; /* (up or down a ladder, over an edge: that first) */ + if (stepAside(self, dt)) return; /* (a pushed load coming at us) */ if (ai->mode != AI_IDLE && !(ai->mode == AI_WORK && ai->order == ORDER_FIRE)) self->sitting = false; /* up again to do anything */ smell(self, objects, count, dt); if (ai->talking) { diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index 057f858..008c50a 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -148,6 +148,9 @@ typedef struct { float clear_time; /* ... so we keep off that side a while */ Vec3 clear_dir; /* ... (the way the load wants to go) */ Vec3 jam_from; /* (where the load was when we started to wonder) */ + float shove_time; /* a pushed load has been shoving us this long (negative: a while not minding it) */ + float dodge; /* stepping out of its way (seconds left) ... */ + Vec3 dodge_dir; /* ... this way */ /* Talking */ Topic topic; /* AI_APPROACH: what they want to talk about */ diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index dbb66a6..6a13fdc 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -759,7 +759,37 @@ static float levelLift(Object *o, Object *const *team, int n, float dt) { return base - o->colliders[0].half_extents.x; /* its underside at the middle */ } +/* People (not holding it) the load would go into, moved by move: their + weight (they stand against it), and each gets told it shoves them. */ +#define BYSTANDER_HOLD 1.0f /* x their weight against the load */ + +static float inTheWay(Object *o, Object *const *team, int n, Object *const *objects, int count, Vec3 move) { + Vec3 dir = vec3(move.x, 0.0f, move.z); + float len = vec3_length(dir); + if (len < 1e-5f) return 0.0f; + dir = vec3_scale(dir, 1.0f / len); + Vec3 was = o->position; + o->position = vec3_add(was, vec3_scale(dir, fmaxf(len, 0.02f))); /* (a little ahead) */ + Object_syncColliders(o); + float weight = 0.0f; + for (int k = 0; k < count; k++) { + Object *c = objects[k]; + if (!c->is_character || c->hands.grabbed == o || c->on_ladder || c->climbing) continue; + bool mine = false; + for (int t = 0; t < n; t++) mine = mine || team[t] == c; + if (mine || fabsf(c->position.x - was.x) > 4.0f || fabsf(c->position.z - was.z) > 4.0f) continue; + if (vec3_dot(vec3_sub(c->position, was), dir) <= 0.0f || Physics_penetration(o, c) <= 0.0f) continue; + weight += c->weight; + c->shoved = dir; + c->shove_load = was; + } + o->position = was; + Object_syncColliders(o); + return weight; +} + void Hands_solveGrabs(Object *const *objects, int count, float dt) { + for (int i = 0; i < count; i++) objects[i]->shoved = vec3_zero(); /* (told again below while it goes on) */ for (int i = 0; i < count; i++) { Object *o = objects[i]; if (o->is_static || o->is_character) continue; @@ -797,6 +827,13 @@ void Hands_solveGrabs(Object *const *objects, int count, float dt) { strength allows; whatever it doesn't follow holds each of them back. */ float share = o->end_up ? END_DRAG : o->lifted ? 1.0f : teamShare(capacity, o->weight); Vec3 move = vec3_scale(sum, share / (float)n); + if (!o->lifted && !o->end_up && share > 0.0f) { /* somebody in the way: they lean against it (and get told) */ + float in_way = inTheWay(o, team, n, objects, count, move); + if (in_way > 0.0f) { + share = teamShare(capacity, o->weight + BYSTANDER_HOLD * in_way); + move = vec3_scale(sum, share / (float)n); + } + } if (o->end_up) { move.y = 0.0f; } else if (o->lifted) { diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c index 51a3b8c..1d52e0b 100644 --- a/entropie/src/world/ladder.c +++ b/entropie/src/world/ladder.c @@ -22,6 +22,7 @@ #define RAISE_LOW 1.15f #define RAISE_HIGH 1.52f #define RAISE_SPEED 1.6f /* rad/s it tips up or down */ +#define RAISE_DIP 0.12f /* tipped down to get under something: nearly flat */ #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) */ #define AIM_VIEW 1.5f /* looking at it: this much further than the ladder is long */ @@ -122,8 +123,55 @@ static void putUp(Object *l, float dt) { } } +static float topOf(const Object *o, float x, float z, float from_y); + +/* How deep the ladder is in solid things (not people, the dog, what's carried), and the deepest one. */ +static float solidIn(const World *world, const Object *l, Object **deepest) { + float most = 0.0f; + if (deepest) *deepest = NULL; + for (int i = 0; world && i < world->count; i++) { + Object *o = world->objects[i]; + if (o == l || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DOG || o->type == OBJ_DECAL || + o->type == OBJ_PUDDLE || o->type == OBJ_STINK || o->type == OBJ_BUSH) continue; + if (!o->is_static && o->weight < HOLDS_WEIGHT) continue; /* (light things it just knocks aside) */ + if (flatDist(o->position, l->position) > 30.0f && !(o->is_static && vec3_length(o->colliders[0].half_extents) > 4.0f)) continue; + float d = Physics_penetration(l, o); + if (d > most) { most = d; if (deepest) *deepest = o; } + } + return most; +} + +/* Falling against something on the way down (a beam, a wall, a tree): it stops + there and leans on it - not as steady as one put up properly. */ +#define CAUGHT_STEADY 0.45f + +static bool caught(World *world, Object *l, Vec3 axis_was, Vec3 face_was, float before) { + Object *thing; + if (solidIn(world, l, &thing) <= before + 0.01f || !thing) return false; + l->rotation = euler_yxz_from_axes(axis_was, face_was); + Object_syncColliders(l); + l->tip = l->tip_v = 0.0f; + l->leaning = true; + l->lean_normal = vec3_normalize(vec3_scale(l->tip_dir, -1.0f)); + l->place_axis[1] = axis_was; + l->place_face[1] = face_was; + /* its top over what caught it (far enough past it to get off there)? */ + float top = -1.0f; + for (float along = lengthOf(l) - 0.1f; along > 0.5f; along -= 0.1f) { + Vec3 p = pointAt(l, along); + float t = topOf(thing, p.x, p.z, p.y + 0.5f); + if (t > -1e8f && t > p.y - 0.25f) { top = along; break; } + } + l->lean_top = top > 0.0f && lengthOf(l) - top > 0.3f ? top : 0.0f; + l->stability = CAUGHT_STEADY; + l->just_caught = true; + return true; +} + /* Falling over about its foot, faster and faster, then it lies there. */ -static void fallOver(Object *l, float dt) { +static void fallOver(World *world, Object *l, float dt) { + Vec3 axis_was = upAlong(l), face_was = l->colliders[0].axes[2]; + float before = world ? solidIn(world, l, NULL) : 0.0f; 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; @@ -147,6 +195,7 @@ static void fallOver(Object *l, float dt) { 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); + if (world) caught(world, l, axis_was, face_was, before); } void Ladder_topple(World *world, Object *l, Vec3 dir) { @@ -187,9 +236,9 @@ Vec3 Ladder_holdSpot(const Object *l, Vec3 *facing) { return at; } -void Ladder_update(Object *l, float dt) { +void Ladder_update(World *world, 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->tip_v > 0.0f) { fallOver(world, l, dt); return; } if (l->shake > 0.0f && l->leaning) { /* wobbling: the top rocks from side to side, dying away */ l->shake = fmaxf(0.0f, l->shake - dt); Vec3 axis = l->place_axis[1], across = vec3_normalize(vec3_cross(l->lean_normal, UP)); @@ -296,9 +345,16 @@ static Object *rayHit(const World *world, const Object *who, const Object *ladde Its top face: the side of it towards you, just under that edge. */ static Object *aimedAt(const World *world, const Object *who, const Object *l, Vec3 *face, Vec3 *n, float *aim_y) { Object *thing = NULL; - if (who->type == OBJ_PLAYER && vec3_length(who->body.look_dir) > 0.5f) { /* where you look */ + if (who->type == OBJ_PLAYER && vec3_length(who->body.look_dir) > 0.5f) { /* where you look (a thin beam: a little above or below it does too) */ Vec3 eye = vec3_add(who->position, vec3(0.0f, 0.93f * who->look.height, 0.0f)); - thing = rayHit(world, who, l, eye, vec3_normalize(who->body.look_dir), lengthOf(l) + AIM_VIEW, face, n); + Vec3 look = vec3_normalize(who->body.look_dir), level = flatv(look); + float pitch = asinf(clampf(look.y, -1.0f, 1.0f)); + static const float NEAR_MISS[] = { 0.0f, 0.05f, -0.05f, 0.1f, -0.1f }; + for (int k = 0; k < 5 && !thing && vec3_length(level) > 1e-3f; k++) { + float a = pitch + NEAR_MISS[k]; + Vec3 dir = vec3_add(vec3_scale(vec3_normalize(level), cosf(a)), vec3_scale(UP, sinf(a))); + thing = rayHit(world, who, l, eye, dir, lengthOf(l) + AIM_VIEW, face, n); + } } if (!thing) { /* else where its top points */ Vec3 axis = upAlong(l), from = pointAt(l, RAISE_GRIP); @@ -306,11 +362,11 @@ static Object *aimedAt(const World *world, const Object *who, const Object *l, V } if (!thing) return NULL; *aim_y = face->y; - if (n->y > 0.5f) { /* on top of it: the edge towards you */ + if (fabsf(n->y) > 0.5f) { /* on top of it (or under it: a beam from below): the side towards you */ Vec3 to = flatv(vec3_sub(*face, who->position)); if (vec3_length(to) < 0.05f) return NULL; to = vec3_normalize(to); - Vec3 o = vec3(who->position.x, face->y - 0.08f, who->position.z); + Vec3 o = vec3(who->position.x, face->y + (n->y > 0.0f ? -0.08f : 0.08f), who->position.z); Vec3 p, nn; if (rayHit(world, who, l, o, to, LEAN_REACH + 4.0f, &p, &nn) != thing) return NULL; *face = p; @@ -794,7 +850,7 @@ void Ladder_placeHeld(Object *who, int hand, Object *l) { Vec3 right = vec3_normalize(vec3_cross(facing, UP)); int jh = hand == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R; if (Ladder_isRaised(l)) { /* slid out: held near its foot, reaching up into the air in front */ - l->raise_goal = raiseGoal(who); /* (eased in Ladder_update) */ + l->raise_goal = l->dipped ? RAISE_DIP : raiseGoal(who); /* (eased in Ladder_update) */ if (l->raised <= 0.0f) l->raised = 0.15f; Vec3 axis = vec3_normalize(vec3_add(vec3_scale(facing, cosf(l->raised)), vec3_scale(UP, sinf(l->raised)))); Vec3 z = vec3_normalize(vec3_cross(right, axis)); /* (rungs across, the rails side by side) */ @@ -808,6 +864,7 @@ void Ladder_placeHeld(Object *who, int hand, Object *l) { return; } l->raised = 0.0f; + l->dipped = false; /* on edge, along the way you walk: local Y forward, the rungs upright */ l->rotation = euler_yxz_from_axes(facing, vec3_scale(right, -1.0f)); Object_syncColliders(l); diff --git a/entropie/src/world/ladder.h b/entropie/src/world/ladder.h index 5913ae6..4a07f5b 100644 --- a/entropie/src/world/ladder.h +++ b/entropie/src/world/ladder.h @@ -21,7 +21,7 @@ void Ladder_slide(Object *ladder); /* Slid out this far (0..1) right away: mesh and collider too. */ void Ladder_setExtend(Object *ladder, float extend); /* Eases the sliding along, rebuilding mesh and collider as it goes. */ -void Ladder_update(Object *ladder, float dt); +void Ladder_update(World *world, Object *ladder, float dt); /* (world: what it falls against; may be NULL) */ #define LADDER_FOLDING 1 /* variant: two sections on a hinge (unfolds); 0: telescopic (slides out) */ #define LADDER_STEP 2 /* variant: a step ladder (opens and stands on its own) */ diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index aaffa42..39c69d2 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -158,6 +158,8 @@ typedef struct Object { bool lifted; /* carried by a team: no gravity, moved by their hands */ bool end_up; /* ... only one end lifted (long and too heavy for them): the other drags on the ground */ Vec3 knock; /* characters: momentum of things that hit them this step */ + Vec3 shoved; /* characters: a pushed load is shoving them this way (zero: not) ... */ + Vec3 shove_load; /* ... the load's middle */ bool toppled; /* tipped over (bottles) */ float topple; /* 0..1 while falling over */ struct Object *thrown_by; /* set while flying after a throw */ @@ -243,6 +245,8 @@ typedef struct Object { Vec3 pee_hit; /* characters: where the stream comes down */ float extend, extend_goal; /* ladders: 0 = slid together .. 1 = all the way out */ float raised, raise_goal; /* ladders carried slid out: held near the foot, this steep up in front (radians, 0 = carried at the side) */ + bool dipped; /* ... tipped down flat to get under something (a beam, a doorway) */ + bool just_caught; /* ladders: fell against something just now and rests there (the game says so) */ bool leaning; /* ladders: leaning against something (stands still) */ float air_fall; /* characters: in the air, falling this fast at most (m/s; for the landing) */ float ladder_skill; /* characters: practice climbing ladders one-handed (0..1) ... */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index 9d4c26a..28a5151 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -52,6 +52,10 @@ #define STEP_UP_MOST 0.75f /* walking up onto something this high at most (scaled; higher: climb) ... */ #define STEP_UP_ROOM 0.45f /* ... with this much room on top ... */ #define STEP_UP_IN 0.2f /* ... the first foot this far in from its edge */ +#define STAND_LOWEST 0.22f /* solid things this high (scaled) you step onto rather than over ... */ +#define STAND_ROOM 0.28f /* ... a foot's width on top will do (a log: balancing) ... */ +#define STAND_UP_TIME 0.4f /* ... one step up taking this long ... */ +#define STAND_UP_HIGH 0.9f /* ... and this much more per meter high */ #define ONTO_HIGHEST 0.95f /* up onto things this high at most (scaled) ... */ #define ONTO_ROOM 0.4f /* ... with this much room on top to land ... */ #define ONTO_HOLDS 40.0f /* ... (loose: this heavy, else it would go over) ... */ @@ -91,9 +95,11 @@ static float halfAcross(const Collider *c) { the fire; a ladder only lying on the ground. */ static bool neverOver(const Object *o) { switch (o->type) { - case OBJ_HOUSE: case OBJ_BARN: case OBJ_TREE: case OBJ_BUSH: case OBJ_FIREPLACE: case OBJ_DOG: + 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: return true; + case OBJ_TREE: + return !(o->toppled && o->topple >= 1.0f); /* (one lying on the ground: over it, or onto it) */ case OBJ_LADDER: return o->leaning; default: @@ -101,6 +107,22 @@ static bool neverOver(const Object *o) { } } +/* Solid enough to stand on, and won't break under you: heavy or fixed, its + top flat (a box or a log lying: one of its sides faces straight up), not a + bottle, a table, a ladder or a bin. */ +static bool standsOn(const Object *o) { + if (o->collider_count == 0 || neverOver(o) || o->is_held || o->lifted || o->broken) return false; + if (!o->is_static && o->weight < ONTO_HOLDS) return false; + switch (o->type) { + case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_FURNITURE: case OBJ_BIN: case OBJ_SUPPLY: case OBJ_LADDER: case OBJ_FIREPLACE: + return false; + default: + break; + } + const Collider *c = &o->colliders[0]; + return fabsf(c->axes[0].y) > 0.95f || fabsf(c->axes[1].y) > 0.95f || fabsf(c->axes[2].y) > 0.95f; +} + float Physics_lowObstacle(const Object *o) { if (neverOver(o) || o->is_held || o->lifted || o->collider_count == 0 || o->broken || o->thrown_by) return 0.0f; const Collider *c = &o->colliders[0]; @@ -494,7 +516,7 @@ static Vec3 steppingOver(const Object *obj, Vec3 m, Object *const *objects, int float speed = sqrtf(m.x * m.x + m.z * m.z); /* A standing leg the body would drag through what it stands behind: the body waits there for the other foot to come down, then that leg comes up */ - for (int k = 0; k < 2; k++) { + for (int k = 0; k < 2 && vec3_length(obj->shoved) < 0.5f; k++) { /* (a load shoving into us: the legs get out of its way anyhow) */ const Foot *ft = &obj->body.feet[k]; if (!ft->stepping && ft->into > LEG_WAIT && obj->body.feet[1 - k].stepping && speed > LEG_WAIT_SPEED && speed <= STEP_OVER_RUNNING) { /* (running, the foot leaps off instead) */ @@ -645,9 +667,12 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x); float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y; float near = ahead - reach, far = ahead + reach; - bool up = !over && !neverOver(o) && obj->type == OBJ_PLAYER && /* (people walk round; and nobody walks up onto tables) */ - o->type != OBJ_FURNITURE && o->type != OBJ_BIN && o->type != OBJ_SUPPLY && (o->is_static || o->weight >= ONTO_HOLDS) && c->axes[1].y > 0.95f && - top > STEP_OVER_HEIGHT * s && top <= STEP_UP_MOST * s && 2.0f * reach >= STEP_UP_ROOM * s && side >= 0.3f * s; + /* up onto it: higher than a stride over, or something solid you can just as well stand on (a log, a crate) - + no straddling then, you stand up there and walk on (people walk round; nobody walks up onto tables) */ + bool up = obj->type == OBJ_PLAYER && standsOn(o) && top <= STEP_UP_MOST * s && side >= 0.3f * s && + ((!over && top > STEP_OVER_HEIGHT * s && 2.0f * reach >= STEP_UP_ROOM * s) || + (top >= STAND_LOWEST * s && 2.0f * reach >= STAND_ROOM * s)); + if (up) over = false; if (!over && !up) continue; float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y; if (bottom > (0.2f + 0.15f * fmaxf(ahead, 0.0f)) * s || bottom < -0.5f * s - 0.15f * fmaxf(ahead, 0.0f)) continue; /* (up on something, or down in a hole: not in your way) */ @@ -655,7 +680,7 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const top = fmaxf(top, 0.05f * s); /* (downhill it may be lower than your feet) */ /* where the feet come down: free? (on top of it, going up) */ Vec3 right = vec3(-dir.z, 0.0f, dir.x); - float land_at = up ? near + STEP_UP_IN * s : far + OVER_LAND * s; + float land_at = up ? near + fminf(STEP_UP_IN * s, reach) : far + OVER_LAND * s; /* (a log: its middle) */ bool free = true; for (int k = -1; k <= 1 && free; k += 2) { Vec3 p = vec3_add(obj->position, vec3_add(vec3_scale(dir, land_at), vec3_scale(right, (float)k * 0.11f * s))); @@ -667,7 +692,8 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const if (!free) continue; obj->over_t = 1e-4f; obj->over_up = up; - obj->over_time = (OVER_MOVE_TIME + OVER_MOVE_HIGH * top / s) * (1.25f - 0.5f * obj->fitness) / fmaxf(Object_vigor(obj), 0.6f); + obj->over_time = (up ? STAND_UP_TIME + STAND_UP_HIGH * top / s /* (onto something low: one step up) */ + : OVER_MOVE_TIME + OVER_MOVE_HIGH * top / s) * (1.25f - 0.5f * obj->fitness) / fmaxf(Object_vigor(obj), 0.6f); obj->over_from = obj->position; obj->over_dir = dir; obj->over_near = near; @@ -743,6 +769,37 @@ static void hurdle(Object *obj, Object *const *objects, int count) { } } +/* Walking at the end of something long lying there (a log, a fallen tree) so + that you'd only just catch it: a little to the side, past its end, instead + of over it or up onto it. */ +#define END_CLIP 0.55f /* m of you that would still catch it at most */ +#define END_LONG 1.4f /* m: only things at least this long across your way */ +#define END_LOOK 1.6f /* m ahead */ +#define END_BODY 0.3f /* (your half width) */ + +static Vec3 pastTheEnd(const Object *obj, Vec3 m, Object *const *objects, int count) { + float speed = sqrtf(m.x * m.x + m.z * m.z); + if (speed < 0.3f || obj->sitting || obj->climbing || obj->on_ladder || obj->hands.grabbed || obj->type == OBJ_DOG) return m; + Vec3 dir = vec3(m.x / speed, 0.0f, m.z / speed), right = vec3(-dir.z, 0.0f, dir.x); + for (int i = 0; i < count; i++) { + const Object *o = objects[i]; + if (o->collider_count == 0 || o->is_character || o->is_held || (Physics_lowObstacle(o) <= 0.0f && !standsOn(o))) continue; + const Collider *c = &o->colliders[0]; + float side = projectRadius(c, right), reach = projectRadius(c, dir); + if (2.0f * side < END_LONG) continue; + float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y; + if (top > STEP_UP_MOST || top < 0.1f) continue; + Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position); + float ahead = vec3_dot(vec3(rel.x, 0.0f, rel.z), dir) - reach, across = vec3_dot(vec3(rel.x, 0.0f, rel.z), right); + float clip = side + END_BODY - fabsf(across); + if (ahead < -0.05f || ahead > END_LOOK || clip <= 0.0f || clip > END_CLIP) continue; + /* away from its middle, more the closer it gets */ + float push = fminf(1.0f, clip / fmaxf(ahead, 0.25f)) * speed; + return vec3_add(m, vec3_scale(right, across > 0.0f ? -push : push)); + } + return m; +} + /* ---------- Public ---------- */ #define REST_SPEED 0.03f /* things lying this still ... */ @@ -795,6 +852,7 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain if (n >= 0) { objects = near; count = n; } } if (obj->is_character && obj->on_terrain) obj->movement = slopeAdjusted(obj, terrain); + if (obj->is_character && obj->is_grounded) obj->movement = pastTheEnd(obj, obj->movement, objects, count); if (obj->is_character && obj->is_grounded) hurdle(obj, objects, count); if (obj->is_character && obj->is_grounded && obj->velocity.y <= 0.0f && startStepOver(obj, objects, count, terrain)) { stepOverMove(obj, terrain, dt); diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index f9feb2d..b485727 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -769,6 +769,9 @@ static bool solidFor(const Object *o, const Object *who) { return o->is_static || o->weight >= CARRY_SOLID; } +#define RAISE_DIP_DONE 0.2f /* rad: tipped down far enough to look whether it goes up again */ +#define RAISE_UP_TEST 1.36f /* (how steep it goes back up) */ + /* Long enough to be in the way (a ladder, a log, a branch; not a bag or a bottle). */ static bool carriedLong(const Object *item) { if (!item || item->collider_count == 0) return false; @@ -808,6 +811,25 @@ static void placeAt(Object *who, Vec3 pos, float yaw) { Hands_placeHeld(who); } +/* A ladder held up that runs into something (a beam, a doorway): tipped down + flat, carried under it, and up again once there's room up there. */ +static void dipLadder(World *world, Object *who, float overlap) { + Object *l = NULL; + for (int h = 0; h < 2; h++) + if (who->hands.state[h] == HAND_HOLDING && Ladder_isRaised(who->hands.item[h])) l = who->hands.item[h]; + if (!l) return; + if (overlap > 0.002f) { l->dipped = true; return; } + if (!l->dipped || l->raised > RAISE_DIP_DONE) return; /* (still going down: stays down) */ + /* down and clear: up again if it would stand up there without going into anything */ + float was = l->raised; + l->raised = RAISE_UP_TEST; + Hands_placeHeld(who); + bool room = carriedOverlap(world, who) <= 0.002f; + l->raised = was; + Hands_placeHeld(who); + if (room) l->dipped = false; +} + /* A carried ladder, log or axe doesn't go through walls or trees: if turning (or walking) this frame pushed it into something, they don't turn (or move). */ static void keepCarriedClear(World *world, Object *who) { @@ -815,6 +837,7 @@ static void keepCarriedClear(World *world, Object *who) { bool carrying = who->hands.state[0] == HAND_HOLDING || who->hands.state[1] == HAND_HOLDING; if (carrying && who->carry_ok && !who->on_ladder && !Tools_isWorking(who)) { float now = carriedOverlap(world, who); + dipLadder(world, who, now); if (now > 0.002f) { Vec3 pos = who->position; float yaw = who->rotation.y; @@ -972,7 +995,7 @@ void World_update(World *world, float dt, float game_dt) { for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type == OBJ_DOG) Dog_update(o, world->objects, world->count, dt); - if (o->type == OBJ_LADDER) Ladder_update(o, dt); + if (o->type == OBJ_LADDER) Ladder_update(world, o, dt); if (!o->is_character) continue; /* How work goes: fresh and sober, and liking the job, is quickest */ o->work_rate = Object_vigor(o) * (1.0f - 0.45f * fminf(o->drunk, 1.0f)) * Ai_workRate(o);