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.94.0: Alice fetches, fallen trees drag, ladders stay out of walls
- Alice runs after what you throw and brings it back to your feet (sometimes catching it in the air); a dropped sausage she eats, an apple she keeps and chews lying somewhere - Felled trees lying on the ground can be pushed or pulled; their branches make them heavy and snag on things in the way, and snap off when you keep pulling. Without branches they move much more easily - A ladder taken off a wall no longer pokes into it: carried at the side it slides back in your hand; walking into a wall stops you - A slid-out ladder walked into a wall is stood up straight instead of tipping flat through it (it still tips down under a beam or doorway)
entropie/Makefile | 2 +- entropie/README.md | 12 +++- entropie/STATE.md | 12 ++-- entropie/commit_message.txt | 31 ++++------ entropie/src/people/dog.c | 135 +++++++++++++++++++++++++++++++++++++++++- entropie/src/people/hands.c | 8 +-- entropie/src/world/forestry.c | 46 +++++++++++++- entropie/src/world/ladder.c | 7 ++- entropie/src/world/object.c | 16 +++++ entropie/src/world/object.h | 15 ++++- entropie/src/world/physics.c | 3 +- entropie/src/world/world.c | 80 +++++++++++++++++++++---- 12 files changed, 318 insertions(+), 49 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index d612aa4..c68f953 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.93.0 +VERSION := 1.94.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index e988ab3..47913ad 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -59,7 +59,9 @@ and satellite pictures (about 340 x 340 m). - The fireplace's tripod is of wooden poles standing well away from the fire; you walk round them - Alice the dachshund lives here (painted like the people): she trots after you, sniffs around, begs - for sausages, comes when called (R), lies by the fire and loves being petted (click) + for sausages, comes when called (R), lies by the fire and loves being petted (click). + She runs after what you throw and brings it back (sometimes catching it in the air); + a sausage on the ground she eats, an apple she keeps and chews somewhere - A little path leads out between the garages and the barn - Across the gravel road: the orchard (two apple trees, bushes all round), then a small path and a row of other garages, some closed, some full of junk @@ -378,6 +380,10 @@ Too heavy alone but not for half its weight: hold a log at one end and you lift that end, the other drags along on the ground (slower). A hand that can't reach its grip lets go and takes hold again. +A felled tree lying on the ground can be pushed or pulled (grab it, call +helpers). Its branches make it heavy and snag on things in the way: pull on +and they snap off. Without branches it moves much more easily. + ## Ladder - A telescopic ladder (6 m out, 1.5 m together) lies in the tool room of @@ -396,7 +402,9 @@ A hand that can't reach its grip lets go and takes hold again. beside it, it says so instead of going through it - 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 + rests on the edge, lower down it leans against the face there. Walked into + a wall it is stood up straight (under a beam or doorway it tips down flat). + Carried at the side it slides back in your hand instead of into a wall Look down and click to slide it together. Nothing to lean on: you stand it up on its own - and it falls over, or against something (a beam): there it rests - A thin beam counts even if you look just past it diff --git a/entropie/STATE.md b/entropie/STATE.md index 5b64106..313a383 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -117,6 +117,11 @@ Round 1.93: - The shell (`manor.c` Shell_build): 6.4 x 6.7 m (2 m longer towards the frame, which moved out 2 m), the ridge front to back, the door (1.6 m) on its -x side towards the woodpile, windows; the front closed but for a window. - Alice is a painted figure (`figure.frag` species 1, buildDog: body, neck, skull, snout, nose, floppy ears, legs with paws, tail; her eyes painted; `dog.c` Dog_draw sets her joints: legs trot in diagonal pairs, lying stretched, nose down sniffing, tail wagging); her shadow the same way. Shapes_dogLeg / dogTail are gone. +Round 1.94: +- Alice fetches (`dog.c` DOG_FETCH / DOG_BRING / DOG_KEEP): something thrown or dropped up to FETCH_KG 2 kg within FETCH_SEES 20 m (food: FOOD_SMELLS 10 m) -> she runs to it with her mouth; caught in the air below CATCH_HIGH 0.75 m (CATCH_CHANCE 0.55); carried at her mouth (`carry`), brought to whoever threw it (`from`) and dropped at their feet ("Woof! Woof!"). A sausage is eaten on the spot ("*gulp*"), an apple kept: she walks off, lies down and chews it (CHEW_TIME 18 s, "*crunch crunch*"). +- Fallen trees (`forestry.c`): once down (not stopped by something) loose (`is_static` false), weight `Object_treeWeight` (wood + TREE_BRANCH_KG 18 per branch). Grabbing uses `Object_dragWeight`: lying on the ground TREE_DRAG 0.5 x weight + TREE_SNAG_KG 25 per branch. Only the trunk collider meets the ground. Dragged with the crown against something static: `snag` adds up (fades 0.5/s when free), the tree is held; at SNAG_HOLD 1 s (or SNAG_SPEED 0.9 m/s) a branch snaps (`dropBranch`, chips). +- Carried ladders and walls (`world.c`): a raised ladder running into something tips down flat (`dipped`) only if that gets it out (a beam, a doorway); else it is stood up straight (`steep`, RAISE_HIGH) until it would stand at 1.36 clear again. Carried at the side, a ladder in a wall slides along in the hand (`grip_shift`, SLIDE_STEP 0.1 m, at least SLIDE_KEEP 0.35 m from its end; back to the middle once clear) before the walk is stopped: taken off a wall, walked into one. + Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves. Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built); the coat rack (FURN_COATRACK, in your kitchen by the doors, garage coords 20.6 / 2.4) shows the jackets hanging on it (`Shapes_coatRack`, up to 6, a bitmask of whose in `shown`). `jacket_hung` (characters, true at the start): cold (chill 0.6+) and the jacket with them: on it goes (as before); hanging: NPCs `ai.want_jacket` -> `ai.c` fetchJacket (idle / wander: goTo the rack, on with it, "Ah, better."; gives up after 90 s or warm again); you are told once ("your jacket hangs on the rack in your garage") and click the rack (`game_actions.c` coatRack: on, or back onto it). Warm again: it comes off and stays with them. @@ -129,7 +134,7 @@ Balancing (`world.c` -> `body.balance_goal` from `Physics_footing`: 1 at a top 0 Chainsaw: collider and pivot fit the mesh (the motor's underside on the ground), the bar lower on the motor (it floated). -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. +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) if flat gets it out, else `steep`; 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. @@ -249,8 +254,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) **From the 15th of October (to do)** -- Trees: push / pull fallen trees; branches break or snag on obstacles (with enough momentum); much easier to carry without branches -- Alice: dumb; she tries to catch things thrown, eats a sausage dropped on the ground, may catch a thrown apple (and not bring it back). NPC pathfinding has problems +- NPC pathfinding has problems (which ones: to look at with examples) - NPCs sitting at the meeting table (chairs work for them, nothing sends them there yet); eating at a table - Felling: aiming could set the cut height more freely @@ -275,4 +279,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.93.0 (build ac550b2)` +State of `entropie 1.94.0 (build 83ab53f)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index eebe5bf..6a09c1c 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,21 +1,12 @@ -entropie 1.93.0: Alice painted like the people, one roof, chairs up to the table +entropie 1.94.0: Alice fetches, fallen trees drag, ladders stay out of walls -- Alice is drawn in the same painted style as the people (smooth body, - floppy ears, paws, wagging tail, her shadow too) -- Chairs: once you sit, the chair is shuffled in to the table; getting up - it goes out again first -- E / Q next to a table, counter or crate sets the thing on top of it -- Dropped bottles nearly always fall over; things on a slope slide or roll - down it when steep enough, and keep going more easily once pushed -- Felling: the cut at about hip height (lower if you look lower); the trunk - slides off the stump and lies on the ground (it hung in the air) -- Chopping: more hips, back and head in the swing -- Main building: one merged roof (the left ridge runs into the right - roof); the ground no longer shows through the floors; the basement - landing is deeper, the stairs room wider, no lintel hanging in the side - opening -- Roofs are closed underneath: no looking through them from below -- Your unfinished building: 2 m longer towards the frame, the roof turned, - the door on the woodpile side -- Garage gates swing right round and lie flat against the wall -- No climbing through a wall onto something behind it +- Alice runs after what you throw and brings it back to your feet + (sometimes catching it in the air); a dropped sausage she eats, an apple + she keeps and chews lying somewhere +- Felled trees lying on the ground can be pushed or pulled; their branches + make them heavy and snag on things in the way, and snap off when you + keep pulling. Without branches they move much more easily +- A ladder taken off a wall no longer pokes into it: carried at the side it + slides back in your hand; walking into a wall stops you +- A slid-out ladder walked into a wall is stood up straight instead of + tipping flat through it (it still tips down under a beam or doorway) diff --git a/entropie/src/people/dog.c b/entropie/src/people/dog.c index 00e98c5..b4d605e 100644 --- a/entropie/src/people/dog.c +++ b/entropie/src/people/dog.c @@ -2,7 +2,7 @@ #include <string.h> -enum { DOG_WANDER, DOG_FOLLOW, DOG_COME, DOG_BEG, DOG_REST }; +enum { DOG_WANDER, DOG_FOLLOW, DOG_COME, DOG_BEG, DOG_REST, DOG_FETCH, DOG_BRING, DOG_KEEP }; #define TROT_SPEED 1.5f #define RUN_SPEED 3.4f @@ -10,6 +10,12 @@ enum { DOG_WANDER, DOG_FOLLOW, DOG_COME, DOG_BEG, DOG_REST }; #define BEG_RANGE 7.0f /* smells a sausage this far */ #define FIRE_RANGE 25.0f #define TURN_RATE 6.0f +#define FETCH_SEES 20.0f /* m: something thrown this near, she's after it */ +#define FOOD_SMELLS 10.0f /* m: a sausage lying on the ground this near: hers */ +#define FETCH_KG 2.0f /* things up to this she carries in her mouth */ +#define CATCH_HIGH 0.75f /* m over the ground: in the air up to this she may snap it */ +#define CATCH_CHANCE 0.55f +#define CHEW_TIME 18.0f /* s an apple keeps her busy */ static float rnd(Object *dog) { unsigned *r = &dog->ai.rng; @@ -39,6 +45,63 @@ static Object *sausageHolder(const Object *dog, Object *const *objects, int coun return NULL; } +static void bark(Object *dog, const char *what); + +/* Something for her: thrown (small enough to carry), or a sausage lying on the ground. */ +static Object *somethingToFetch(const Object *dog, Object *const *objects, int count) { + Object *best = NULL; + float best_d = 1e9f; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o->is_held || o->broken || o->weight > FETCH_KG || o->collider_count == 0 || o->on_grill) continue; + float d = flatDist(o->position, dog->position); + bool thrown = o->thrown_by && o->thrown_by != dog && d < FETCH_SEES; + bool food = o->type == OBJ_SAUSAGE && !o->thrown_by && o->is_grounded && d < FOOD_SMELLS; + if ((thrown || food) && d < best_d) { best = o; best_d = d; } + } + return best; +} + +/* In her mouth: in front of her head. */ +static Vec3 mouth(const Object *dog) { + return vec3_add(dog->position, vec3_add(vec3_scale(Object_facing(dog), 0.5f), vec3(0.0f, 0.27f - 0.09f * dog->dog.lie, 0.0f))); +} + +static void letGo(Object *dog) { + Object *o = dog->dog.carry; + if (!o) return; + o->is_held = false; + o->velocity = vec3_zero(); + o->position = vec3_add(mouth(dog), vec3(0.0f, -0.12f, 0.0f)); + Object_syncColliders(o); + dog->dog.carry = NULL; + dog->dog.from = NULL; +} + +/* Got it: a sausage goes down at once, an apple she keeps, the rest she brings back. */ +static void takeIt(Object *dog, Object *o, bool in_air) { + dog->dog.fetch = NULL; + dog->dog.wag = 3.0f; + if (o->type == OBJ_SAUSAGE) { + Object_useUp(o); + bark(dog, in_air ? "*snap* *gulp*" : "*gulp*"); + dog->dog.mode = DOG_WANDER; + return; + } + dog->dog.from = o->thrown_by; + dog->dog.carry = o; + o->is_held = true; + o->thrown_by = NULL; + o->velocity = vec3_zero(); + dog->dog.chew = 0.0f; + if (in_air) bark(dog, "*snap!*"); + dog->dog.mode = o->type == OBJ_APPLE ? DOG_KEEP : DOG_BRING; + if (o->type == OBJ_APPLE) { /* (off with it, away from people) */ + float a = rnd(dog) * 2.0f * PI_F; + dog->dog.goal = vec3_add(dog->position, vec3(cosf(a) * 4.0f, 0.0f, sinf(a) * 4.0f)); + } +} + /* Turns towards dir and moves that way (smooth turns). */ static void trot(Object *dog, Vec3 dir, float speed, float dt) { dir.y = 0.0f; @@ -72,9 +135,21 @@ void Dog_update(Object *dog, Object *const *objects, int count, float dt) { if (objects[i]->type == OBJ_FIREPLACE && objects[i]->burning && flatDist(objects[i]->position, dog->position) < FIRE_RANGE) fire = objects[i]; + /* Something thrown, or food on the ground: after it */ + Object *carry = dog->dog.carry; + if (carry && (carry->broken || !carry->is_held)) { dog->dog.carry = carry = NULL; } + if (carry && dog->dog.mode != DOG_KEEP && dog->dog.mode != DOG_BRING && dog->dog.mode != DOG_COME) dog->dog.mode = DOG_BRING; + if (!carry) { + Object *f = dog->dog.fetch; + if (!f || f->is_held || f->broken) f = somethingToFetch(dog, objects, count); + dog->dog.fetch = f; + if (f && dog->dog.mode != DOG_COME) dog->dog.mode = DOG_FETCH; + else if (!f && dog->dog.mode == DOG_FETCH) dog->dog.mode = DOG_WANDER; + } + /* What now */ int mode = dog->dog.mode; - if (mode != DOG_COME) { + if (mode != DOG_COME && mode != DOG_FETCH && mode != DOG_BRING && mode != DOG_KEEP) { if (holder) mode = DOG_BEG; else if (to_player > FOLLOW_FAR) mode = DOG_FOLLOW; else if (mode == DOG_BEG) mode = DOG_WANDER; @@ -121,6 +196,56 @@ void Dog_update(Object *dog, Object *const *objects, int count, float dt) { goal = dog->dog.goal; lie = flatDist(goal, dog->position) < 0.5f; break; + case DOG_FETCH: { /* after it: where it's going (in the air), or where it lies */ + Object *f = dog->dog.fetch; + Vec3 at = Object_colliderCenter(f, 0); + goal = vec3_add(at, f->thrown_by ? vec3_scale(vec3(f->velocity.x, 0.0f, f->velocity.z), 0.35f) : vec3_zero()); + goal = vec3_sub(goal, vec3_scale(Object_facing(dog), 0.4f)); /* (her mouth to it, not her feet) */ + speed = RUN_SPEED; + float d = flatDist(at, mouth(dog)), over = Object_bottom(f) - dog->position.y; + if (d < 0.4f) { + if (f->thrown_by && over > 0.08f) { /* still in the air: snap at it */ + if (over < CATCH_HIGH && dog->dog.timer <= 0.0f) { + if (rnd(dog) < CATCH_CHANCE) takeIt(dog, f, true); + else { dog->dog.timer = 0.5f; bark(dog, "*snap* ...missed"); } + } + } else if (over < 0.3f) { + takeIt(dog, f, false); + } + } + break; + } + case DOG_BRING: { /* a stick (or so) back to whoever threw it, at their feet */ + const Object *to = dog->dog.from ? dog->dog.from : player; + Vec3 away = vec3_sub(dog->position, to->position); + away.y = 0.0f; + float d = vec3_length(away); + goal = d > 1e-3f ? vec3_add(to->position, vec3_scale(away, 0.8f / d)) : to->position; + speed = RUN_SPEED * 0.8f; + if (flatDist(goal, dog->position) < 0.4f) { + letGo(dog); + dog->dog.mode = DOG_WANDER; + dog->dog.timer = 3.0f; + dog->dog.wag = 4.0f; + bark(dog, "Woof! Woof!"); + } + break; + } + case DOG_KEEP: /* an apple: off with it, down, and eaten bit by bit */ + goal = dog->dog.goal; + lie = flatDist(goal, dog->position) < 0.5f; + if (lie) { + dog->dog.chew += dt; + if (dog->dog.bark > 5.0f && rnd(dog) < 0.05f) bark(dog, "*crunch crunch*"); + if (dog->dog.chew > CHEW_TIME) { + Object *a = dog->dog.carry; + dog->dog.carry = NULL; + if (a) Object_useUp(a); + dog->dog.mode = DOG_WANDER; + dog->dog.timer = 2.0f; + } + } + break; default: /* sniffing around near you */ if (dog->dog.timer <= 0.0f) { float a = rnd(dog) * 2.0f * PI_F, r = 1.5f + 4.0f * rnd(dog); @@ -137,6 +262,12 @@ void Dog_update(Object *dog, Object *const *objects, int count, float dt) { if (!lie && vec3_length(to) > 0.35f) trot(dog, to, speed, dt); else if (mode == DOG_BEG && holder) trot(dog, vec3_scale(vec3_sub(holder->position, dog->position), 0.001f), 0.0f, dt); dog->dog.lie = clampf(dog->dog.lie + (lie ? dt : -2.0f * dt), 0.0f, 1.0f); + if (dog->dog.carry) { /* in her mouth */ + Object *c = dog->dog.carry; + c->position = mouth(dog); + c->rotation = vec3(PI_F * 0.5f, dog->rotation.y + PI_F * 0.5f, 0.0f); /* (crosswise) */ + Object_syncColliders(c); + } dog->dog.phase += dog->ground_speed * dt * 11.0f; } diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index f2e9bdd..8500a2a 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -438,7 +438,7 @@ bool Hands_grabObject(Object *who, Object *target) { h->grabbed = target; h->grab_heading = vec3_scale(toward, -1.0f); h->grab_vector = vec3_sub(target->position, who->position); - h->grab_share = teamShare(pushCapacity(who), target->weight); + h->grab_share = teamShare(pushCapacity(who), Object_dragWeight(target)); who->rotation.y = yawFacing(h->grab_heading); Object_syncColliders(who); return true; @@ -480,7 +480,7 @@ float Hands_turnGrabbed(Object *who, Object *const *objects, int count, float wa team[n++] = objects[k]; capacity += pushCapacity(objects[k]); } - float share = o->lifted ? 1.0f : teamShare(capacity, o->weight); + float share = o->lifted ? 1.0f : teamShare(capacity, Object_dragWeight(o)); float reach = fmaxf(extentAlong(o, vec3(1.0f, 0.0f, 0.0f)), extentAlong(o, vec3(0.0f, 0.0f, 1.0f))); float most = TURN_RATE * share / (0.5f + reach) * dt; float a = clampf(want, -most, most); @@ -882,12 +882,12 @@ void Hands_solveGrabs(Object *const *objects, int count, float dt) { /* Lifted: it goes where the team goes, carried at hip height. On the ground: it follows the team's average wish as far as their 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); + float share = o->end_up ? END_DRAG : o->lifted ? 1.0f : teamShare(capacity, Object_dragWeight(o)); 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); + share = teamShare(capacity, Object_dragWeight(o) + BYSTANDER_HOLD * in_way); move = vec3_scale(sum, share / (float)n); } } diff --git a/entropie/src/world/forestry.c b/entropie/src/world/forestry.c index dd2e98e..a011d0e 100644 --- a/entropie/src/world/forestry.c +++ b/entropie/src/world/forestry.c @@ -252,7 +252,44 @@ static bool touches(const Object *tree, const Object *o) { return false; } +/* A fallen tree dragged along: its branches catch on what stands in the way + and hold it back; pulled on (or going fast) one snaps off. */ +#define SNAG_HOLD 1.0f /* s pulling against a snag before a branch gives */ +#define SNAG_SPEED 0.9f /* m/s: into it this fast, it snaps at once */ + +static bool draggedBy(const World *world, const Object *tree) { + for (int i = 0; i < world->count; i++) + if (world->objects[i]->hands.grabbed == tree) return true; + return false; +} + +static void snagBranches(World *world, Object *tree, float dt) { + if (tree->is_static || tree->branches <= 1 || tree->collider_count < 2 || !draggedBy(world, tree)) { tree->snag = 0.0f; return; } + Object *caught = NULL; + for (int i = 0; i < world->count && !caught; i++) { + Object *o = world->objects[i]; + if (o == tree || !o->is_static || o == tree->stump || o->collider_count == 0 || o->type == OBJ_DECAL || o->type == OBJ_STRUCTURE) continue; + if (fabsf(o->position.x - tree->position.x) > 15.0f || fabsf(o->position.z - tree->position.z) > 15.0f) continue; + for (int c = 0; c < o->collider_count && !caught; c++) + if (Physics_overlap(tree, 1, o, c)) caught = o; + } + if (!caught) { tree->snag = fmaxf(0.0f, tree->snag - 0.5f * dt); return; } /* (caught and freed again by the push back: it adds up) */ + float speed = sqrtf(tree->velocity.x * tree->velocity.x + tree->velocity.z * tree->velocity.z); + tree->snag += dt; + if (speed < SNAG_SPEED) tree->velocity = vec3(0.0f, tree->velocity.y, 0.0f); /* (held back) */ + if (tree->snag >= SNAG_HOLD || speed >= SNAG_SPEED) { + tree->snag = 0.0f; + Vec3 at = Object_colliderCenter(tree, 1); + dropBranch(world, tree, vec3_scale(vec3_add(at, Object_colliderCenter(caught, 0)), 0.5f), 80 + tree->branches); + Chips_spray(&world->chips, vec3_add(at, vec3(0.0f, 0.4f, 0.0f)), vec3(0.0f, 1.0f, 0.0f), 8, false); + } +} + void Forestry_update(World *world, float dt) { + for (int i = 0; i < world->count; i++) { + Object *t = world->objects[i]; + if (t->type == OBJ_TREE && t->toppled && t->topple >= 1.0f) snagBranches(world, t, dt); + } for (int i = 0; i < world->count; i++) { Object *tree = world->objects[i]; if (tree->type != OBJ_TREE || !tree->toppled || tree->topple >= 1.0f) continue; @@ -273,6 +310,13 @@ void Forestry_update(World *world, float dt) { tree->topple = 1.0f; Object_syncColliders(tree); } - if (tree->topple >= 1.0f) crash(world, tree, stopped && stopped->type == OBJ_TREE ? stopped : NULL); + if (tree->topple >= 1.0f) { + crash(world, tree, stopped && stopped->type == OBJ_TREE ? stopped : NULL); + if (!stopped) { /* lying on the ground: it can be dragged about now (heavy) */ + tree->is_static = false; + tree->velocity = vec3_zero(); + tree->weight = Object_treeWeight(tree); + } + } } } diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c index eda10c4..7a56f4f 100644 --- a/entropie/src/world/ladder.c +++ b/entropie/src/world/ladder.c @@ -588,6 +588,8 @@ bool Ladder_take(Object *who, Object *l) { l->is_static = false; l->lean_top = 0.0f; l->rest_time = 0.0f; + l->dipped = l->steep = false; + l->grip_shift = 0.0f; if (stepLadder(l)) l->extend_goal = 0.0f; /* (folds up as you take it) */ else l->raised = clampf(asinf(clampf(upAlong(l).y, -1.0f, 1.0f)), 0.15f, RAISE_HIGH); /* (held up as steep as it leaned: no swing down and up again) */ return Hands_pickUpObject(who, hand, l); @@ -833,7 +835,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 = l->dipped ? RAISE_DIP : raiseGoal(who); /* (eased in Ladder_update) */ + l->raise_goal = l->dipped ? RAISE_DIP : l->steep ? RAISE_HIGH : 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) */ @@ -847,13 +849,14 @@ void Ladder_placeHeld(Object *who, int hand, Object *l) { return; } l->raised = 0.0f; - l->dipped = false; + l->dipped = l->steep = 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); Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[jh]); Vec3 center = vec3_sub(palm, vec3(0.0f, LADDER_WIDTH * 0.5f - 0.02f, 0.0f)); /* holding the upper rail */ center = vec3_add(center, vec3_scale(right, hand == HAND_LEFT ? -0.05f : 0.05f)); + center = vec3_add(center, vec3_scale(facing, l->grip_shift)); /* (slid along in the hand: off a wall) */ l->position = vec3_sub(center, l->colliders[0].offset); l->velocity = vec3_zero(); } diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 9c9bf26..40a3b07 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -890,6 +890,22 @@ void Object_setBranches(Object *obj, int branches) { if (obj->type != OBJ_TREE || branches == obj->branches) return; obj->branches = branches; if (branches > 0) rebuildTree(obj); + if (!obj->is_static) obj->weight = Object_treeWeight(obj); +} + +#define TREE_BRANCH_KG 18.0f /* a layer of branches */ +#define TREE_DRAG 0.5f /* dragged along: one end on the ground, about half its weight ... */ +#define TREE_SNAG_KG 25.0f /* ... and each layer of branches catches in the ground */ + +float Object_treeWeight(const Object *obj) { + float r = obj->colliders[0].half_extents.x; + return WOOD_DENSITY * PI_F * r * r * obj->size.y + TREE_BRANCH_KG * (float)obj->branches; +} + +float Object_dragWeight(const Object *obj) { + if (obj->type == OBJ_TREE && obj->toppled && !obj->lifted) + return TREE_DRAG * obj->weight + TREE_SNAG_KG * (float)obj->branches; + return obj->weight; } void Object_topple(Object *obj, Vec3 dir) { diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 0d4a6e1..1384d44 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -233,6 +233,10 @@ typedef struct Object { int mode; float timer, phase, wag, lie, bark; Vec3 goal; + struct Object *fetch; /* after this (thrown, or food lying about) ... */ + struct Object *carry; /* ... got it in her mouth ... */ + struct Object *from; /* ... thrown by (a stick goes back to them) */ + float chew; /* seconds with it (an apple: she keeps it, lies down, eats it) */ } dog; float charred; /* wood: 0 = fresh .. 1 = burnt to ashes */ float glow; /* wood: 0..1 glowing embers (in a burning fire) */ @@ -255,6 +259,8 @@ typedef struct Object { 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 steep; /* ... stood up straight: a wall in front (flat it would go into it) */ + float grip_shift; /* ladders carried at the side: slid back (< 0) or on in the hand (m), off a wall */ bool just_caught; /* ladders: fell against something just now and rests there (the game says so) */ bool jacket_hung; /* characters: the jacket hangs on the coat rack (else it's with them, worn or not) */ bool jacket_told; /* the player: told where the jacket is this cold spell */ @@ -265,7 +271,8 @@ typedef struct Object { Vec3 gate_shut, gate_out; /* ... the way it reaches shut, and open (flat, unit) */ Vec3 tucked; /* chairs: where it stands pushed in (set when first pulled out) ... */ bool tucked_set; - bool trunk_cut; /* trees: felled (cut off its foot: a sawn end, no roots) */ + bool trunk_cut; + float snag; /* fallen trees: seconds its branches have been caught on something while dragged */ /* trees: felled (cut off its foot: a sawn end, no roots) */ float chair_out, chair_goal; /* ... pulled back from there this far (m), and going to this far */ bool seat_scooted; bool drop_tip; /* bottles: dropped (not set down): it falls over when it lands, mostly */ /* characters: sitting, the chair shuffled in to the table */ @@ -347,6 +354,12 @@ float Object_rayToCollider(const Object *obj, int index, Vec3 origin, Vec3 dir /* How far obj's colliders reach from its position at most (a bounding sphere). */ float Object_reach(const Object *obj); +/* Trees: the trunk's wood and the branches (kg). */ +float Object_treeWeight(const Object *obj); +/* What pushing or pulling it along takes (kg): its weight; a fallen tree is + dragged (one end on the ground) and its branches catch: easier without them. */ +float Object_dragWeight(const Object *obj); + /* Characters: up high (on a roof, or well over the ground)? */ bool Object_upHigh(const Object *obj, float ground_y); /* Characters up high: how careful (0 not at all .. 1 creeping): fear, drink and wits (drunk and dim: careless). */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index 7fd3bc8..6c26d60 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -578,7 +578,8 @@ static void corners(const Object *obj, int i, Vec3 out[8]) { static void resolveTerrain(Object *obj, const Terrain *terrain) { if (!obj->is_character) { float lift = -1e9f; - for (int i = 0; i < obj->collider_count; i++) { + const int parts = obj->type == OBJ_TREE ? 1 : obj->collider_count; /* (a tree lies on its trunk: the crown gives) */ + for (int i = 0; i < parts; i++) { Vec3 p[8]; corners(obj, i, p); for (int k = 0; k < 8; k++) lift = fmaxf(lift, Terrain_height(terrain, p[k].x, p[k].z) - p[k].y); diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 98ab6e1..0031d4a 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -969,23 +969,81 @@ 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. */ +#define RAISE_DIP_TEST 0.12f /* (how flat it goes down) */ + +static float raisedAt(World *world, Object *who, Object *l, float angle) { + float was = l->raised; + l->raised = angle; + Hands_placeHeld(who); + float o = carriedOverlap(world, who); + l->raised = was; + Hands_placeHeld(who); + return o; +} + +/* A ladder held up that runs into something: under a beam or a doorway it is + tipped down flat, carried under it, and up again once there's room up there. + Against a wall (flat it would only go in deeper) it's stood up straight. */ 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 (overlap > 0.002f) { + if (l->dipped || l->steep) return; + float flat = raisedAt(world, who, l, RAISE_DIP_TEST), up = raisedAt(world, who, l, RAISE_UP_TEST); + if (flat + 0.002f < overlap && flat <= up) l->dipped = true; + else l->steep = true; + return; + } + if (l->steep) { /* clear: back to how it's held once that's clear too */ + if (raisedAt(world, who, l, RAISE_UP_TEST) <= 0.002f) l->steep = false; + 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; + if (raisedAt(world, who, l, RAISE_UP_TEST) <= 0.002f) l->dipped = false; +} + +#define SLIDE_STEP 0.1f /* m: a ladder carried at the side slides back in the hand this much at a time ... */ +#define SLIDE_KEEP 0.35f /* ... held at least this far from its end */ + +/* A ladder carried at the side that is in a wall (taken off it, walked into it): + slid along in the hand till it's out (back to the middle once there's room). + Returns how deep it's in now. */ +static float slideLadder(World *world, Object *who, float now) { + Object *l = NULL; + for (int h = 0; h < 2; h++) { + Object *item = who->hands.item[h]; + if (who->hands.state[h] == HAND_HOLDING && item && item->type == OBJ_LADDER && !Ladder_isRaised(item) && item->leaning == false) l = item; + } + if (!l) return now; + float most = fmaxf(0.0f, l->colliders[0].half_extents.y - SLIDE_KEEP), was = l->grip_shift; + if (now > 0.002f) { + float best = was, best_o = now; + for (int k = 1; k * SLIDE_STEP <= 2.0f * most + SLIDE_STEP && best_o > 0.002f; k++) { + for (int side = -1; side <= 1; side += 2) { + float shift = clampf(was + side * k * SLIDE_STEP, -most, most); + l->grip_shift = shift; + Hands_placeHeld(who); + float o = carriedOverlap(world, who); + if (o < best_o - 0.002f) best = shift, best_o = o; + if (best_o <= 0.002f) break; + } + } + l->grip_shift = best; + Hands_placeHeld(who); + return best_o; + } + if (was != 0.0f) { + l->grip_shift = was > 0.0f ? fmaxf(0.0f, was - 0.5f * SLIDE_STEP) : fminf(0.0f, was + 0.5f * SLIDE_STEP); + Hands_placeHeld(who); + if (carriedOverlap(world, who) > 0.002f) { + l->grip_shift = was; + Hands_placeHeld(who); + } + } + return now; } /* A carried ladder, log or axe doesn't go through walls or trees: if turning @@ -994,7 +1052,7 @@ static void keepCarriedClear(World *world, Object *who) { who->carry_blocked = false; 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); + float now = slideLadder(world, who, carriedOverlap(world, who)); dipLadder(world, who, now); if (now > 0.002f) { Vec3 pos = who->position;