Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie 2.47.0: the big old tree, injuries
- A big old tree between the Obstwiese and the Kartoffelacker leans towards the road and the phone line along it (poles, a tall mast, wires) - Enrico from Go-Darmstadt (new, not at the Berlin meetings) fells it: the throw line at its foot, the notch with the chainsaw, the crew on the rope, the back cut - You throw the line up over a branch (T, looking up): the higher it catches, the better it pulls; pull it down to try again; take hold of the rope yourself - Not enough pull, a low rope, a gust: it comes down off to the side or across the road through the phone line (1500 EUR, Kurti cross) - Broken arms and legs: under a falling tree, landing hard from high up. The ambulance comes (lights, two-tone horn), the hospital, back days later with a cast - weaker or slower for two weekends - The cast is drawn (white plaster on the forearm or shin) - No rain drawn or heard in Berlin - Dev menu: the big tree step by step
commit_message.txt | 34 ++-- entropie/ARCHITECTURE.md | 6 +- entropie/Makefile | 2 +- entropie/README.md | 7 +- entropie/STATE.md | 75 ++++++++- entropie/assets/shaders/figure.frag | 10 +- entropie/commit_message.txt | 34 ++-- entropie/src/game/game.c | 1 + entropie/src/game/game.h | 16 ++ entropie/src/game/game_actions.c | 2 +- entropie/src/game/game_bigtree.c | 311 ++++++++++++++++++++++++++++++++++++ entropie/src/game/game_dev.c | 7 +- entropie/src/game/game_injury.c | 209 ++++++++++++++++++++++++ entropie/src/game/game_internal.h | 9 ++ entropie/src/game/game_meeting.c | 13 +- entropie/src/game/game_story.c | 2 + entropie/src/game/game_why.c | 2 + entropie/src/game/save.c | 13 +- entropie/src/game/sound.c | 15 +- entropie/src/people/ai.c | 2 + entropie/src/people/ai_internal.h | 2 + entropie/src/people/ai_jobs.c | 140 ++++++++++++++++ entropie/src/people/ai_project.c | 2 +- entropie/src/people/hands.c | 9 ++ entropie/src/people/hands.h | 2 + entropie/src/render/figure.h | 1 + entropie/src/render/renderer.c | 2 + entropie/src/render/renderer.h | 2 +- entropie/src/render/shapes.h | 9 ++ entropie/src/render/shapes_props.c | 121 +++++++++++++- entropie/src/world/bigtree.c | 52 ++++++ entropie/src/world/bigtree.h | 37 +++++ entropie/src/world/cast.c | 10 +- entropie/src/world/cast.h | 2 + entropie/src/world/forestry.c | 30 +++- entropie/src/world/forestry.h | 3 + entropie/src/world/injury.c | 52 ++++++ entropie/src/world/injury.h | 22 +++ entropie/src/world/object.c | 18 ++- entropie/src/world/object.h | 6 + entropie/src/world/site.c | 83 ++++++++++ entropie/src/world/site.h | 8 + entropie/src/world/weather.c | 2 +- entropie/src/world/world.c | 7 +- entropie/src/world/world.h | 16 ++ 45 files changed, 1344 insertions(+), 64 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index 97c18ed..c9bf6fa 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,18 +1,18 @@ -entropie 2.46.0: the festival +entropie 2.47.0: the big old tree, injuries -- Weekend 6 is the festival: the Herberges before it say so, everybody's - on the ground Friday at ten, the gates open at six -- 36 guests come in from the chicken farm's yard (Friday evening, Saturday - late morning), camp right of the strip of wood and pitch their tents -- Floors with at least their deck play, each its own music and times; - guests dance to the beat (dancing is new to the bodies), better floors - are more fun -- Saturday at two the storm warning: get everybody inside (main building, - barn, garages' kitchen) - shout it (X, then 1), the crew help, a floor - with a roof announces it. At its worst, who's still out is counted -- The awareness team, free Urinellas and "no shirt, no service" now do - something at the festival -- Sunday the guests pack up and leave; at four the crew sums it up at the - fire (a result in %); the notebook has a festival part -- Dev menu: jump to the festival and through it -- Saves keep the guests and the festival +- A big old tree between the Obstwiese and the Kartoffelacker leans towards + the road and the phone line along it (poles, a tall mast, wires) +- Enrico from Go-Darmstadt (new, not at the Berlin meetings) fells it: the + throw line at its foot, the notch with the chainsaw, the crew on the rope, + the back cut +- You throw the line up over a branch (T, looking up): the higher it + catches, the better it pulls; pull it down to try again; take hold of the + rope yourself +- Not enough pull, a low rope, a gust: it comes down off to the side or + across the road through the phone line (1500 EUR, Kurti cross) +- Broken arms and legs: under a falling tree, landing hard from high up. + The ambulance comes (lights, two-tone horn), the hospital, back days + later with a cast - weaker or slower for two weekends +- The cast is drawn (white plaster on the forearm or shin) +- No rain drawn or heard in Berlin +- Dev menu: the big tree step by step diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index 333bb35..923f867 100644 --- a/entropie/ARCHITECTURE.md +++ b/entropie/ARCHITECTURE.md @@ -10,9 +10,9 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL | `src/core/` | window, GL loader (`gl.h`: every GL call is listed there), input, sound out (`audio.c`: a synthesizer and mixer, no sound files) | core | | `src/math/` | vectors, matrices, frustum, yaw conventions (`linalg.h`) | math | | `src/render/` | renderer and its passes, shaders' uniforms, meshes, procedural shapes (`shapes*.c`), figures, camera, sky, UI | core, math, render | -| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c` - also the Berlin rooms off the map, `course.c`), everybody in the story (`cast.c`), the floors' stages (`stage.c`), the side jobs' ground (`field.c`: the Kartoffelacker's grass, the snails, Kerry's art), the why questions' content (`why.c`), the festival's guests (`guests.c`), tools, forestry, ladder, hose, camp, clothes, car, water, weather, grass, grid, day | everything but game | -| `src/people/` | bodies (animation), hands, AI (the projects: `ai_project.c`, the side jobs: `ai_jobs.c`, the festival weekend: `ai_fest.c`), talk, condition, dog, climbing | everything but game | -| `src/game/` | the frame: input to actions, player, camera follow, talk and dev UI, the arrival by car at the start, the story (`game_story.c`: the crew turning up, the crew overview), the shopping run (`game_shop.c`), the meetings in Berlin and the weekends (`game_meeting.c`), the why questions coming up and your notebook (`game_why.c`), the festival weekend and the storm (`game_festival.c`), saves (`save.c`), what you hear (`sound.c`) | all | +| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c` - also the Berlin rooms off the map, `course.c`), everybody in the story (`cast.c`), the floors' stages (`stage.c`), the side jobs' ground (`field.c`: the Kartoffelacker's grass, the snails, Kerry's art), the why questions' content (`why.c`), the festival's guests (`guests.c`), the big old tree's places (`bigtree.c`), broken bones and casts (`injury.c`), tools, forestry, ladder, hose, camp, clothes, car, water, weather, grass, grid, day | everything but game | +| `src/people/` | bodies (animation), hands, AI (the projects: `ai_project.c`, the side jobs and the big tree: `ai_jobs.c`, the festival weekend: `ai_fest.c`), talk, condition, dog, climbing | everything but game | +| `src/game/` | the frame: input to actions, player, camera follow, talk and dev UI, the arrival by car at the start, the story (`game_story.c`: the crew turning up, the crew overview), the shopping run (`game_shop.c`), the meetings in Berlin and the weekends (`game_meeting.c`), the why questions coming up and your notebook (`game_why.c`), the festival weekend and the storm (`game_festival.c`), the big tree's rope and fall (`game_bigtree.c`), the ambulance and the hospital (`game_injury.c`), saves (`save.c`), what you hear (`sound.c`) | all | - `render` knows nothing of the world: it gets meshes, matrices and plain numbers. - `world` and `people` are one layer: an `Object` owns its `Body`, `Ai` and `Hands`. diff --git a/entropie/Makefile b/entropie/Makefile index 1c21b91..6c4d1b1 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.46.0 +VERSION := 2.47.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index e431334..297555a 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -40,6 +40,11 @@ people living there. - **Side jobs:** mow the Kartoffelacker with the scythe, rescue the protected vineyard snails before the regulatory office comes (or pay a fine), bring Kerry logs to carve into figures with the chainsaw +- **The big old tree:** Enrico from Go-Darmstadt fells it with the + chainsaw. You throw the rope up as high as you can, the others pull it + away from the phone line. Not enough pull and it comes down on the line +- **Injuries:** a falling tree or a hard fall can break an arm or a leg. + The ambulance comes; they're back days later with a cast, and slower - **Money and shopping:** a cash box of 1200 EUR. Send Volkram to the hardware store for boards, nails, screws, paint and food. The stages need that material; the crew fetches it from the store @@ -150,5 +155,5 @@ Dependencies point downward: `main → game → world/people → render → core ## Version -`make version` prints e.g. `entropie 2.46.0 (build 9a90412)`. The build id is +`make version` prints e.g. `entropie 2.47.0 (build eafe545)`. The build id is an md5 of all sources and assets. diff --git a/entropie/STATE.md b/entropie/STATE.md index 037aa72..fb52aa1 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -49,6 +49,8 @@ things the way they do. - Kilian: builds the Obstwiese (psytrance) stage. - Kerry: wood art with the chainsaw (needs logs and the chainsaw). - Julia: quick at every job, very good at building. +- Enrico (Go-Darmstadt, the geo-dome crew): fells the big old tree with the + chainsaw. Not from Berlin: never at the meetings, stays on the ground. - The ES-Bar collective: the food stand. The Darmstadt group: the geo-dome. - Jana: one of the crew (her task not settled yet). - Kurti (the farm is his) and his sons Martin and Jonas: the hosts. @@ -64,7 +66,12 @@ things the way they do. forest cut to beams and boards; nails, screws and paint bought at the hardware store (by car, from the crew's cash box). - Side jobs: mow the Kartoffelacker (scythe); pick up the vineyard snails - (protected) before the regulatory office comes; Kerry's chainsaw art. + (protected) before the regulatory office comes; Kerry's chainsaw art; the + big old tree between the Obstwiese and the Kartoffelacker felled away from + the phone line (Enrico: the chainsaw; a rope thrown up high, the others + pulling). +- Injuries: something heavy on top of somebody, a bad fall from high up - a + broken arm or leg, the ambulance, back days later with a cast (slower). - The "why" questions (an awareness team, Urinellas, "no shirt, no service", ...): found out through what happens on the way; each answer opens an option for the festival. @@ -86,6 +93,7 @@ things the way they do. 6. The "why" questions and their options. (2.45) 7. The festival (guests, the floors running, the storm and the evacuation) (2.46), the clean-up with the supporters, the drive home. + In between: the big old tree and injuries (2.47). ## People @@ -368,6 +376,56 @@ Painted in the rotoscope style of *Waking Life* (2001): - The notebook also has who leads the floors, the cash box and what the weekend is for +### The big old tree (Enrico) + +- Between the Obstwiese and the Kartoffelacker, just south of the gravel + loop, stands a big old tree (about 21 m, a metre through). It leans + towards the road - and along the road's other side runs the phone line on + wooden poles, the tall mast nearest the tree +- Enrico from Go-Darmstadt (the geo-dome crew) turns up Saturday morning of + the first weekend and takes it on in working hours, like the side jobs + (F2, the projects page; your notebook has a line on it): + 1. He looks at it and puts the throw line (a little bag on a long cord) + down at its foot: "A rope up high, then we pull it the other way" + 2. You throw it: pick it up, look up at the crown, T (hold to wind up - + longer: harder), let go. Where it touches the crown it catches over a + branch: the height is said (from about 5 to 16 m). Low? Left click at + the rope's end at the foot: pulled down, throw again. Nobody throws it + for an hour and a half of his time: he does it himself (6 - 7.5 m) + 3. The notch: with the chainsaw (he fetches it), on the side it should + fall to (about 40 minutes of his time) + 4. "Everybody on the rope!": the nearest crew (four at most; not Kurti, + nobody hurt or drunk) go out to the rope's end, 28 m out in the meadow, + and take hold. You can too: left click near the end (walk off: let go) + 5. Three or more on it, nobody standing where it could come down: the back + cut. People in the way get shouted at; after 45 s he cuts anyway. + "It's going - PULL!" - they lean back into it +- Which way it goes: the pull (how many, how fit, how high the rope) and the + notch against the lean and a gust. Plenty of pull: right where it should. + Not quite: off to the side. Not enough: round towards the road - across it + and through the phone line: the wires torn down, the phone company's bill + (1500 EUR from the cash box), Kurti cross, everybody a bit down. Enrico + jumps out of the way to the side it's not turning to +- Lying down it's a big tree to delimb and cut up like any other (big logs) +- The Herberge hears how it went + +### Injuries + +- A falling tree that knocks somebody down breaks an arm or a leg about one + time in three (the big tree: nearly always). Landing hard from high up + (from about 4 m) can break a leg or an arm too +- Hurt, they go down and stay down. The nearest of the crew calls 112; out + here the ambulance takes about 20 minutes (game time). It comes in along + the way in by car - white, a red band, blue lights, the two-tone horn - + stops by the yard, the paramedics carry them in on a stretcher, off it + goes. No saving while it's on its way +- At the hospital (out of sight) for a day and a half - or till the next + weekend, whichever is first - then back with a cast (white, on the forearm + or the shin). An arm in plaster: weaker (lifting), slower at work; a leg: + slow on their feet, hardly a jump. The cast comes off two weekends later +- You: the ambulance takes you; six hours later you're back with the cast +- A guest: that's their festival over + ### The festival (the sixth weekend) - Weekend 6 is the festival. The Herberge before weekend 4 says two more @@ -1552,6 +1610,17 @@ Round 2.46 (the festival): - `game_meeting.c`: the Herberges before it say so (no focus question the last time), nextWeekend: Friday at ten for the festival (FESTIVAL_ARRIVE), no weekend end on it. `game_talk.c`: the storm shout first in the shout menu ("Hey, everybody" calls the crew only). `ai_project.c`: no building once the gates are open. `talk.c`: guests say how they like it. Guests don't come to you with jobs. Dev menu row DEV_FESTIVAL. Notebook: the festival part (`game_festivalNotes`). - Checked (the harness, time sped up): the jump through the last Herberge to Friday 10:00; 36 guests in, camping, tents up, dancing in front of the Hofbuehne; 47 of 50 warned, 36 inside at the worst, 14 out; the all-clear; all 36 gone by Sunday three; the review (62%); the awareness team helping one; a save from Friday night loaded (18 guests back). The real game: through the dev jump to the festival weekend and the gates. +Round 2.47 (the big old tree, injuries): +- `world/site.c`: the big tree (OBJ_TREE variant 2, `keep`: BIG_TRUNK 0.5 m radius, 5 m trunk, 26 layers, 10 m crown; forest trees and bushes within 7 m cleared) at (-45, 73.5); the phone line: three wooden poles (8.5 m, the mast 11 m with two cross arms) along the gravel loop's north side, each a structure plus an invisible OBJ_WALL; the wires one structure (`Shapes_wires`, three wires a span, sagging; torn: hanging to the ground). `Site_bigTreeSafe` / `Lean`, `Site_phoneLine`, `Site_phoneWiresDown`. +- `world/bigtree.c` (where: the rope's tie BigTree_attach, its end BT_ROPE_OUT 28 m out, BT_PULLERS 4 places BT_PULL_GAP 1.3 m apart, the notch / back cut / escape spots); `World.bigtree` (BigTree: step BT_STANDING .. BT_DOWN, work, rope_h, rope_wait, pullers, off, lines_down; saved in the `bigtree` line, mid-cut loaded back to the pulling). +- `cast.c`: Enrico (id 14, `far_away`: no meetings - `game_meeting.c` gather skips him; arrives 21 h), PROJECT_BIG_TREE ("big old tree"). `ai_jobs.c` bigTree: the look and the throw line (OBJ_ROPE), waiting for the rope (ROPE_WAIT 1.5 game h, then 6-7.5 m himself), the notch (BT_NOTCH_WORK 0.8 person-hours with the chainsaw), the pulling (3 at least; nobody within 6 m or within FALL_ZONE 24 m ahead; CLEAR_WAIT 45 s), the back cut (BT_CUT_TIME 22 s, "PULL!" at 16). `ai_onRope` (Object.puller): out to the place, both hands on the rope (`Hands.rope`: hands along it, leaning back when pulling). +- `game/game_bigtree.c`: the throw line caught when it touches the crown's box in the air (rope_h = its height, 4.5 - 16 m); left click at the foot: pulled down again; at the pulling: left click near the end - you on the rope (`Game.on_rope`); pullers called (the nearest crew within 160 m, CALL_NEAR); the fall: margin = pull (0.35 x (0.7 + 0.6 fitness) each) x leverage (rope_h / 10, 0.3 - 1.15) + notch (0.45 x Enrico's tree skill) - LEAN 1.15 - GUST_LEAN 0.8 x gust; >= 0.3: within a few degrees; 0 - 0.3: up to 30; below: round towards the lean (up to 180). `Forestry_fell` that way (the cut 0.6 m up). Down: its line from the stump crossing a span, or passing within 1.2 m of a pole: the lines down (LINE_BILL 1500 EUR, Kurti -0.25, the crew -0.05); else the crew lifted. The rope seen as a structure (`Shapes_rope`), made again when it changes. +- `world/injury.c`: Injury_break (down and staying down, a cry), Injury_roll, Injury_setCast (arm: strength x0.55, tempo x0.75; leg: top speed and pace slower, jump x0.35; `world.c` work rate x0.6 / x0.85). `forestry.c` hit: a falling tree breaks something BREAK_TREE 0.35 (the big one 0.9). `world.c` landFromHigh: a crash landing breaks something smoothstep(9, 13 m/s) x 0.8. Object: injured, injury_side, hospital_until, cast_until, look.cast (all saved). +- `game/game_injury.c`: the ambulance (Game.ambulance: called, AMB_COMES 1200 game s, in along the way at 9 m/s to 12 m short of the car's stop, loading 10 s, out; OBJ_CAR variant 1, `Shapes_ambulance`; the two-tone horn in `sound.c`); the hospital: in Berlin, back after AWAY_HOURS 36 (or with the next weekend) with the cast; CAST_WEEKENDS 2. You: lying 4 s, then six hours on, back at the yard with the cast. A guest: gone home. Saving blocked meanwhile. +- The figure shader: `castLimb` (white plaster over the forearm or the shin). Rain isn't drawn or heard in Berlin any more. +- Dev menu: "The big tree" (Right: Enrico here and at it, the rope up, the notch done). F2 projects page and the notebook: how far it is. +- Checked (the harness): Enrico in, the throw line put down, your throw caught at 7.5 m, the notch, three called to the rope and you on it, the back cut, down 1 degree off; from a save before the pulling with a 4.5 m rope and you off the rope: 21 and 75 degrees off, and once (no pullers) right through the phone line (1500 EUR); Enrico hit by it once (a broken leg - the escape now goes away from the turn); the ambulance in and out, Toni at the hospital and back with a cast on the arm; the casts drawn. + ## Open tasks Most important first. @@ -1567,6 +1636,8 @@ Most important first. - Next: the clean-up (with the supporters: a free ticket for 2 x 6 h of work), the drive home, the end - The festival, still to do: bands and DJs on the stages; the ES-Bar's food stand and the bars; compost toilets for the guests; tickets (money in); guests leaving early when it's bad; the storm blowing tents and pavilions over; the camping's place (check it: now right of the strip of wood, between the chicken halls) - Crowds: lots of "Ouch! My shin" where many people mill about (the shin scrapes) +- The big tree, still to do: the trunk cut up for the stages' frames (it's huge); the phone line mended (now it stays down); Enrico's geo-dome at the Kartoffelacker +- Injuries, still to do: heavy things (logs, crates) falling on somebody; a limp with a leg cast; crutches; somebody sitting with the hurt till the ambulance comes **Bugs** - 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). Note (2.35): the audit is chaotic - each part starts from where the one before left you, so one small change anywhere reshuffles the later parts (a fall in the drunk walk or not, the ladder test not getting on the ladder); compare parts run on their own (the harness: LADDERONLY / JUMPSONLY / JOGONLY). Tried in 2.35: easing the running swing's ends (sprint worse), a swing not hurrying when stopping (no better on its own) @@ -1599,4 +1670,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 2.46.0 (build 9a90412)` +State of `entropie 2.47.0 (build eafe545)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index f247e7f..01204cb 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -23,6 +23,7 @@ uniform vec3 figurePos; uniform vec3 facing; // figure forward, horizontal unit vector uniform int hairStyle; // 0 = short, 1 = long uniform bool longSleeves; +uniform int castLimb; // a plaster cast: 0 none, 1 left forearm, 2 right forearm, 3 left shin, 4 right shin uniform vec3 headForward; // face direction (figure space) uniform vec3 headUp; uniform vec3 chestForward; // front of the torso @@ -68,7 +69,7 @@ uniform float boilTime; uniform sampler2D shadowMap; const int M_SKIN = 0, M_HAIR = 1, M_SHIRT = 2, M_PANTS = 3, M_SHOES = 4, M_PEE = 5, M_SICK = 6, M_WATER = 7, M_CIG = 8, M_JACKET = 9, M_URINELLA = 10; -const int M_FUR = 11, M_FUR_DARK = 12, M_NOSE = 13, M_HAT = 14; +const int M_FUR = 11, M_FUR_DARK = 12, M_NOSE = 13, M_HAT = 14, M_CAST = 15; const float FIGURE_LIGHT_WRAP = 0.6; // soft, flat faces instead of a hard light split const float SELF_SHADOW_SKIP = 0.10; // meters @@ -225,7 +226,8 @@ void buildBody() { vec3 knee = joints[J_KNEE_L + o], ankle = joints[J_ANKLE_L + o], toe = joints[J_TOE_L + o]; vec3 heel = ankle - normalize(toe - ankle) * 0.03 + normalize(ankle - knee) * 0.02; addPart(joints[J_HIP_L + o], knee, 0.085, 0.058, 0.05, 1.0, thigh); - addPart(knee, ankle, 0.055, 0.040, 0.02, 1.0, shin); + if (castLimb == 3 + s) addPart(knee - normalize(ankle - knee) * 0.01, ankle + normalize(ankle - knee) * 0.03, 0.066, 0.056, 0.02, 1.0, M_CAST); // (a cast on the shin) + else addPart(knee, ankle, 0.055, 0.040, 0.02, 1.0, shin); addPart(ankle, toe, 0.045, 0.034, 0.02, 1.0, M_SHOES); addPart(heel, heel, 0.045, 0.045, 0.02, 1.0, M_SHOES); } @@ -234,7 +236,8 @@ void buildBody() { for (int s = 0; s < 2; s++) { int o = s * 4; addPart(joints[J_SHOULDER_L + o], joints[J_ELBOW_L + o], 0.055 + puff, 0.045 + puff, 0.035, 1.0, sleeve); - addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o], 0.044 + puff, 0.034 + puff * 0.5, 0.02, 1.0, forearm); + if (castLimb == 1 + s) addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o] + normalize(joints[J_HAND_L + o] - joints[J_WRIST_L + o]) * 0.03, 0.054, 0.047, 0.02, 1.0, M_CAST); // (a cast on the forearm) + else addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o], 0.044 + puff, 0.034 + puff * 0.5, 0.02, 1.0, forearm); addPart(joints[J_WRIST_L + o], joints[J_HAND_L + o], 0.033, 0.036, 0.015, 1.0, M_SKIN); } @@ -436,6 +439,7 @@ vec3 clothDetail(int mat, vec3 base, vec3 p) { vec3 materialColor(int mat) { if (mat == M_HAIR) return hairColor; if (mat == M_HAT) return hatColor; + if (mat == M_CAST) return vec3(0.94, 0.93, 0.88); if (mat == M_SHIRT) return shirtColor; if (mat == M_PANTS) return pantsColor; if (mat == M_SHOES) return shoeColor; diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 97c18ed..c9bf6fa 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,18 +1,18 @@ -entropie 2.46.0: the festival +entropie 2.47.0: the big old tree, injuries -- Weekend 6 is the festival: the Herberges before it say so, everybody's - on the ground Friday at ten, the gates open at six -- 36 guests come in from the chicken farm's yard (Friday evening, Saturday - late morning), camp right of the strip of wood and pitch their tents -- Floors with at least their deck play, each its own music and times; - guests dance to the beat (dancing is new to the bodies), better floors - are more fun -- Saturday at two the storm warning: get everybody inside (main building, - barn, garages' kitchen) - shout it (X, then 1), the crew help, a floor - with a roof announces it. At its worst, who's still out is counted -- The awareness team, free Urinellas and "no shirt, no service" now do - something at the festival -- Sunday the guests pack up and leave; at four the crew sums it up at the - fire (a result in %); the notebook has a festival part -- Dev menu: jump to the festival and through it -- Saves keep the guests and the festival +- A big old tree between the Obstwiese and the Kartoffelacker leans towards + the road and the phone line along it (poles, a tall mast, wires) +- Enrico from Go-Darmstadt (new, not at the Berlin meetings) fells it: the + throw line at its foot, the notch with the chainsaw, the crew on the rope, + the back cut +- You throw the line up over a branch (T, looking up): the higher it + catches, the better it pulls; pull it down to try again; take hold of the + rope yourself +- Not enough pull, a low rope, a gust: it comes down off to the side or + across the road through the phone line (1500 EUR, Kurti cross) +- Broken arms and legs: under a falling tree, landing hard from high up. + The ambulance comes (lights, two-tone horn), the hospital, back days + later with a cast - weaker or slower for two weekends +- The cast is drawn (white plaster on the forearm or shin) +- No rain drawn or heard in Berlin +- Dev menu: the big tree step by step diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 48318ea..2bbc3fe 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -379,6 +379,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { game->quest_t += dt; if (!game->meeting.kind) game_why(game, dt); game_festival(game, dt); + if (!game->meeting.kind) { game_bigTree(game, dt); game_injury(game, dt); } Ai_setSettled(game->arrival.task == TASK_NONE); tooHeavy(game); bool warm = game->player->look.top == TOP_JACKET; diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 8afea56..72b1cc9 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -66,6 +66,16 @@ typedef struct { bool skipping; /* Backspace: the rest of it at once, the first answers */ } Meeting; +/* The ambulance (game_injury.c): called, on its way in, parked (the paramedics), on its way out. */ +typedef enum { AMB_NONE, AMB_CALLED, AMB_IN, AMB_PARKED, AMB_OUT } AmbulanceStage; +typedef struct { + int stage; + struct Object *car; /* (while it's here) */ + struct Object *who; /* who it's for */ + float t; /* game s since called; real s parked */ + float s; /* m along the way in */ +} Ambulance; + /* The festival weekend (game_festival.c): the last weekend on the ground, the guests, the storm. */ typedef struct { int stage; /* FEST_* (world.h) */ @@ -150,6 +160,12 @@ typedef struct { bool show_notes; /* your notebook (J): the festival, the why questions */ float why_check; /* until the next look round for something that makes you wonder (game_why.c) */ Festival festival; /* the festival weekend (game_festival.c) */ + Ambulance ambulance; /* somebody hurt: the ambulance (game_injury.c) ... */ + float you_hurt_t; /* ... you hurt: lying there this long */ + bool on_rope; /* you on the big tree's rope (game_bigtree.c) ... */ + struct Object *rope_look; /* ... the rope as it's seen (made again when it changes) ... */ + int rope_shown; + bool wires_shown; /* ... the phone line's wires shown torn */ } Game; /* A line of speech above someone's head, in screen pixels (y up from the bottom). */ diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index c79678c..1cef801 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -1327,7 +1327,7 @@ static int leftClick(Game *game) { if (pickSnail(game)) return CLICK_NONE; /* (a snail in the grass: picked up, not mown) */ int mow = startMowing(game); if (mow >= 0) return mow; - if (game_unloadCar(game) || mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || takePullover(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE; + if (game_unloadCar(game) || game_bigTreeClick(game) || mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || takePullover(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE; int watering = useWater(game); if (watering >= 0) return watering; if (bagTrash(game)) return CLICK_BAG; diff --git a/entropie/src/game/game_bigtree.c b/entropie/src/game/game_bigtree.c new file mode 100644 index 0000000..c28d558 --- /dev/null +++ b/entropie/src/game/game_bigtree.c @@ -0,0 +1,311 @@ +/* + * The big old tree (world/bigtree.h): what the game does around Enrico's work + * (ai_jobs.c bigTree) and the pullers (ai_onRope). + * + * - The throw line: lying at the tree's foot. Thrown (T, wound up, looking up) + * so it touches the crown, it's caught over a branch where it touched: the + * higher, the better it pulls. Left click at the rope's end: pulled down + * again to try once more (before the pulling starts). + * - The pulling: when the notch is in, the crew nearest are called to the + * rope's end (four at most); you can take hold of it too (left click near + * the end). Enrico cuts once three or more are on it. + * - Which way it goes: the pull (how many, how strong, how high the rope) and + * the notch against the lean (towards the road) and a gust. Way off, it + * comes down across the road onto the phone line: torn, a bill from the + * phone company, Kurti cross. Anybody it comes down on: injured (Forestry). + */ + +#include <math.h> + +#include "game/game_internal.h" +#include "world/bigtree.h" +#include "world/cast.h" +#include "world/forestry.h" +#include "world/injury.h" + +#define LINE_BILL 1500.0f /* EUR: the phone company puts the line back up */ +#define PULL_EACH 0.35f /* each on the rope (x how strong: fitness) ... */ +#define NOTCH_PULL 0.45f /* ... the notch steering it too ... */ +#define LEAN 1.15f /* ... against the lean, and a gust on top */ +#define GUST_LEAN 0.8f +#define ROPE_TOP 16.0f /* m: caught higher than this is no better */ +#define CALL_NEAR 160.0f /* m: the crew this near get called to the rope */ +#define ON_ROPE 3.0f /* m: you near the rope's end: on it (left click) */ +#define PLACE_NEAR 1.2f /* m: a puller at their place counts */ + +static Object *named(const Game *game, const char *name) { + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->is_character && !strcmp(o->name, name)) return o; + } + return NULL; +} + +/* ---------- The throw line ---------- */ + +static void throwLine(Game *game) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + Object *tree = BigTree_tree(w); + if (!tree || tree->toppled || bt->step < BT_ROPE || bt->rope_h > 0.0f || tree->collider_count < 2) return; + for (int i = 0; i < w->count; i++) { + Object *bag = w->objects[i]; + if (bag->type != OBJ_ROPE || bag->broken || bag->is_held || bag->is_grounded) continue; + Vec3 c = Object_colliderCenter(tree, 1), half = tree->colliders[1].half_extents; + Vec3 p = bag->position; + if (fabsf(p.x - c.x) > half.x + 0.1f || fabsf(p.z - c.z) > half.z + 0.1f || p.y < c.y - half.y || p.y > c.y + half.y) continue; + bt->rope_h = fminf(p.y - Terrain_height(&w->terrain, tree->position.x, tree->position.z), ROPE_TOP); + bt->rope_h = fmaxf(bt->rope_h, 4.5f); + Object_useUp(bag); + static char line[128]; + snprintf(line, sizeof line, "Caught over a branch - %.1f m up!%s", bt->rope_h, + bt->rope_h < 7.0f ? " A bit low (left click the rope's end: pull it down, throw again)" : ""); + say(game, line); + game->message_time = 5.0f; + Object *e = named(game, "Enrico"); + if (e && !Game_inBerlin(e)) Ai_say(e, bt->rope_h >= 10.0f ? "Yes! Way up there - that pulls like a dream." : bt->rope_h >= 7.0f ? "Good throw! That'll do nicely." + : "Hm, low. It'll hold - but higher pulls better."); + return; + } +} + +/* ---------- The rope, as it's seen ---------- */ + +static void showRope(Game *game) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + int shown = bt->rope_h <= 0.0f || bt->step >= BT_FALLING ? 0 : bt->step >= BT_PULL ? 2 : 1; + int key = shown * 1000 + (int)(bt->rope_h * 10.0f); + if (key == game->rope_shown) return; + game->rope_shown = key; + if (game->rope_look) { Object_useUp(game->rope_look); game->rope_look = NULL; } + if (!shown) return; + Vec3 a = BigTree_attach(w); + Vec3 end = shown == 2 ? BigTree_ropeEnd(w) : vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f)); + if (shown == 1) end.y = Terrain_height(&w->terrain, end.x, end.z) + 0.02f; + Mesh m = Shapes_rope(vec3_zero(), vec3_sub(end, a), shown == 2 ? 0.3f : 0.0f, shown == 1); + if (!m.vao) return; + Object *look = Object_createStructure(m, a, 0.0f, vec3(BT_ROPE_OUT, 12.0f, BT_ROPE_OUT)); + if (look && World_add(w, look)) game->rope_look = look; +} + +bool game_bigTreeClick(Game *game) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + Object *p = game->player; + if (bt->rope_h <= 0.0f || bt->step >= BT_FALLING || Hands_freeHand(p) < 0) return false; + if (bt->step < BT_PULL) { /* the rope hanging down by the trunk: pulled down again, to throw it once more */ + Vec3 end = vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f)); + if (hypotf(end.x - p->position.x, end.z - p->position.z) > 2.0f) return false; + bt->rope_h = 0.0f; + bt->rope_wait = 0.0f; + if (bt->step == BT_NOTCH && bt->work <= 0.0f) bt->step = BT_ROPE; + Vec3 at = vec3_add(p->position, vec3_scale(Object_facing(p), 0.6f)); + at.y = World_groundHeight(w, at.x, at.z, p->position.y + 1.0f) + 0.02f; + Object *bag = Object_create(OBJ_ROPE, PALETTE_WARM, at, vec3_zero(), 0.0f); + if (bag && !World_add(w, bag)) bag = NULL; + say(game, "You pull the line down again - pick it up and throw it once more"); + return true; + } + Vec3 end = BigTree_ropeEnd(w); /* the pulling: hold of the rope's end */ + if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f) return false; + game->on_rope = !game->on_rope; + say(game, game->on_rope ? "You take hold of the rope - when it goes, you pull (walk off: let go)" : "You let go of the rope"); + return true; +} + +/* ---------- The pulling ---------- */ + +static bool canPull(const Object *o) { + return o->type == OBJ_HUMAN && o->cast_id > 0 && strcmp(o->name, "Enrico") && strcmp(o->name, "Kurti") && !o->injured && o->hospital_until <= 0.0f + && Injury_castKind(o) == INJ_NONE && !Body_isDown(&o->body) && !o->in_tent && o->indoors <= 0.0f && !o->riding && !o->ai.meeting + && !Game_inBerlin(o) && o->drunk < 0.6f; +} + +static void pullers(Game *game, const Object *enrico) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + bool on = ((bt->step == BT_PULL || bt->step == BT_CUT) && enrico && enrico->ai.on_project) || bt->step == BT_FALLING; /* (falling: they hold on till it's down) */ + int taken[BT_PULLERS] = { 0 }, n = 0; + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (o->puller <= 0.0f) continue; + if (!on || !canPull(o)) { o->puller = 0.0f; continue; } + taken[(int)o->puller - 1] = 1; + if (vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR) n++; + } + Object *p = game->player; + if (game->on_rope) { + Vec3 end = BigTree_ropeEnd(w); + if (!on && bt->step != BT_FALLING) game->on_rope = false; + else if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f || Body_isDown(&p->body)) { game->on_rope = false; say(game, "You let go of the rope"); } + else { + n++; + p->hands.rope = bt->step == BT_CUT && bt->work >= BT_CUT_GOES - 3.0f ? 1.0f : 0.05f; + p->hands.rope_at = BigTree_ropeAt(w, p->position); + p->hands.rope_dir = vec3_normalize(vec3_sub(BigTree_ropeEnd(w), BigTree_attach(w))); + } + } + if (bt->step != BT_FALLING) bt->pullers = n; + if (!on) return; + for (int k = 0; k < BT_PULLERS; k++) { /* free places: the nearest who can come */ + if (taken[k]) continue; + Object *best = NULL; + float best_d = CALL_NEAR; + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (o->puller > 0.0f || !canPull(o) || o->ai.mode == AI_FOLLOW || o->ai.talking) continue; + float d = hypotf(o->position.x - Site_bigTree().x, o->position.z - Site_bigTree().z); + if (d < best_d) { best_d = d; best = o; } + } + if (!best) break; + best->puller = (float)(k + 1); + best->ai.on_project = false; + best->ai.mode = AI_IDLE; + Ai_say(best, k == 0 ? "Coming - I'll take the end of the rope!" : "I'm on it!"); + } +} + +/* ---------- Down it comes ---------- */ + +static float gauss(World *w) { /* (-1..1, mostly near 0) */ + float s = 0.0f; + for (int k = 0; k < 3; k++) { w->rng = w->rng * 1664525u + 1013904223u; s += (float)(w->rng >> 8) / 16777216.0f; } + return (s - 1.5f) / 1.5f; +} + +static void fell(Game *game, Object *tree, Object *enrico) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + float pull = 0.0f; + for (int i = 0; i < w->count; i++) { + const Object *o = w->objects[i]; + if (o->puller > 0.0f && vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR + 0.5f) + pull += PULL_EACH * (0.7f + 0.6f * o->fitness); + } + if (game->on_rope) pull += PULL_EACH * (0.7f + 0.6f * game->player->fitness); + float leverage = clampf(bt->rope_h / 10.0f, 0.3f, 1.15f); + float notch = NOTCH_PULL * (enrico ? 0.6f + 0.4f * enrico->skill[ORDER_TREES] : 0.7f); + float margin = pull * leverage + notch - LEAN - GUST_LEAN * w->gust; + float side = gauss(w) >= 0.0f ? 1.0f : -1.0f; + float off = margin >= 0.3f ? 6.0f * gauss(w) + : margin >= 0.0f ? (6.0f + (0.3f - margin) * 80.0f) * gauss(w) + : side * fminf(180.0f, -margin / 0.5f * 180.0f + 25.0f * fabsf(gauss(w))); /* (not enough: round towards the lean) */ + bt->off = off; + Vec3 safe = Site_bigTreeSafe(); + float a = off * PI_F / 180.0f; + Vec3 dir = vec3(safe.x * cosf(a) - safe.z * sinf(a), 0.0f, safe.x * sinf(a) + safe.z * cosf(a)); + Forestry_fell(w, tree, dir, 0.6f); + for (int i = 0; i < w->count; i++) if (w->objects[i]->puller > 0.0f) w->objects[i]->ai.speech_time = 0.0f; + if (enrico) { /* (out of the way: back and to the side) */ + enrico->ai.errand = true; + enrico->ai.errand_there = false; + enrico->ai.errand_to = BigTree_escapeSpot(w, off > 0.0f ? -1.0f : 1.0f); /* (away from the way it's turning) */ + enrico->ai.errand_with = NULL; + enrico->carving = false; + Ai_say(enrico, fabsf(off) < 30.0f ? "TIMBEEER!" : "Watch out - it's turning! Get away!"); + } + say(game, "TIMBEEER! The big tree comes down"); +} + +static bool crosses(Vec3 a, Vec3 b, Vec3 c, Vec3 d) { /* (two stretches on the ground cross) */ + float d1 = (b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x), d2 = (b.x - a.x) * (d.z - a.z) - (b.z - a.z) * (d.x - a.x); + float d3 = (d.x - c.x) * (a.z - c.z) - (d.z - c.z) * (a.x - c.x), d4 = (d.x - c.x) * (b.z - c.z) - (d.z - c.z) * (b.x - c.x); + return d1 * d2 < 0.0f && d3 * d4 < 0.0f; +} + +static void down(Game *game, Object *tree) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + Vec3 t = Site_bigTree(), tip = vec3_add(t, vec3_scale(tree->fall_dir, 21.0f)); + Vec3 pole[4]; + int n = Site_phoneLine(pole, 4); + for (int k = 0; k + 1 < n; k++) if (crosses(t, tip, pole[k], pole[k + 1])) bt->lines_down = true; + for (int k = 0; k < n; k++) /* (into a pole) */ + if (vec3_length(vec3_cross(vec3_sub(vec3(pole[k].x, 0, pole[k].z), vec3(t.x, 0, t.z)), tree->fall_dir)) < 1.2f + && vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) > 0.0f && vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) < 21.0f) bt->lines_down = true; + bt->step = BT_DOWN; + bt->told = true; + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (o->puller > 0.0f) o->puller = 0.0f; + if (o->is_character && o->cast_id > 0 && !strcmp(o->name, "Enrico")) o->ai.errand = false; + } + game->on_rope = false; + static char line[192]; + Object *kurti = named(game, "Kurti"); + if (bt->lines_down) { + Site_phoneWiresDown(w, true); + w->cash -= LINE_BILL; + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (o->is_character && o->cast_id > 0) o->commitment = clampf(o->commitment - (o == kurti ? 0.25f : 0.05f), 0.0f, 1.0f); + } + if (kurti) Ai_say(kurti, "My phone line! Forty years that line stood. Who pays for that?!"); + snprintf(line, sizeof line, "It came down across the road - right through the phone line. The phone company puts it back up: %d EUR from the cash box", + (int)LINE_BILL); + } else { + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (o->is_character && o->cast_id > 0) o->commitment = fminf(1.0f, o->commitment + (!strcmp(o->name, "Enrico") ? 0.1f : 0.04f)); + } + if (kurti && fabsf(bt->off) < 40.0f) Ai_say(kurti, "Hah! Right where you said. Well pulled, all of you."); + snprintf(line, sizeof line, fabsf(bt->off) < 15.0f ? "Down - right where it should be. Well pulled!" : "Down - %.0f degrees off, but clear of the phone line. Phew.", + fabsf(bt->off)); + } + say(game, line); + game->message_time = 8.0f; +} + +void game_bigTree(Game *game, float dt) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + (void)dt; + if (bt->lines_down != game->wires_shown) { Site_phoneWiresDown(w, bt->lines_down); game->wires_shown = bt->lines_down; } + Object *tree = BigTree_tree(w); + if (!tree) return; + Object *enrico = named(game, "Enrico"); + throwLine(game); + pullers(game, enrico); + if (bt->step == BT_FALLING && !tree->toppled) fell(game, tree, enrico); + if (bt->step == BT_FALLING && tree->toppled && tree->topple >= 1.0f) down(game, tree); + showRope(game); +} + +const char *game_bigTreeStatus(const Game *game) { + const BigTree *bt = &game->world.bigtree; + static char text[96]; + switch (bt->step) { + case BT_STANDING: return "standing (Enrico hasn't started)"; + case BT_ROPE: return "the rope up (throw the line from its foot)"; + case BT_NOTCH: snprintf(text, sizeof text, "the notch, %d%% (rope %.1f m up)", (int)(100.0f * bt->work / BT_NOTCH_WORK), bt->rope_h); return text; + case BT_PULL: snprintf(text, sizeof text, "on the rope: %d of 3 (rope %.1f m up)", bt->pullers, bt->rope_h); return text; + case BT_CUT: case BT_FALLING: return "coming down!"; + default: return bt->lines_down ? "down - and the phone line too" : "down, the phone line fine"; + } +} + +/* Dev: on to the next step (Enrico here and at it; the rope up; the notch in). */ +void game_bigTreeJump(Game *game) { + World *w = &game->world; + BigTree *bt = &w->bigtree; + Object *e = named(game, "Enrico"); + if (!e) { + for (int id = 1; id < Cast_count() && !e; id++) + if (!strcmp(Cast_get(id)->name, "Enrico")) { + Vec3 at = BigTree_notchSpot(w); + at.x += 2.0f; + e = Cast_create(id, at); + if (e && !World_add(w, e)) e = NULL; + } + } + if (e && Game_inBerlin(e)) return; + if (e) { e->ai.on_project = true; e->ai.project_off = 0.0f; e->ai.greet = false; } + if (bt->step == BT_STANDING) return; /* (he starts it: the throw line put down) */ + if (bt->step == BT_ROPE && bt->rope_h <= 0.0f) { + for (int i = 0; i < w->count; i++) if (w->objects[i]->type == OBJ_ROPE && !w->objects[i]->broken) Object_useUp(w->objects[i]); + bt->rope_h = 10.0f; + return; + } + if (bt->step == BT_NOTCH) bt->work = BT_NOTCH_WORK; +} diff --git a/entropie/src/game/game_dev.c b/entropie/src/game/game_dev.c index ef85fac..acfe07c 100644 --- a/entropie/src/game/game_dev.c +++ b/entropie/src/game/game_dev.c @@ -10,7 +10,7 @@ #include "world/weather.h" typedef enum { - DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_WIND, DEV_START, DEV_FESTIVAL, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY, + DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_WIND, DEV_START, DEV_FESTIVAL, DEV_BIGTREE, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY, DEV_BLADDER, DEV_HUNGER, DEV_THIRST, DEV_SLEEPY, DEV_STRENGTH, DEV_ANNOYED, DEV_PRIDE, DEV_LIVELY, DEV_NERVES, DEV_CLUMSY, DEV_WITS, DEV_HEIGHTS, DEV_WILLING, DEV_COMMIT, DEV_LIKES, /* likes for each job ... */ @@ -121,6 +121,10 @@ void game_dev(Game *game, const Input *input) { if (dir > 0) game_skipArrival(game); return; } + if (game->dev_row == DEV_BIGTREE) { + if (dir > 0) game_bigTreeJump(game); + return; + } if (game->dev_row == DEV_FESTIVAL) { if (dir > 0) game_festivalJump(game); return; @@ -188,6 +192,7 @@ static void devLine(const Game *game, const Object *o, int row, char *out) { case DEV_WEATHER: snprintf(out, 96, "Weather: %s (raining %.0f%%, wet %.0f%%)", Weather_name((WeatherKind)game->world.weather), game->world.rain * 100.0f, game->world.wet * 100.0f); break; case DEV_WIND: snprintf(out, 96, "Wind: %s (%.0f m/s)", Weather_windName((WindKind)game->world.wind_kind), Weather_windSpeed(&game->world)); break; + case DEV_BIGTREE: snprintf(out, 96, "The big tree: %s (on ->)", game_bigTreeStatus(game)); break; case DEV_FESTIVAL: snprintf(out, 96, "The festival: %s", game_festivalJumpText(game)); break; case DEV_START: snprintf(out, 96, "The start: %s", game->arrival.task == TASK_NONE && game->arrival.stage != ARRIVAL_DRIVING ? "done" : "skip it ->"); break; case DEV_FEMALE: snprintf(out, 96, "Female: %s", o->female ? "yes" : "no"); break; diff --git a/entropie/src/game/game_injury.c b/entropie/src/game/game_injury.c new file mode 100644 index 0000000..e4018e0 --- /dev/null +++ b/entropie/src/game/game_injury.c @@ -0,0 +1,209 @@ +/* + * Broken bones (world/injury.h): somebody lies there hurt - an ambulance is + * called. A while later it comes in along the way in by car (lights, siren), + * stops by the yard, the paramedics fetch them, off it goes. They're at the + * hospital (out of sight: in Berlin with the others between weekends) and + * come back a day and a half later - or with the next weekend, whichever is + * first - with a cast (an arm: weaker, slower at work; a leg: slow on their + * feet). The cast comes off two weekends on. + * + * You: the ambulance takes you; six hours later you're back with a cast. + * A guest: the ambulance takes them, and that's their festival over. + */ + +#include <math.h> + +#include "game/game_internal.h" +#include "world/car.h" +#include "world/cast.h" +#include "world/injury.h" +#include "world/manor.h" + +#define AMB_COMES 1200.0f /* game s till it's here (out in the country) */ +#define AMB_SPEED 9.0f /* m/s along the way in */ +#define AMB_STOP 12.0f /* m short of where your car stops */ +#define AMB_LOADING 10.0f /* real s the paramedics take */ +#define AWAY_HOURS 36.0f /* story hours at the hospital (a cast on, then back) */ +#define YOU_AWAY 6.0f /* ... you: hours */ +#define CAST_WEEKENDS 2 /* the cast comes off this many weekends on */ +#define YOU_WAIT 4.0f /* real s lying there before you're taken */ + +static const char *limb(int kind) { return kind == INJ_ARM ? "arm" : "leg"; } + +static void putOnGround(Game *game, Object *o) { + Vec3 path[16]; + int n = 0; + float yaw; + Site_arrivalPath(path, &n, &yaw); + Vec3 at = n > 4 ? path[4] : Site_yard(); + at.y = World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f); + o->sitting = false; + o->seat = NULL; + o->ai.meeting = false; + o->position = at; + o->velocity = o->movement = vec3_zero(); + o->body.replant = true; + Object_syncColliders(o); +} + +/* Back from the hospital: the cast on. */ +static void back(Game *game, Object *o, bool to_ground) { + int kind = o->injured ? o->injured : INJ_LEG; + Injury_setCast(o, kind, o->injury_side, true); + o->injured = INJ_NONE; + o->hospital_until = 0.0f; + o->cast_until = game->weekend + CAST_WEEKENDS; + if (to_ground) putOnGround(game, o); + if (o->type == OBJ_HUMAN) Ai_say(o, kind == INJ_ARM ? "I'm back! Arm in plaster - I'll do what I can one-handed." : "Back again! Slow on this leg, but I'm here."); + snprintf(game->story_line, sizeof game->story_line, "%s %s back from the hospital - with a cast on the %s", o->type == OBJ_PLAYER ? "You" : o->name, + o->type == OBJ_PLAYER ? "are" : "is", limb(kind)); + say(game, game->story_line); + game->message_time = 6.0f; +} + +/* You: taken to the hospital, back hours later with the cast. */ +static void youHurt(Game *game, float dt) { + Object *p = game->player; + if (!p->injured) { game->you_hurt_t = 0.0f; return; } + if (game->you_hurt_t == 0.0f) { + snprintf(game->story_line, sizeof game->story_line, "Your %s - it's broken. Somebody calls the ambulance...", limb(p->injured)); + say(game, game->story_line); + game->message_time = YOU_WAIT + 1.0f; + } + if ((game->you_hurt_t += dt) < YOU_WAIT) return; + game->you_hurt_t = 0.0f; + float add = YOU_AWAY; + game->story_hours += add; + game->daycycle.seconds = fmodf(game->daycycle.seconds + add * 3600.0f, 24.0f * 3600.0f); + p->body.down_time = 0.0f; + p->body.pose = POSE_UP; + p->body.replant = true; + Vec3 at = Site_yard(); + at.y = World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f); + p->position = at; + p->velocity = p->movement = vec3_zero(); + Object_syncColliders(p); + game->on_rope = false; + back(game, p, false); +} + +static Object *waiting(const Game *game) { + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->type == OBJ_HUMAN && o->injured && o->hospital_until <= 0.0f && !o->gone_home) return o; + } + return NULL; +} + +static Object *crewNear(const Game *game, const Object *hurt) { + Object *best = NULL; + float best_d = 60.0f; + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o == hurt || o->type != OBJ_HUMAN || o->cast_id <= 0 || o->injured || Body_isDown(&o->body) || Game_inBerlin(o)) continue; + float d = hypotf(o->position.x - hurt->position.x, o->position.z - hurt->position.z); + if (d < best_d) { best_d = d; best = o; } + } + return best; +} + +static void ambulance(Game *game, float dt) { + Ambulance *a = &game->ambulance; + World *w = &game->world; + switch (a->stage) { + case AMB_NONE: { + Object *hurt = waiting(game); + if (!hurt) return; + a->who = hurt; + a->stage = AMB_CALLED; + a->t = 0.0f; + Object *c = crewNear(game, hurt); + if (c) Ai_say(c, "Don't move - I'm calling 112! The ambulance is on its way."); + snprintf(game->story_line, sizeof game->story_line, "%s is hurt - a broken %s. The ambulance is called; out here it takes a while", hurt->name, limb(hurt->injured)); + say(game, game->story_line); + game->message_time = 6.0f; + return; + } + case AMB_CALLED: + if ((a->t += w->game_dt) < AMB_COMES || !game_drivePath(game)) return; + { + Vec3 pos, dir; + game_driveAt(&game->arrival, 0.0f, &pos, &dir); + Object *car = Car_create(pos, yaw_facing(dir)); + if (!car) return; + car->variant = 1; + Mesh_destroy(&car->mesh); + car->mesh = Shapes_ambulance(); + snprintf(car->name, sizeof car->name, "Ambulance"); + if (!World_add(w, car)) return; + a->car = car; + a->s = 0.0f; + a->stage = AMB_IN; + say(game, "Blue lights on the gravel road - the ambulance is here"); + } + return; + case AMB_IN: case AMB_OUT: { + float stop = game->arrival.length - AMB_STOP; + a->s += (a->stage == AMB_IN ? 1.0f : -1.0f) * AMB_SPEED * dt; + if (a->stage == AMB_IN && a->s >= stop) { a->s = stop; a->stage = AMB_PARKED; a->t = 0.0f; } + Vec3 pos, dir; + game_driveAt(&game->arrival, a->s, &pos, &dir); + Car_place(w, a->car, pos, yaw_facing(a->stage == AMB_OUT ? vec3_scale(dir, -1.0f) : dir)); + if (a->stage == AMB_OUT && a->s <= 0.0f) { /* (gone: to the hospital) */ + Object *o = a->who; + if (o && o->riding == a->car) Car_getOut(o); + if (o && o->guest) { o->gone_home = true; o->inside_door = pos; o->indoors = 1e7f; o->injured = INJ_NONE; } + else if (o) { + o->hospital_until = game->story_hours + AWAY_HOURS; + o->position = vec3(BERLIN_X + BERLIN_BAR + 1.5f, 0.05f, 2.0f); + o->velocity = o->movement = vec3_zero(); + o->ai.meeting = true; + Object_syncColliders(o); + } + Object_useUp(a->car); + memset(a, 0, sizeof *a); + } + return; + } + case AMB_PARKED: { + Object *o = a->who; + if ((a->t += dt) < AMB_LOADING) return; + if (o && !o->riding) { + o->body.down_time = 0.0f; /* (on the stretcher, in) */ + o->body.pose = POSE_UP; + Car_getIn(a->car, o, 2); + snprintf(game->story_line, sizeof game->story_line, "The paramedics carry %s to the ambulance on a stretcher", o->name); + say(game, game->story_line); + } + a->stage = AMB_OUT; + return; + } + default: return; + } +} + +void game_injury(Game *game, float dt) { + World *w = &game->world; + if (game->meeting.kind) return; + youHurt(game, dt); + ambulance(game, dt); + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (!o->is_character || o->type == OBJ_DOG) continue; + if (o->hospital_until > 0.0f) { /* at the hospital: back when it's time (or the next weekend brought them) */ + if (!Game_inBerlin(o)) back(game, o, false); + else if (game->story_hours >= o->hospital_until) back(game, o, true); + } + if (o->look.cast && game->weekend >= o->cast_until && o->cast_until > 0) { + int kind = Injury_castKind(o), side = (o->look.cast - 1) % 2; + Injury_setCast(o, kind, side, false); + o->cast_until = 0; + if (!Game_inBerlin(o)) { + snprintf(game->story_line, sizeof game->story_line, "%s%s cast is off - good as new", o->type == OBJ_PLAYER ? "Your" : o->name, o->type == OBJ_PLAYER ? "" : "'s"); + say(game, game->story_line); + } + } + } +} + +bool game_ambulanceHere(const Game *game) { return game->ambulance.stage >= AMB_IN; } diff --git a/entropie/src/game/game_internal.h b/entropie/src/game/game_internal.h index 1ba4a87..d58c919 100644 --- a/entropie/src/game/game_internal.h +++ b/entropie/src/game/game_internal.h @@ -50,6 +50,15 @@ void game_why(Game *game, float dt); /* game_festival.c: the festival weekend (the last on the ground): every frame; is it this weekend (or after); the weekend starting (Friday morning); dev: on to it, or on to what's next in it; the storm shouted; the notebook's lines about it. */ +/* game_bigtree.c: the big old tree around Enrico's work - the throw line, the rope, the pullers, which way it falls; + left click (the rope: pull it down again / take hold); how far it is (said plainly). */ +void game_bigTree(Game *game, float dt); +bool game_bigTreeClick(Game *game); +const char *game_bigTreeStatus(const Game *game); +void game_bigTreeJump(Game *game); +/* game_injury.c: somebody hurt - the ambulance, the hospital, back with a cast, the cast off; the ambulance here (no saving). */ +void game_injury(Game *game, float dt); +bool game_ambulanceHere(const Game *game); #define FESTIVAL_WEEKEND 6 void game_festival(Game *game, float dt); bool game_festivalWeekend(const Game *game); diff --git a/entropie/src/game/game_meeting.c b/entropie/src/game/game_meeting.c index 967ba1c..dbd3f78 100644 --- a/entropie/src/game/game_meeting.c +++ b/entropie/src/game/game_meeting.c @@ -164,7 +164,7 @@ static void gather(Game *game) { } else { for (int id = 1; id < Cast_count(); id++) { const CastMember *c = Cast_get(id); - if (c->lives_here) continue; + if (c->lives_here || c->far_away) continue; /* (the hosts, and Enrico: Darmstadt's far) */ Object *o = castMember(game, id); if (!o) { /* (not on the ground yet: here they are, for the Herberge) */ o = Cast_create(id, tableAt(MEET_HERBERGE)); @@ -514,7 +514,16 @@ static bool herbergeLater(Game *game, int step) { option(game, 1, "Yes - another 20 each (400 EUR)"); option(game, 0, "No, we'll manage"); return true; - case N_FLOORS + 8: line(game, "Volkram", last ? "See you Friday morning. Our festival!" : "Friday at five at Kurti's. See you there!"); return true; + case N_FLOORS + 8: { /* the big old tree, once it's started */ + const BigTree *bt = &game->world.bigtree; + if (bt->step == BT_STANDING || game->festival.stage) { game->meeting.wait = 0.0f; return true; } + if (bt->step < BT_FALLING) snprintf(text, sizeof text, "Enrico says the big tree's next weekend. %s", bt->rope_h > 0.0f ? "The rope's up already." : "Somebody has to get the rope up."); + else if (bt->lines_down) snprintf(text, sizeof text, "The big tree's down - and the phone line with it. Kurti's still cross."); + else snprintf(text, sizeof text, "The big tree's down, the phone line's fine. Good work on that rope!"); + line(game, "Volkram", text); + return true; + } + case N_FLOORS + 9: line(game, "Volkram", last ? "See you Friday morning. Our festival!" : "Friday at five at Kurti's. See you there!"); return true; default: return false; } } diff --git a/entropie/src/game/game_story.c b/entropie/src/game/game_story.c index 579183a..6723d18 100644 --- a/entropie/src/game/game_story.c +++ b/entropie/src/game/game_story.c @@ -120,6 +120,7 @@ void game_story(Game *game, float game_dt) { o->ai.greet_left = HELLO_TIME; o->ai.side = (float)(come % 3 - 1); if (c->leads == PROJECT_SNAILS) Ai_say(o, "Hi all! Mind the vineyard snails - they're protected, and the office comes soon!"); + else if (c->leads == PROJECT_BIG_TREE) Ai_say(o, "Servus! Enrico, Go-Darmstadt. Where's this big tree you told me about?"); else Ai_say(o, come == 0 ? "Hello everybody - we're here!" : "Hi! Where's the fire?"); size_t len = 0; while (names[len]) len++; @@ -216,6 +217,7 @@ int Game_crewLines(const Game *game, char lines[][128], int max, const char **ti } if (p == PROJECT_WOOD_ART) snprintf(built, sizeof built, " [%d figure%s; %s]", Field_artPieces(w), Field_artPieces(w) == 1 ? "" : "s", Field_artLog(w) ? "a log there" : "no log"); + if (p == PROJECT_BIG_TREE) snprintf(built, sizeof built, " [%s]", game_bigTreeStatus(game)); const Object *s = Stage_of(game->world.objects, game->world.count, (Project)p); if (s && s->build_stage >= BUILD_DONE) snprintf(built, sizeof built, " [built]"); else if (s) { diff --git a/entropie/src/game/game_why.c b/entropie/src/game/game_why.c index cbe39ae..861c414 100644 --- a/entropie/src/game/game_why.c +++ b/entropie/src/game/game_why.c @@ -96,6 +96,8 @@ int Game_notesLines(const Game *game, char lines[][128], int max, const char **t if (n < max) snprintf(lines[n++], 128, "%s", who); static const char *const FOCUS[] = { "-", "the floors", "the side jobs", "a party" }; if (n < max) snprintf(lines[n++], 128, "Cash box: %d EUR Weekend %d, for: %s", (int)w->cash, game->weekend, FOCUS[(unsigned)game->focus < 4 ? game->focus : 0]); + if (n < max && (w->bigtree.step > BT_STANDING || Cast_leadOf(w->objects, w->count, PROJECT_BIG_TREE))) + snprintf(lines[n++], 128, "The big old tree (Enrico): %s", game_bigTreeStatus(game)); if (n < max) snprintf(lines[n++], 128, " "); if (n < max) snprintf(lines[n++], 128, "Why do festivals do that?"); for (int k = 0; k < WHY_COUNT && n < max; k++) { diff --git a/entropie/src/game/save.c b/entropie/src/game/save.c index 54fb12a..2846a41 100644 --- a/entropie/src/game/save.c +++ b/entropie/src/game/save.c @@ -78,7 +78,8 @@ static const Field PERSON[] = { IN("ai_mode", ai.mode), IN("ai_order", ai.order), VE("ai_area", ai.work_area), VE("ai_home", ai.home), RE("ai_woodpile", ai.woodpile), BY("ai_banned", ai.banned), BY("ai_defies", ai.defies), BO("ai_unload", ai.unload), BO("ai_greet", ai.greet), FL("willing", ai.character.willing), IN("snails", snails), - IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("storm_told", storm_told), + IN("injured", injured), IN("injury_side", injury_side), FL("hospital_until", hospital_until), IN("cast_until", cast_until), + IN("cast_limb", look.cast), IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("storm_told", storm_told), VE("inside_door", inside_door), }; @@ -148,6 +149,7 @@ const char *Save_blocked(const Game *game) { if (game->arrival.stage == ARRIVAL_DRIVING) return "Not while you're driving in"; if (game->meeting.kind || game->meeting.fading) return "Not during a meeting in Berlin"; if (game->player && game->player->riding) return "Not while you're in the car"; + if (game->ambulance.stage != AMB_NONE || (game->player && game->player->injured)) return "Not now - somebody's hurt, the ambulance is coming"; return NULL; } @@ -190,6 +192,9 @@ bool Save_write(const Game *game, const char *dir, const char *name, char *err, fprintf(f, "guide %d\n", a->guide); fprintf(f, "why %d %d %d %d\n", w->why[0], w->why[1], w->why[2], w->why_heard); fprintf(f, "weekend %d %.9g %d %d %d\n", game->weekend, (double)game->weekend_from, game->weekend_warned, game->fest_name, game->focus); + const BigTree *bt = &w->bigtree; + fprintf(f, "bigtree %d %.9g %.9g %.9g %d %.9g %d %d\n", bt->step, (double)bt->work, (double)bt->rope_h, (double)bt->rope_wait, bt->pullers, + (double)bt->off, bt->lines_down, bt->told); const Festival *fe = &game->festival; fprintf(f, "festival %d %d %d %d %d %u %d %.9g %d %d\n", fe->stage, fe->came, fe->caught, fe->inside, fe->counted, fe->played, fe->review, (double)fe->review_t, fe->result, fe->helped); @@ -462,6 +467,12 @@ static void applyGame(Game *game, const Parsed *p) { a->welcome = numI(&s); a->welcome_t = numF(&s); a->chat = numI(&s); a->chat_wait = numF(&s); a->greeted = numI(&s) != 0; a->car = byId(p, numI(&s)); } else if (!strcmp(key, "told")) { game->rain_told = numI(&s) != 0; game->wet_told = numI(&s) != 0; } + else if (!strcmp(key, "bigtree")) { + BigTree *bt = &w->bigtree; + bt->step = numI(&s); bt->work = numF(&s); bt->rope_h = numF(&s); bt->rope_wait = numF(&s); bt->pullers = numI(&s); + bt->off = numF(&s); bt->lines_down = numI(&s) != 0; bt->told = numI(&s) != 0; + if (bt->step == BT_CUT || bt->step == BT_FALLING) bt->step = BT_PULL; /* (mid-cut: from the pulling again) */ + } else if (!strcmp(key, "festival")) { Festival *fe = &game->festival; fe->stage = numI(&s); fe->came = numI(&s); fe->caught = numI(&s); fe->inside = numI(&s); fe->counted = numI(&s) != 0; diff --git a/entropie/src/game/sound.c b/entropie/src/game/sound.c index 8ccd1b7..2e02f02 100644 --- a/entropie/src/game/sound.c +++ b/entropie/src/game/sound.c @@ -186,7 +186,8 @@ void Sound_update(Game *game, float dt, bool paused) { /* the air: wind and rain (in a tent: dull and drumming on it) */ float wind = fminf(1.0f, hypotf(w->wind.x, w->wind.z) / WIND_MAX); Audio_bed(BED_WIND, (0.06f + 0.55f * wind) * (tent ? 0.5f : 1.0f), 0.0f, wind); - Audio_bed(BED_RAIN, 0.6f * smoothstepf(0.02f, 0.6f, w->rain), 0.0f, tent ? 1.0f : 0.0f); + bool berlin = you && Game_inBerlin(you); /* (at a meeting: indoors, far off) */ + Audio_bed(BED_RAIN, berlin ? 0.0f : 0.6f * smoothstepf(0.02f, 0.6f, w->rain), 0.0f, tent ? 1.0f : 0.0f); /* fires, a chainsaw, the hose: the loudest near you */ float fire_g = 0.0f, fire_pan = 0.0f, fire_k = 0.0f, saw_g = 0.0f, saw_pan = 0.0f, saw_k = 0.0f, hose_g = 0.0f, hose_pan = 0.0f; @@ -223,6 +224,18 @@ void Sound_update(Game *game, float dt, bool paused) { Audio_bed(BED_ENGINE, g, you && you->riding ? 0.0f : pan, game->arrival.speed / 5.5f); } else Audio_bed(BED_ENGINE, 0.0f, 0.0f, 0.0f); + /* the ambulance: the two-tone horn (driving in and out, a while once it's stopped) */ + static float horn_t = 0.0f; + static int horn_k = 0; + const Ambulance *amb = &game->ambulance; + if (amb->car && (amb->stage == AMB_IN || amb->stage == AMB_OUT || (amb->stage == AMB_PARKED && amb->t < 3.0f)) && (horn_t -= dt) <= 0.0f) { + float pan, g = hear(&ear, amb->car->position, &pan, NULL); + horn_t = 0.62f; + horn_k ^= 1; + float f = horn_k ? 466.0f : 622.0f; /* (B flat and E flat: tatue - tata) */ + play(WAVE_SAW, f, f, 2200.0f, 300.0f, 0.02f, 0.6f, 0.6f, 0.25f * fmaxf(g, 0.05f), pan, 0.0f); + } + /* the floors playing: each its own music, from its stage (far off only the bass and the kick carry) */ for (int k = 0; k < FEST_FLOORS; k++) { float pan = 0.0f, g = 0.0f, far = 1.0f; diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 199ef00..4cfe250 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -2586,6 +2586,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { /* The wind took something out of their hand: after it */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) && fetchBlown(self, objects, count, dt)) return; + /* The big tree's rope: out to their place on it, pulling when it goes */ + if (ai->mode != AI_FOLLOW && ai_onRope(self, objects, count, dt)) return; /* The festival: the storm, guests camping and leaving, dancing, the awareness team */ if (ai->mode != AI_FOLLOW && ai->mode != AI_APPROACH && ai_festival(self, objects, count, dt)) return; /* Rain: inside */ diff --git a/entropie/src/people/ai_internal.h b/entropie/src/people/ai_internal.h index 7feb2d4..4fa7a2b 100644 --- a/entropie/src/people/ai_internal.h +++ b/entropie/src/people/ai_internal.h @@ -113,6 +113,8 @@ bool ai_festival(Object *self, Object *const *objects, int count, float dt); enum { SIDE_HELD = -2, SIDE_FINISHED = -1, SIDE_WORKING = 0, SIDE_STEP = 1 }; int ai_job(Object *self, int project, Object *const *objects, int count, float dt, char *why, size_t size); bool ai_jobDone(int project); +/* On the big tree's rope (Object.puller): out to the place, hands on, pulling when it goes. True while on it. */ +bool ai_onRope(Object *self, Object *const *objects, int count, float dt); const char *ai_jobStart(int project); static inline float random01(Ai *ai) { diff --git a/entropie/src/people/ai_jobs.c b/entropie/src/people/ai_jobs.c index 92b2cfd..559ab7d 100644 --- a/entropie/src/people/ai_jobs.c +++ b/entropie/src/people/ai_jobs.c @@ -18,6 +18,7 @@ #include "world/field.h" #include "world/site.h" #include "world/stage.h" +#include "world/bigtree.h" #define MOW_REACH 1.2f /* m from the cell: mowing it from here */ #define MOW_SWING 1.5f /* real s a swing of the scythe (quicker for the quick) */ @@ -186,11 +187,148 @@ static int carve(Object *self, Object *const *objects, int count, float dt, char return SIDE_STEP; } +/* ---------- The big old tree (Enrico) ---------- */ + +#define ROPE_WAIT 5400.0f /* game s waiting for somebody to throw the line up: then he does it himself (not as high) */ +#define CLEAR_WAIT 45.0f /* real s shouting at people in the way before he cuts anyway */ +#define FALL_ZONE 24.0f /* m round the tree on the side it falls to (and some of the rest): nobody but the pullers */ + +static void sayEvery(Object *self, const char *line) { + if (self->ai.remark > 0.0f || self->ai.speech_time > 0.0f) return; + Ai_say(self, line); + self->ai.remark = 60.0f; +} + +/* Somebody standing where it could come down (not the pullers out at the rope's end). */ +static Object *inTheWay(const Object *self, Object *const *objects, int count) { + Vec3 t = Site_bigTree(), safe = Site_bigTreeSafe(); + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o == self || !o->is_character || o->type == OBJ_DOG || o->puller > 0.0f || o->indoors > 0.0f || o->riding) continue; + Vec3 d = vec3_sub(o->position, t); + d.y = 0.0f; + float dist = vec3_length(d), ahead = vec3_dot(d, safe); + if (dist < 6.0f || (dist < FALL_ZONE && ahead > -4.0f)) return o; + } + return NULL; +} + +static int bigTree(Object *self, Object *const *objects, int count, float dt, char *why, size_t size) { + Ai *ai = &self->ai; + BigTree *bt = &g_world->bigtree; + Object *tree = BigTree_tree(g_world); + if (!tree || bt->step >= BT_FALLING) { snprintf(why, size, "%s", bt->lines_down ? "Down. And the line with it... sorry, Kurti." : "Down it is - and the line's fine!"); return SIDE_FINISHED; } + Vec3 t = Site_bigTree(); + self->carving = false; + switch (bt->step) { + case BT_STANDING: { /* first a good look at it, the throw line put down at its foot */ + if (!ai_goToPoint(self, BigTree_notchSpot(g_world), 1.0f, objects, count, dt)) return SIDE_WORKING; + Vec3 at = vec3_add(BigTree_notchSpot(g_world), vec3_scale(Site_bigTreeSafe(), 1.5f)); + at.y = World_groundHeight(g_world, at.x, at.z, at.y + 2.0f) + 0.02f; + Object *bag = Object_create(OBJ_ROPE, PALETTE_WARM, at, vec3_zero(), 0.0f); + if (bag && !World_add(g_world, bag)) bag = NULL; + bt->step = BT_ROPE; + snprintf(why, size, "It leans to the road - the phone line! A rope up high, then we pull it the other way."); + return SIDE_STEP; + } + case BT_ROPE: /* the rope up over a branch: you (the throw line lies at its foot), or after a while he does it */ + if (bt->rope_h > 0.0f) { bt->step = BT_NOTCH; bt->work = 0.0f; return SIDE_WORKING; } + if (!ai_goToPoint(self, vec3_add(BigTree_notchSpot(g_world), vec3_scale(Site_bigTreeSafe(), 3.0f)), 1.0f, objects, count, dt)) return SIDE_WORKING; + stand(self); + turnTowards(self, vec3_sub(t, self->position), dt); + self->body.look_dir = vec3_normalize(vec3_add(vec3_sub(t, self->position), vec3(0.0f, 12.0f, 0.0f))); + self->body.look_weight = 0.8f; + if ((bt->rope_wait += g_world->game_dt) < ROPE_WAIT) { + sayEvery(self, "Who throws the line up over a branch? As high as you can - the bag's at the foot."); + return SIDE_WORKING; + } + for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_ROPE && !objects[i]->broken) Object_useUp(objects[i]); + bt->rope_h = 6.0f + 1.5f * random01(ai); + snprintf(why, size, "Right, I'll do it myself... %.1f m. Not great - but it'll have to do.", bt->rope_h); + return SIDE_STEP; + case BT_NOTCH: { /* the chainsaw, the notch on the side it should fall to */ + Object *saw = heldOf(self, OBJ_CHAINSAW); + if (!saw) { + Object *c = ai->fetch && ai->fetch->type == OBJ_CHAINSAW && !ai->fetch->is_held ? ai->fetch : nearestFree(self, objects, count, OBJ_CHAINSAW, -1, 400.0f); + if (!c) { snprintf(why, size, "Who's got the chainsaw? I need it for the big tree."); return SIDE_HELD; } + ai->fetch = c; + take(self, c, objects, count, dt); + return SIDE_WORKING; + } + if (ai->fetch == saw) ai->fetch = NULL; + if (!ai_goToPoint(self, BigTree_notchSpot(g_world), 0.35f, objects, count, dt)) return SIDE_WORKING; + stand(self); + turnTowards(self, vec3_sub(t, self->position), dt); + Hands_touch(self, vec3_add(vec3_lerp(t, self->position, 0.45f), vec3(0.0f, 0.55f, 0.0f))); + self->carving = true; + self->body.crouch_goal = fmaxf(self->body.crouch_goal, 0.45f); + if ((bt->work += handy(self) * g_world->game_dt / 3600.0f) < BT_NOTCH_WORK) return SIDE_WORKING; + bt->step = BT_PULL; + bt->work = 0.0f; + snprintf(why, size, "Notch is in! Now - everybody on the rope, out at the far end. Three at least!"); + return SIDE_STEP; + } + case BT_PULL: case BT_CUT: { /* the back cut, once there are enough on the rope and the way's clear */ + if (!heldOf(self, OBJ_CHAINSAW)) { bt->step = BT_NOTCH; return SIDE_WORKING; } /* (somebody took it: back for it) */ + if (!ai_goToPoint(self, BigTree_backSpot(g_world), 0.35f, objects, count, dt)) return SIDE_WORKING; + stand(self); + turnTowards(self, vec3_sub(t, self->position), dt); + if (bt->step == BT_PULL) { + if (bt->pullers < 3) { sayEvery(self, "I need three on the rope - at least! Out at the end of it."); return SIDE_WORKING; } + Object *way = inTheWay(self, objects, count); + if (way && (ai->fetch_time += dt) < CLEAR_WAIT) { + char line[AI_SPEECH_MAX]; + snprintf(line, sizeof line, "%s! Out of there - that's where it comes down!", way->type == OBJ_PLAYER ? "Hey, you" : way->name); + if (ai->speech_time <= 0.0f) Ai_say(self, line); + return SIDE_WORKING; + } + ai->fetch_time = 0.0f; + bt->step = BT_CUT; + bt->work = 0.0f; + Ai_say(self, "Back cut! On the rope - get ready..."); + } + Hands_touch(self, vec3_add(vec3_lerp(t, self->position, 0.45f), vec3(0.0f, 0.6f, 0.0f))); + self->carving = true; + float was = bt->work; + bt->work += dt; + if (was < BT_CUT_GOES && bt->work >= BT_CUT_GOES) Ai_say(self, "It's going - PULL! PULL!"); + if (bt->work < BT_CUT_TIME) return SIDE_WORKING; + self->carving = false; + bt->step = BT_FALLING; /* (game_bigtree.c: which way, down it comes) */ + return SIDE_WORKING; + } + default: return SIDE_FINISHED; + } +} + +/* On the rope (game_bigtree.c picks them: puller = their place + 1): out to it, hands on, and when it goes: pull. */ +bool ai_onRope(Object *self, Object *const *objects, int count, float dt) { + if (self->puller <= 0.0f || !g_world) return false; + BigTree *bt = &g_world->bigtree; + if (bt->step < BT_PULL || bt->step > BT_FALLING || Body_isBusy(&self->body)) { self->puller = 0.0f; return false; } + if (self->sitting) self->sitting = false; + for (int h = 0; h < 2; h++) if (self->hands.item[h]) Hands_drop(self, h); /* (both hands on it) */ + Vec3 spot = BigTree_pullerSpot(g_world, (int)self->puller - 1); + if (!ai_goToPoint(self, spot, 0.35f, objects, count, dt)) return true; + stand(self); + Vec3 t = Site_bigTree(); + turnTowards(self, vec3_sub(t, self->position), dt); + Vec3 at = BigTree_ropeAt(g_world, self->position); + self->hands.rope = bt->step == BT_CUT && bt->work >= BT_CUT_GOES - 3.0f ? 1.0f : 0.05f; + self->hands.rope_at = at; + self->hands.rope_dir = vec3_normalize(vec3_sub(BigTree_ropeEnd(g_world), BigTree_attach(g_world))); + if (bt->step == BT_CUT && bt->work >= BT_CUT_GOES && self->ai.speech_time <= 0.0f && random01(&self->ai) < 0.5f * dt) + Ai_say(self, random01(&self->ai) < 0.5f ? "Hnnngh!" : "Pull!"); + (void)objects; (void)count; + return true; +} + int ai_job(Object *self, int project, Object *const *objects, int count, float dt, char *why, size_t size) { switch (project) { case PROJECT_MOWING: return mow(self, objects, count, dt, why, size); case PROJECT_SNAILS: return snails(self, objects, count, dt, why, size); case PROJECT_WOOD_ART: return carve(self, objects, count, dt, why, size); + case PROJECT_BIG_TREE: return self->leads == PROJECT_BIG_TREE ? bigTree(self, objects, count, dt, why, size) : SIDE_FINISHED; /* (helpers: on the rope when it's time) */ default: return SIDE_FINISHED; } } @@ -200,6 +338,7 @@ bool ai_jobDone(int project) { switch (project) { case PROJECT_MOWING: return Field_done(g_world) >= 1.0f; case PROJECT_SNAILS: return Field_snailsLeft(g_world) == 0 || g_world->inspected; + case PROJECT_BIG_TREE: return g_world->bigtree.step >= BT_FALLING; default: return false; /* (Kerry: as long as there are logs) */ } } @@ -209,6 +348,7 @@ const char *ai_jobStart(int project) { case PROJECT_MOWING: return "Right - the Kartoffelacker. Where's my scythe?"; case PROJECT_SNAILS: return "Snail time! Gently, everybody - they're protected."; case PROJECT_WOOD_ART: return "Time to make some art. Chainsaw, log, go!"; + case PROJECT_BIG_TREE: return "Right - the big tree. Let's have a look at it."; default: return "Off to work."; } } diff --git a/entropie/src/people/ai_project.c b/entropie/src/people/ai_project.c index f9c4775..0b35591 100644 --- a/entropie/src/people/ai_project.c +++ b/entropie/src/people/ai_project.c @@ -49,7 +49,7 @@ static bool floorProject(int p) { /* Something to go and do on the ground: a floor, or a side job (ai_jobs.c). */ static bool workable(int p) { - return floorProject(p) || p == PROJECT_MOWING || p == PROJECT_SNAILS || p == PROJECT_WOOD_ART; + return floorProject(p) || p == PROJECT_MOWING || p == PROJECT_SNAILS || p == PROJECT_WOOD_ART || p == PROJECT_BIG_TREE; } static int theirProject(const Object *self) { diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 9f447ff..a2bcf1b 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -1078,6 +1078,15 @@ void Hands_update(Object *who, float dt) { } h->touching = false; } + if (h->rope > 0.0f) { /* on a rope: both hands on it, one behind the other; leaning back to pull */ + for (int hand = 0; hand < 2; hand++) { + if (h->state[hand] != HAND_FREE) continue; + b->hand_active[hand] = true; + b->hand_target[hand] = vec3_add(h->rope_at, vec3_scale(h->rope_dir, hand == who->main_hand ? 0.0f : 0.35f)); + } + b->lean_goal = fminf(b->lean_goal, -0.6f * fminf(h->rope, 1.0f) + 0.1f); + h->rope = 0.0f; + } if (!h->on_edge) b->reach_turn = 0.0f; if (h->on_edge) { /* hanging on, pulling up: both hands on the edge */ for (int hand = 0; hand < 2; hand++) { diff --git a/entropie/src/people/hands.h b/entropie/src/people/hands.h index 0633ff8..9fe7e71 100644 --- a/entropie/src/people/hands.h +++ b/entropie/src/people/hands.h @@ -49,6 +49,8 @@ typedef struct { bool winding; /* ... still held this frame */ bool touching; /* this frame: a hand reaches for touch_point */ Vec3 touch_point; + float rope; /* this frame: both hands on a rope at rope_at (0 none), leaning back into it this hard (0..1) */ + Vec3 rope_at, rope_dir; /* ... the near hand's grip, the way the rope runs (to where it's tied) */ bool on_edge; /* this frame: both free hands hold on to an edge (climbing up) */ Vec3 edge_point, edge_across; float edge_crouch; diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index 9ff8823..2cbb97a 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -37,6 +37,7 @@ typedef struct { bool hood_up; /* ... pulled up over the head (rain) */ int hat; /* HAT_*: on the head (off under a hood) ... */ Vec3 hat_color; /* ... its colour */ + int cast; /* a plaster cast: 0 none, 1 left forearm, 2 right forearm, 3 left shin, 4 right shin */ } FigureLook; enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET }; diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index 0822181..16a4bef 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -92,6 +92,7 @@ static bool loadShaders(Renderer *r) { f->shoes = glGetUniformLocation(f->program, "shoeColor"); f->hair_style = glGetUniformLocation(f->program, "hairStyle"); f->long_sleeves = glGetUniformLocation(f->program, "longSleeves"); + f->cast = glGetUniformLocation(f->program, "castLimb"); f->head_forward = glGetUniformLocation(f->program, "headForward"); f->head_up = glGetUniformLocation(f->program, "headUp"); f->chest_forward = glGetUniformLocation(f->program, "chestForward"); @@ -561,6 +562,7 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { setVec3(f->shoes, pose->look.shoes); glUniform1i(f->hair_style, (GLint)pose->look.hair_style); glUniform1i(f->long_sleeves, pose->look.long_sleeves ? 1 : 0); + glUniform1i(f->cast, pose->look.cast); setVec3(f->head_forward, sk->head_forward); setVec3(f->head_up, sk->head_up); setVec3(f->chest_forward, sk->chest_forward); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index 9bb0bd5..173f29f 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -69,7 +69,7 @@ typedef struct { GLuint program; GLint view_proj, view, light_space, shadow_pass, boil_time; GLint figure_pos, facing, card_center, card_u, card_v, card_min, card_max, joints; - GLint skin, hair, shirt, pants, shoes, hair_style, long_sleeves; + GLint skin, hair, shirt, pants, shoes, hair_style, long_sleeves, cast; GLint head_forward, head_up, chest_forward, waist_bend, face, expression; GLint stream, stream_kind, stream_width; GLint garment, undress, top, jacket_tied, belly, wet, hood, hat, hat_color; diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index b474122..09be0b4 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -169,6 +169,15 @@ Mesh Shapes_gear(int kind, Vec3 color); a log carved into a figure (on y = 0): kind 0 an owl, 1 a bear, 2 a mushroom man. */ Mesh Shapes_scythe(float length); Mesh Shapes_snail(void); +/* The ambulance (the estate's shape: white, a red band, blue lights). */ +Mesh Shapes_ambulance(void); +/* The big tree's rope from a to c (local), sagging; coil: the rest lying in loops at c. The throw line's bag. */ +Mesh Shapes_rope(Vec3 a, Vec3 c, float sag, bool coil); +Mesh Shapes_throwBag(void); +/* A wooden phone pole this high (mast: the tall one), and the wires along n cross arms' middles (local); + down: torn, hanging to the ground (ground_y under each point, local; may be NULL). */ +Mesh Shapes_phonePole(float height, bool mast); +Mesh Shapes_wires(const Vec3 *points, int n, bool down, const float *ground_y); Mesh Shapes_sculpture(int kind, float diameter, float height); /* The marker over who or what to go to next: a diamond, centred, this tall. */ Mesh Shapes_questMarker(float height, Vec3 color); diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 4057975..794f732 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -911,9 +911,32 @@ static void addProfile(MeshBuilder *b, const float (*pt)[2], int n, float x0, fl /* Volkram's old estate car (see shapes.h): rounded off, a faded paint, rust at the arches, roof rails; the cabin glass all round with the pillars and the roof over it. Nose to +Z. */ +static void carBody(MeshBuilder *bp, Vec3 color, bool boot_open); + Mesh Shapes_car(Vec3 color, bool boot_open) { MeshBuilder b; MeshBuilder_init(&b); + carBody(&b, color, boot_open); + return Mesh_fromBuilder(&b); +} + +/* The ambulance: the same estate, white, a red band round it, the blue lights on the roof. */ +Mesh Shapes_ambulance(void) { + MeshBuilder b; + MeshBuilder_init(&b); + const Vec3 white = { 0.93f, 0.93f, 0.90f }, red = { 0.86f, 0.22f, 0.16f }, blue = { 0.25f, 0.45f, 0.95f }; + carBody(&b, white, false); + const float hw = CAR_WIDTH * 0.5f; + for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * (hw + 0.012f), 0.76f, -0.1f), vec3(0.01f, 0.13f, CAR_LENGTH * 0.9f), red); + addBlock(&b, vec3(0.0f, 0.76f, CAR_LENGTH * 0.5f + 0.012f), vec3(CAR_WIDTH * 0.8f, 0.1f, 0.01f), red); + addBlock(&b, vec3(0.0f, 1.63f, -0.15f), vec3(CAR_WIDTH * 0.62f, 0.09f, 0.22f), (Vec3){ 0.2f, 0.2f, 0.22f }); /* the light bar */ + for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * CAR_WIDTH * 0.2f, 1.71f, -0.15f), vec3(0.3f, 0.09f, 0.18f), blue); + for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * 0.25f, 1.585f, -1.2f), vec3(0.35f, 0.012f, 0.35f), red); /* (a cross on the roof, more or less) */ + return Mesh_fromBuilder(&b); +} + +static void carBody(MeshBuilder *bp, Vec3 color, bool boot_open) { + MeshBuilder b = *bp; const float w = CAR_WIDTH, hw = w * 0.5f, l = CAR_LENGTH, wheel = 0.31f, back = -l * 0.5f; const Vec3 glass = { 0.34f, 0.42f, 0.48f }, dark = { 0.12f, 0.12f, 0.12f }, trim = { 0.30f, 0.30f, 0.29f }; const Vec3 rust = { 0.45f, 0.27f, 0.16f }, chrome = { 0.62f, 0.63f, 0.62f }; @@ -963,7 +986,7 @@ Mesh Shapes_car(Vec3 color, bool boot_open) { } else { addBlock(&b, vec3(0.0f, 1.20f, back + 0.035f), vec3(w * 0.84f, 0.40f, 0.02f), glass); /* the back window */ } - return Mesh_fromBuilder(&b); + *bp = b; } /* The car seen from inside (the body round you would hide it all): the @@ -1795,3 +1818,99 @@ Mesh Shapes_ladder(float extend) { addBlock(&b, vec3(0.0f, Shapes_ladderRung(extend, k), 0.0f), vec3(LADDER_WIDTH, 0.03f, 0.045f), alu); return Mesh_fromBuilder(&b); } + +/* ---------- The big tree's rope, the phone line ---------- */ + +/* A rope from a to c (local), sagging this far in the middle; coil: a few loops lying at c. */ +Mesh Shapes_rope(Vec3 a, Vec3 c, float sag, bool coil) { + MeshBuilder b; + MeshBuilder_init(&b); + const Vec3 color = { 0.86f, 0.62f, 0.22f }; + const int n = 14; + Vec3 was = a; + for (int k = 1; k <= n; k++) { + float t = (float)k / (float)n; + Vec3 p = vec3_add(vec3_lerp(a, c, t), vec3(0.0f, -sag * 4.0f * t * (1.0f - t), 0.0f)); + addStick(&b, was, p, 0.018f, color); + was = p; + } + for (int k = 0; coil && k < 3; k++) { /* (the rest of it in loops on the ground) */ + float r = 0.22f + 0.03f * (float)k; + Vec3 last = vec3_add(c, vec3(r, 0.02f * (float)k + 0.01f, 0.0f)); + for (int i = 1; i <= 10; i++) { + float an = 6.2832f * (float)i / 10.0f; + Vec3 p = vec3_add(c, vec3(cosf(an) * r, 0.02f * (float)k + 0.01f, sinf(an) * r)); + addStick(&b, last, p, 0.016f, color); + last = p; + } + } + return Mesh_fromBuilder(&b); +} + +/* The throw line: a little sand bag with the cord coiled beside it. */ +Mesh Shapes_throwBag(void) { + MeshBuilder b; + MeshBuilder_init(&b); + addBlock(&b, vec3(0.0f, 0.05f, 0.0f), vec3(0.09f, 0.1f, 0.09f), (Vec3){ 0.78f, 0.22f, 0.18f }); + Vec3 last = vec3(0.16f, 0.015f, 0.0f); + for (int i = 1; i <= 10; i++) { + float an = 6.2832f * (float)i / 10.0f; + Vec3 p = vec3(0.16f * cosf(an), 0.015f, 0.16f * sinf(an)); + addStick(&b, last, p, 0.014f, (Vec3){ 0.86f, 0.62f, 0.22f }); + last = p; + } + return Mesh_fromBuilder(&b); +} + +/* A wooden phone pole this high (mast: the tall one with two cross arms), its foot at the origin; the arms across X. */ +Mesh Shapes_phonePole(float height, bool mast) { + MeshBuilder b; + MeshBuilder_init(&b); + const Vec3 wood = { 0.38f, 0.30f, 0.22f }, porcelain = { 0.88f, 0.88f, 0.84f }; + shapes_addTube(&b, 0.0f, mast ? 0.16f : 0.12f, mast ? 0.11f : 0.09f, height, 10, wood, true); + for (int arm = 0; arm < (mast ? 2 : 1); arm++) { + float y = height - 0.4f - 0.6f * (float)arm; + addBlock(&b, vec3(0.0f, y, 0.0f), vec3(1.4f, 0.1f, 0.1f), wood); + for (int k = -1; k <= 1; k++) addBlock(&b, vec3(0.6f * (float)k, y + 0.1f, 0.0f), vec3(0.06f, 0.1f, 0.06f), porcelain); + } + if (mast) { /* (a box with the junctions, a step bolt or two) */ + addBlock(&b, vec3(0.0f, 2.2f, 0.16f), vec3(0.3f, 0.45f, 0.14f), (Vec3){ 0.55f, 0.56f, 0.52f }); + for (int k = 0; k < 6; k++) addBlock(&b, vec3(k % 2 ? 0.16f : -0.16f, 3.0f + 0.5f * (float)k, 0.0f), vec3(0.14f, 0.03f, 0.03f), COLOR_STEEL); + } + return Mesh_fromBuilder(&b); +} + +/* The wires: three of them from pole to pole (n points, each the middle of a cross arm, local), sagging; down: torn, + hanging off the poles to the ground (ground_y: local height of the ground under each point). */ +Mesh Shapes_wires(const Vec3 *points, int n, bool down, const float *ground_y) { + MeshBuilder b; + MeshBuilder_init(&b); + const Vec3 color = { 0.12f, 0.12f, 0.12f }; + for (int i = 0; i + 1 < n; i++) { + Vec3 a0 = points[i], c0 = points[i + 1]; + Vec3 d = vec3_sub(c0, a0); + Vec3 across = vec3_normalize(vec3(-d.z, 0.0f, d.x)); + for (int w = -1; w <= 1; w++) { + Vec3 a = vec3_add(a0, vec3_scale(across, 0.6f * (float)w)), c = vec3_add(c0, vec3_scale(across, 0.6f * (float)w)); + a.y += 0.15f; c.y += 0.15f; + if (down) { /* (snapped in the middle: each half hangs down to the ground) */ + Vec3 ga = vec3_lerp(a, c, 0.35f), gc = vec3_lerp(a, c, 0.65f); + ga.y = ground_y ? ground_y[i] + 0.03f : 0.03f; + gc.y = ground_y ? ground_y[i + 1] + 0.03f : 0.03f; + addStick(&b, a, vec3_lerp(a, ga, 0.6f), 0.012f, color); + addStick(&b, vec3_lerp(a, ga, 0.6f), ga, 0.012f, color); + addStick(&b, c, vec3_lerp(c, gc, 0.6f), 0.012f, color); + addStick(&b, vec3_lerp(c, gc, 0.6f), gc, 0.012f, color); + continue; + } + Vec3 was = a; + for (int k = 1; k <= 12; k++) { + float t = (float)k / 12.0f; + Vec3 p = vec3_add(vec3_lerp(a, c, t), vec3(0.0f, -0.7f * 4.0f * t * (1.0f - t), 0.0f)); + addStick(&b, was, p, 0.012f, color); + was = p; + } + } + } + return Mesh_fromBuilder(&b); +} diff --git a/entropie/src/world/bigtree.c b/entropie/src/world/bigtree.c new file mode 100644 index 0000000..9a02400 --- /dev/null +++ b/entropie/src/world/bigtree.c @@ -0,0 +1,52 @@ +#include "world/bigtree.h" +#include "world/site.h" + +#include <math.h> + +Object *BigTree_tree(const World *world) { + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (o->type == OBJ_TREE && o->variant == 2 && !o->broken) return o; + } + return NULL; +} + +static Vec3 base(const World *world) { + Vec3 t = Site_bigTree(); + t.y = world->has_terrain ? Terrain_height(&world->terrain, t.x, t.z) : 0.0f; + return t; +} + +static Vec3 onGround(const World *world, Vec3 p, float up) { + p.y = (world->has_terrain ? Terrain_height(&world->terrain, p.x, p.z) : 0.0f) + up; + return p; +} + +Vec3 BigTree_attach(const World *world) { + Vec3 t = base(world); + return vec3_add(t, vec3_add(vec3(0.0f, world->bigtree.rope_h, 0.0f), vec3_scale(Site_bigTreeSafe(), 0.45f))); +} + +Vec3 BigTree_ropeEnd(const World *world) { + return onGround(world, vec3_add(base(world), vec3_scale(Site_bigTreeSafe(), BT_ROPE_OUT)), 1.0f); +} + +Vec3 BigTree_pullerSpot(const World *world, int k) { + Vec3 safe = Site_bigTreeSafe(), side = vec3(-safe.z, 0.0f, safe.x); + return onGround(world, vec3_add(base(world), vec3_add(vec3_scale(safe, BT_ROPE_OUT + 0.4f - BT_PULL_GAP * (float)k), + vec3_scale(side, (k % 2 ? 0.35f : -0.35f)))), 0.0f); +} + +Vec3 BigTree_ropeAt(const World *world, Vec3 near) { + Vec3 a = BigTree_attach(world), b = BigTree_ropeEnd(world), ab = vec3_sub(b, a); + float t = clampf(vec3_dot(vec3_sub(near, a), ab) / fmaxf(vec3_dot(ab, ab), 1e-4f), 0.0f, 1.0f); + return vec3_add(a, vec3_scale(ab, t)); /* (pulled tight: straight) */ +} + +Vec3 BigTree_notchSpot(const World *world) { return onGround(world, vec3_add(base(world), vec3_scale(Site_bigTreeSafe(), 1.15f)), 0.0f); } +Vec3 BigTree_backSpot(const World *world) { return onGround(world, vec3_sub(base(world), vec3_scale(Site_bigTreeSafe(), 1.15f)), 0.0f); } + +Vec3 BigTree_escapeSpot(const World *world, float sign) { /* (back and off to the side: the way out every feller plans) */ + Vec3 safe = Site_bigTreeSafe(), side = vec3(-safe.z, 0.0f, safe.x); + return onGround(world, vec3_add(base(world), vec3_add(vec3_scale(side, 7.0f * sign), vec3_scale(safe, -1.0f))), 0.0f); +} diff --git a/entropie/src/world/bigtree.h b/entropie/src/world/bigtree.h new file mode 100644 index 0000000..df8b0da --- /dev/null +++ b/entropie/src/world/bigtree.h @@ -0,0 +1,37 @@ +#ifndef WORLD_BIGTREE_H +#define WORLD_BIGTREE_H + +/* + * The big old tree between the Obstwiese and the Kartoffelacker (world/site.c + * plants it): it leans towards the road, and over the road runs the phone + * line. Enrico (Go-Darmstadt) fells it the other way: a rope thrown up over a + * branch as high as it'll go (a job for you), the notch cut on the side it + * should fall to, a few people pulling on the rope from well out, the back + * cut. Where it comes down depends on how many pull, how high the rope is, a + * gust - the people's part is ai_jobs.c, the rest game_bigtree.c. Here: where + * everything is. + */ + +#include "world/world.h" + +#define BT_ROPE_OUT 28.0f /* m out from the tree (the way it should fall): where the rope's pulled from */ +#define BT_PULLERS 4 /* places along the rope's end (the crew; you make one more) */ +#define BT_PULL_GAP 1.3f /* m between them */ +#define BT_NOTCH_WORK 0.8f /* person-hours of chainsaw work for the notch */ +#define BT_CUT_TIME 22.0f /* real s for the back cut ... */ +#define BT_CUT_GOES 16.0f /* ... it starts to go at this */ + +/* The tree (standing or down), or NULL. */ +Object *BigTree_tree(const World *world); +/* Where the rope's tied (on the trunk at its height), and its end out in the meadow (hand height). */ +Vec3 BigTree_attach(const World *world); +Vec3 BigTree_ropeEnd(const World *world); +/* The puller places (k 0 .. BT_PULLERS-1, from the end of the rope in), and where a hand goes on the rope there. */ +Vec3 BigTree_pullerSpot(const World *world, int k); +Vec3 BigTree_ropeAt(const World *world, Vec3 near); +/* Where Enrico cuts the notch (the side it should fall to), the back cut (the other side), and gets out of the way to. */ +Vec3 BigTree_notchSpot(const World *world); +Vec3 BigTree_backSpot(const World *world); +Vec3 BigTree_escapeSpot(const World *world, float sign); /* (sign: which side, 1 or -1) */ + +#endif /* WORLD_BIGTREE_H */ diff --git a/entropie/src/world/cast.c b/entropie/src/world/cast.c index 07d9387..3510e17 100644 --- a/entropie/src/world/cast.c +++ b/entropie/src/world/cast.c @@ -122,6 +122,14 @@ static const CastMember CAST[] = { .smoker = 0.2f, .wits = 0.8f, .height_fear = 0.05f, .worn = 0.15f, .fitness = 0.55f, .tolerance = 1.1f, .cigarettes = 6, .role = "the light, and a proper floor for the main stage", .leads = PROJECT_LIGHT, .helps = PROJECT_HOFBUEHNE, .commitment = 0.85f, .arrives = 26.0f }, + { .name = "Enrico", .temperament = { .pride = 0.4f, .energy = 0.3f, .nerves = -0.2f }, + .look = LOOK(0.84f, 0.62f, 0.46f, 0.15f, 0.12f, 0.10f, 0.70f, 0.42f, 0.16f, 0.26f, 0.30f, 0.22f, 0.30f, 0.22f, 0.14f, 1.83f, HAIR_LONG, false), + .character = { SKILLS(0.6f, 0.9f, -0.2f, 0.4f), 0.6f, SKILLS(0.7f, 0.95f, 0.3f, 0.5f), + "Enrico, Go-Darmstadt - the geo-dome lot. Trees? I've felled bigger ones than that.", 1.05f, 1.2f, BREAK_SIT, 100.0f, 30.0f }, + .kg = 86, .age = 38, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .far_away = true, + .smoker = 0.5f, .wits = 0.7f, .height_fear = 0.0f, .worn = 0.3f, .fitness = 0.7f, .tolerance = 1.3f, .cigarettes = 14, + .role = "Go-Darmstadt (the geo-dome); fells the big old tree away from the phone line", .leads = PROJECT_BIG_TREE, .helps = PROJECT_KARTOFFELACKER, + .commitment = 0.85f, .arrives = 21.0f }, }; #define CAST_COUNT ((int)(sizeof(CAST) / sizeof(CAST[0]))) @@ -186,7 +194,7 @@ const char *Cast_projectName(Project p) { [PROJECT_DECORATION] = "decoration", [PROJECT_LIGHT] = "light", [PROJECT_HOFBUEHNE] = "Hofbuehne (bands)", [PROJECT_OBSTWIESE] = "Obstwiese (psytrance)", [PROJECT_KARTOFFELACKER] = "Kartoffelacker (world music)", [PROJECT_HIDDEN] = "hidden floor (techno)", [PROJECT_MOWING] = "mowing (scythe)", [PROJECT_SNAILS] = "snail rescue", - [PROJECT_WOOD_ART] = "wood art (chainsaw)", + [PROJECT_WOOD_ART] = "wood art (chainsaw)", [PROJECT_BIG_TREE] = "big old tree", }; return p >= 0 && p < PROJECT_COUNT && NAMES[p] ? NAMES[p] : "?"; } diff --git a/entropie/src/world/cast.h b/entropie/src/world/cast.h index be1add8..2e093d9 100644 --- a/entropie/src/world/cast.h +++ b/entropie/src/world/cast.h @@ -25,6 +25,7 @@ typedef enum { PROJECT_MOWING, /* the Kartoffelacker's grass, with the scythe */ PROJECT_SNAILS, /* the vineyard snails picked up before the regulatory office comes */ PROJECT_WOOD_ART, /* art cut from logs with the chainsaw */ + PROJECT_BIG_TREE, /* the big old tree between the Obstwiese and the Kartoffelacker: felled away from the phone line */ PROJECT_COUNT } Project; @@ -38,6 +39,7 @@ typedef struct { int hand; /* HAND_LEFT / HAND_RIGHT */ int garment; /* GARMENT_* */ bool female, urinella, lighter, filter_habit, pocket_tray, lives_here; + bool far_away; /* not from Berlin (Go-Darmstadt): never at the meetings, stays on the ground */ float smoker, wits, height_fear, worn, fitness, tolerance; int cigarettes, tobacco, papers, filters; const char *keys; /* a key in the pocket, or NULL */ diff --git a/entropie/src/world/forestry.c b/entropie/src/world/forestry.c index 4e56df8..374cf3f 100644 --- a/entropie/src/world/forestry.c +++ b/entropie/src/world/forestry.c @@ -1,5 +1,6 @@ #include "world/forestry.h" #include "world/physics.h" +#include "world/injury.h" #define STUMP_MIN 0.3f /* stumps are at least this wide */ #define ROUND_PIECE 0.6f /* a chopped-out stump gives a chunk of wood this long at most */ @@ -25,11 +26,23 @@ static void releaseAll(World *world, Object *o) { /* ---------- Jobs ---------- */ +static void fellTowards(World *world, Object *tree, Vec3 away, float cut_height); + static void fell(World *world, Object *tree, const Object *by, float cut_height) { if (tree->type != OBJ_TREE || tree->toppled) return; Vec3 away = vec3_sub(tree->position, by->position); away.y = 0.0f; if (vec3_length(away) < 1e-3f) away = Object_facing(by); + fellTowards(world, tree, away, cut_height); +} + +void Forestry_fell(World *world, Object *tree, Vec3 dir, float cut_height) { + if (tree->type != OBJ_TREE || tree->toppled) return; + dir.y = 0.0f; + fellTowards(world, tree, vec3_length(dir) > 1e-3f ? dir : vec3(1.0f, 0.0f, 0.0f), cut_height); +} + +static void fellTowards(World *world, Object *tree, Vec3 away, float cut_height) { /* The trunk comes apart where it was cut: the part below stays as the stump */ Vec3 base = tree->position; @@ -227,11 +240,18 @@ void Forestry_finishJobs(World *world, Object *by) { /* ---------- Falling trees ---------- */ /* What the tree hits on its way down. Returns true if something stops it. */ -static bool hit(Object *tree, Object *o) { +#define BREAK_TREE 0.35f /* a falling tree knocks somebody down: it breaks something this often ... */ +#define BREAK_BIG 0.9f /* ... the big old one: nearly always */ + +static bool hit(World *world, Object *tree, Object *o) { Vec3 dir = tree->fall_dir; - if (o->is_character) { /* knocked down */ - if (o->body.pose == POSE_UP) o->velocity = vec3_add(o->velocity, vec3_scale(dir, 2.0f)); - Body_fall(&o->body, dir); + if (o->is_character) { /* knocked down - and maybe worse */ + bool big = tree->variant == 2; + if (o->body.pose == POSE_UP || (big && !o->injured)) { + if (o->body.pose == POSE_UP) o->velocity = vec3_add(o->velocity, vec3_scale(dir, 2.0f)); + Body_fall(&o->body, dir); + if (o->type != OBJ_DOG) Injury_break(world, o, Injury_roll(world, big ? BREAK_BIG : BREAK_TREE)); + } return false; } if (Object_isBottle(o)) { @@ -303,7 +323,7 @@ void Forestry_update(World *world, float dt) { for (int k = 0; k < world->count; k++) { Object *o = world->objects[k]; if (o == tree || o == tree->stump || o->is_held || o->broken || o->collider_count == 0) continue; - if (touches(tree, o) && hit(tree, o)) stopped = o; + if (touches(tree, o) && hit(world, tree, o)) stopped = o; } if (stopped) { /* Comes to rest where it was, leaning on what it hit */ diff --git a/entropie/src/world/forestry.h b/entropie/src/world/forestry.h index 96c5edb..70edab4 100644 --- a/entropie/src/world/forestry.h +++ b/entropie/src/world/forestry.h @@ -23,6 +23,9 @@ /* Applies jobs finished by a character's tools (see tools.h). */ void Forestry_finishJobs(World *world, Object *by); +/* Felled on purpose this way (the big tree: game_bigtree.c), cut this high. */ +void Forestry_fell(World *world, Object *tree, Vec3 dir, float cut_height); + /* Falling trees: advance and collide. */ void Forestry_update(World *world, float dt); diff --git a/entropie/src/world/injury.c b/entropie/src/world/injury.c new file mode 100644 index 0000000..3516264 --- /dev/null +++ b/entropie/src/world/injury.c @@ -0,0 +1,52 @@ +#include "world/injury.h" +#include "people/hands.h" + +#include <math.h> + +#define ARM_STRENGTH 0.55f /* a cast on the arm: this much of their strength ... */ +#define ARM_TEMPO 0.75f /* ... and of their tempo at work */ +#define LEG_SPEED 0.42f /* a cast on the leg: this much of their speed ... */ +#define LEG_JUMP 0.35f /* ... and of their jump */ + +static float dice(World *world) { + world->rng = world->rng * 1664525u + 1013904223u; + return (float)(world->rng >> 8) / 16777216.0f; +} + +int Injury_roll(World *world, float chance) { + if (dice(world) >= chance) return INJ_NONE; + return dice(world) < 0.55f ? INJ_LEG : INJ_ARM; +} + +bool Injury_break(World *world, Object *who, int kind) { + if (!who || !who->is_character || who->type == OBJ_DOG || kind == INJ_NONE || who->injured || who->hospital_until > 0.0f) return false; + who->injured = kind; + who->injury_side = dice(world) < 0.5f ? 0 : 1; + if (Hands_isGrabbing(who)) Hands_release(who); /* (whatever they held: down) */ + for (int h = 0; h < 2; h++) if (who->hands.state[h] != HAND_FREE) Hands_drop(who, h); + if (who->body.pose == POSE_UP) Body_fall(&who->body, Object_facing(who)); + who->body.down_time = -1.0f; /* (they stay down: the ambulance) */ + who->body.asleep = false; + if (who->type != OBJ_PLAYER) Ai_say(who, kind == INJ_LEG ? "AAARGH! My leg - it's broken!" : "AAARGH! My arm! I heard it crack!"); + World_noise(world, NOISE_THUD, who->position, 1.0f); + return true; +} + +int Injury_castKind(const Object *who) { + return who->look.cast == 0 ? INJ_NONE : who->look.cast <= 2 ? INJ_ARM : INJ_LEG; +} + +void Injury_setCast(Object *who, int kind, int side, bool on) { + int limb = kind == INJ_ARM ? 1 + side : kind == INJ_LEG ? 3 + side : 0; + if (on == (who->look.cast != 0) || limb == 0) return; + float k = on ? 1.0f : -1.0f; + if (kind == INJ_ARM) { + who->strength *= powf(ARM_STRENGTH, k); + who->tempo *= powf(ARM_TEMPO, k); + } else { + who->top_speed *= powf(LEG_SPEED, k); + who->jump_speed *= powf(LEG_JUMP, k); + who->pace *= powf(LEG_SPEED + 0.1f, k); + } + who->look.cast = on ? limb : 0; +} diff --git a/entropie/src/world/injury.h b/entropie/src/world/injury.h new file mode 100644 index 0000000..e35902f --- /dev/null +++ b/entropie/src/world/injury.h @@ -0,0 +1,22 @@ +#ifndef WORLD_INJURY_H +#define WORLD_INJURY_H + +/* + * Broken bones: something heavy coming down on somebody (a falling tree), a + * bad fall from high up. They stay lying there till the ambulance has them + * (game_injury.c); back from the hospital they wear a cast for a while - an + * arm: weaker and slower at work; a leg: slow on their feet, no jumping. + */ + +#include "world/world.h" + +/* Breaks it (INJ_ARM / INJ_LEG): down they go and stay down. False: already hurt (or nobody to hurt). */ +bool Injury_break(World *world, Object *who, int kind); +/* A hit that breaks something this often (0..1): which (INJ_ARM / INJ_LEG), or INJ_NONE (lucky). */ +int Injury_roll(World *world, float chance); +/* The cast on (what it does to them) or off again. */ +void Injury_setCast(Object *who, int kind, int side, bool on); +/* What they have on: INJ_* of the cast (from look.cast). */ +int Injury_castKind(const Object *who); + +#endif /* WORLD_INJURY_H */ diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 4644300..61072e7 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -17,6 +17,7 @@ #define BOTTLE_FALL_TIME 0.35f #define TREE_FALL_TIME 2.4f #define TRUNK_RADIUS 0.2f +#define BIG_TRUNK 0.5f /* the big old tree (variant 2): a metre through */ #define WOOD_DENSITY 500.0f /* kg per cubic meter */ #define WOODPILE_BASE 0.08f /* height of the sleepers */ #define SPRINT_SPEED 6.6f /* an average person flat out (m/s) */ @@ -54,7 +55,7 @@ static void setupBuilding(Object *obj, BuildingSize b) { #define TREE_LAYERS 8 static TreeSize treeSize(const Object *obj) { - return (TreeSize){ .trunk_height = obj->size.y, .trunk_radius = obj->variant ? TRUNK_RADIUS * 0.8f : TRUNK_RADIUS, + return (TreeSize){ .trunk_height = obj->size.y, .trunk_radius = obj->variant == 2 ? BIG_TRUNK : obj->variant ? TRUNK_RADIUS * 0.8f : TRUNK_RADIUS, .leaf_layers = obj->branches > 0 ? obj->branches : 1, .leaf_radius = obj->size.x * 0.5f, .fruit = obj->variant == 1, .cut = obj->trunk_cut, .seed = Shapes_treeSeed(obj->position.x, obj->position.z) }; @@ -438,6 +439,15 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size addCollider(obj, vec3(0.0f, 0.022f, 0.0f), obj->size, vec3_zero()); break; + case OBJ_ROPE: + obj->size = vec3(0.32f, 0.1f, 0.32f); + obj->weight = 0.6f; + obj->mesh = Shapes_throwBag(); + obj->pivot = vec3(0.0f, 0.05f, 0.0f); + addCollider(obj, vec3(0.0f, 0.05f, 0.0f), vec3(0.12f, 0.1f, 0.12f), vec3_zero()); + snprintf(obj->name, sizeof obj->name, "Throw line"); + break; + case OBJ_SCULPTURE: obj->variant = (int)weight % 3; obj->size = size; @@ -1549,6 +1559,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { break; case OBJ_STONE: snprintf(out, size, kg >= 1 ? "A stone - %d kg" : "A pebble", kg); break; case OBJ_CAR: + if (obj->variant == 1) { snprintf(out, size, "The ambulance"); break; } if (!obj->load || obj->load->n == 0) snprintf(out, size, "Volkram's car - the boot is empty"); else if (!obj->boot_open) snprintf(out, size, "Volkram's car - full of your things"); else snprintf(out, size, "Volkram's car - left click at the boot: take out %s", Car_nextName(obj)); @@ -1592,6 +1603,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { break; } case OBJ_SCYTHE: snprintf(out, size, "Scythe - for the Kartoffelacker's grass (in the field: hold left click and walk)"); break; + case OBJ_ROPE: snprintf(out, size, "Throw line - up over a branch of the big tree, as high as you can (T: hold to wind up, look up, let go)"); break; case OBJ_SNAIL: snprintf(out, size, obj->variant ? "A vineyard snail - safe here" : "A vineyard snail (protected) - left click: into your bucket, let go at the wood by the Kartoffelacker"); break; @@ -1645,7 +1657,9 @@ void Object_describe(const Object *obj, char *out, size_t size) { case OBJ_BRANCH: snprintf(out, size, "Branch"); break; case OBJ_WOODPILE: snprintf(out, size, "Woodpile - %d pieces of firewood", obj->stored); break; case OBJ_TREE: - if (!obj->toppled && obj->variant) snprintf(out, size, "Apple tree - pick an apple"); + if (obj->variant == 2 && !obj->toppled) snprintf(out, size, "The big old tree - it leans towards the phone line. Enrico's job (Go-Darmstadt)"); + else if (obj->variant == 2) snprintf(out, size, "The big old tree, down - %d branch%s left", obj->branches, obj->branches == 1 ? "" : "es"); + else if (!obj->toppled && obj->variant) snprintf(out, size, "Apple tree - pick an apple"); else if (!obj->toppled && obj->keep) snprintf(out, size, "Tree - this one stays (Kurti's rule)"); else if (!obj->toppled) snprintf(out, size, "Tree - fell it with the axe or the chainsaw"); else snprintf(out, size, "Fallen tree - %d branch%s left", obj->branches, obj->branches == 1 ? "" : "es"); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index eb7a8e8..07a851d 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -94,6 +94,7 @@ typedef enum { OBJ_SCYTHE, /* the scythe: mows the Kartoffelacker's grass (world/field.h); carried in one hand */ OBJ_SNAIL, /* a vineyard snail: variant 0 out there, 1 let go at the refuge (weight given: the variant) */ OBJ_SCULPTURE, /* static: a log carved into a figure with the chainsaw (Kerry); size = (diameter, height, -), variant = which */ + OBJ_ROPE, /* the throw line for the big tree: a little bag with a long cord coiled to it (thrown up over a branch) */ } ObjectType; /* Building material (OBJ_MATERIAL): what the stages are built with besides the logs. */ @@ -327,6 +328,11 @@ typedef struct Object { bool gone_home; /* ... left on Sunday (out of sight at the gate, nothing more) */ bool storm_told; /* people: told the storm's coming - in under a roof (the festival) */ Vec3 inside_door; /* people: indoors (above), at this door (zero: the main building's) */ + int injured; /* people: INJ_* just broken, waiting for the ambulance (lying there) ... */ + int injury_side; /* ... which side (0 left, 1 right) ... */ + float hospital_until; /* ... away at the hospital till this many story hours (0: not away) ... */ + int cast_until; /* ... a cast on (look.cast) till this weekend starts */ + float puller; /* people: on the big tree's rope (> 0: their place along it, m from its end) */ int leads, helps; /* people: the Project they lead, one they help with (world/cast.h; PROJECT_NONE 0) */ struct { /* dogs */ int mode; diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index 5e71dc2..107de95 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -1298,7 +1298,90 @@ Vec3 Site_floorSpot(int project) { return Site_yard(); } +/* ---------- The big old tree and the phone line ---------- */ + +/* The tree south of the gravel loop between the Obstwiese and the Kartoffelacker, leaning towards the road; + the phone line along the road's other side, on poles, the tall mast nearest the tree. */ +static const Vec3 BIG_TREE_AT = { -45.0f, 0.0f, 73.5f }; +#define BIG_TREE_TRUNK 5.0f /* m of bare trunk ... */ +#define BIG_TREE_LAYERS 26 /* ... then the crown (about 21 m in all) */ +#define BIG_TREE_CROWN 10.0f /* m across at the bottom of the crown */ +static const Vec3 POLES[] = { { -68.0f, 0.0f, 84.8f }, { -51.3f, 0.0f, 83.7f }, { -29.4f, 0.0f, 92.6f } }; +#define MAST 1 /* (the middle one: the tall mast) */ +#define POLE_H 8.5f +#define MAST_H 11.0f +static Object *g_wires; /* (the wires: torn when the tree takes them down) */ + +Vec3 Site_bigTree(void) { return BIG_TREE_AT; } +/* Which way it should go (away from the road, into the meadow) and which way it leans (the road, the line). */ +Vec3 Site_bigTreeSafe(void) { return vec3_normalize(vec3(0.42f, 0.0f, -0.91f)); } +Vec3 Site_bigTreeLean(void) { return vec3_normalize(vec3(-0.30f, 0.0f, 0.95f)); } + +int Site_phoneLine(Vec3 *out, int max) { + int n = 0; + for (int k = 0; k < COUNT(POLES) && n < max; k++) out[n++] = vec3(POLES[k].x, (k == MAST ? MAST_H : POLE_H) - 0.4f, POLES[k].z); + return n; +} + +static Mesh wiresMesh(const World *world, bool down) { + Vec3 pts[COUNT(POLES)]; + float ground[COUNT(POLES)]; + Vec3 origin = POLES[MAST]; + origin.y = Terrain_height(&world->terrain, origin.x, origin.z); + for (int k = 0; k < COUNT(POLES); k++) { + float y = Terrain_height(&world->terrain, POLES[k].x, POLES[k].z); + pts[k] = vec3(POLES[k].x - origin.x, y + (k == MAST ? MAST_H : POLE_H) - 0.4f - origin.y, POLES[k].z - origin.z); + ground[k] = y - origin.y; + } + /* (torn: only the span the tree came down on - which one, the game tells: both spans at the mast hang) */ + return Shapes_wires(pts, COUNT(POLES), down, ground); +} + +void Site_phoneWiresDown(World *world, bool down) { + if (!g_wires) return; + Mesh m = wiresMesh(world, down); + if (!m.vao) return; + Mesh_destroy(&g_wires->mesh); + g_wires->mesh = m; +} + +static bool phoneLine(World *world) { + for (int k = 0; k < COUNT(POLES); k++) { + Vec3 at = POLES[k]; + at.y = Terrain_height(&world->terrain, at.x, at.z) - 0.3f; + float h = (k == MAST ? MAST_H : POLE_H) + 0.3f; + Vec3 d = k + 1 < COUNT(POLES) ? vec3_sub(POLES[k + 1], POLES[k]) : vec3_sub(POLES[k], POLES[k - 1]); + float yaw = atan2f(d.x, d.z); /* (the cross arms across the line) */ + Object *look = Object_createStructure(Shapes_phonePole(h, k == MAST), at, yaw, vec3(1.4f, h, 1.4f)); + if (!look || !World_add(world, look)) return false; + Object *solid = Object_create(OBJ_WALL, PALETTE_STANDARD, at, vec3(0.3f, h, 0.3f), 0.0f); + if (!solid || !World_add(world, solid)) return false; + } + Vec3 origin = POLES[MAST]; + origin.y = Terrain_height(&world->terrain, origin.x, origin.z); + g_wires = Object_createStructure(wiresMesh(world, false), origin, 0.0f, vec3(40.0f, 12.0f, 40.0f)); + return g_wires && World_add(world, g_wires); +} + +static bool plantBigTree(World *world) { + Vec3 at = BIG_TREE_AT; + at.y = Terrain_height(&world->terrain, at.x, at.z) - 0.15f; + for (int i = 0; i < world->count; i++) { /* (room round it: no forest tree, no bush in its crown) */ + Object *o = world->objects[i]; + if ((o->type == OBJ_TREE || o->type == OBJ_BUSH) && hypotf(o->position.x - at.x, o->position.z - at.z) < 7.0f) Object_useUp(o); + } + Object *tree = Object_create(OBJ_TREE, PALETTE_WARM, at, vec3(BIG_TREE_CROWN, BIG_TREE_TRUNK, 0.0f), 0.0f); + if (!tree) return false; + tree->variant = 2; /* (a metre through) */ + tree->keep = true; /* (nobody fells this one on a whim: Enrico's job) */ + tree->branches = 0; + Object_setBranches(tree, BIG_TREE_LAYERS); + return World_add(world, tree) && phoneLine(world); +} + bool Site_finish(World *world) { + g_wires = NULL; + if (!plantBigTree(world)) return false; for (int k = 0; k < COUNT(FLOORS); k++) { /* the stages, marked out on the ground */ Vec3 at = court(FLOORS[k].a, FLOORS[k].b); at.y = Terrain_height(&world->terrain, at.x, at.z); diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h index 4e2e58c..b73d791 100644 --- a/entropie/src/world/site.h +++ b/entropie/src/world/site.h @@ -35,6 +35,14 @@ Vec3 Site_rainRoom(void); Vec3 Site_floorSpot(int project); /* The foot of the path up to the tents' meadow (the end of your garages by the barn). */ Vec3 Site_campPath(void); +/* The big old tree (between the Obstwiese and the Kartoffelacker): where it stands, which way it should fall + (away from the road) and which way it leans (the road, the phone line). */ +Vec3 Site_bigTree(void); +Vec3 Site_bigTreeSafe(void); +Vec3 Site_bigTreeLean(void); +/* The phone line along the road: its cross arms' middles, how many; its wires torn down (or up again). */ +int Site_phoneLine(Vec3 *out, int max); +void Site_phoneWiresDown(World *world, bool down); /* The storm: in at the barn's door here (out of sight inside, like the main building). */ Vec3 Site_barnDoor(void); /* Where the vineyard snails are let go (out of the way of the building), and where Kerry carves (world/field.h). */ diff --git a/entropie/src/world/weather.c b/entropie/src/world/weather.c index 75c4cb7..4f7433b 100644 --- a/entropie/src/world/weather.c +++ b/entropie/src/world/weather.c @@ -175,7 +175,7 @@ void Weather_update(World *world, float dt, float game_dt) { } void Weather_draw(World *world, Renderer *renderer) { - if (world->rain <= 0.01f || !world->drops) return; + if (world->rain <= 0.01f || !world->drops || world->eye.x > WORLD_BERLIN_FROM) return; /* (Berlin: indoors, another sky) */ Vec3 shown[RAIN_DROPS]; int n = 0, live = (int)(RAIN_DROPS * world->rain); for (int k = 0; k < live; k++) diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 12a606e..22ab6b1 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -1,4 +1,5 @@ #include "world/world.h" +#include "world/injury.h" #include "world/camp.h" #include "world/clothes.h" #include "world/car.h" @@ -938,11 +939,12 @@ static void landFromHigh(World *world, Object *o) { o->needs.stamina = fmaxf(0.0f, o->needs.stamina - (how == LAND_CRASH ? 0.25f : 0.08f) * jar); if (how == LAND_CRASH) { o->dirt = fminf(1.0f, o->dirt + 0.1f); + int broke = Injury_roll(world, smoothstepf(9.0f, 13.0f, speed) * 0.8f); /* (from high enough: a broken leg, or an arm put out to catch it) */ + if (broke && Injury_break(world, o, broke)) return; Ai_say(o, speed > 9.5f + 1.3f * sporty ? "Aaargh... my ankle!" : "Ouch!"); } else if (how == LAND_ROLL) { o->dirt = fminf(1.0f, o->dirt + 0.05f); } - (void)world; } /* Up a steep bank (walking straight up it): bent forward, a free hand put down @@ -1534,7 +1536,8 @@ void World_update(World *world, float dt, float game_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); + o->work_rate = Object_vigor(o) * (1.0f - 0.45f * fminf(o->drunk, 1.0f)) * Ai_workRate(o) + * (Injury_castKind(o) == INJ_ARM ? 0.6f : Injury_castKind(o) == INJ_LEG ? 0.85f : 1.0f); /* (a cast: slower at it) */ /* Every finished tool job is practice */ if (o->work_state.jobs_done > o->jobs_seen) { Ai_practice(o, o->work_state.last_job == JOB_FELL ? ORDER_TREES : ORDER_FIREWOOD, diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index 93d9981..63d6ba0 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -47,6 +47,21 @@ enum { FOCUS_NONE, FOCUS_FLOORS, FOCUS_SIDE, FOCUS_PARTY }; enum { WHY_URINELLA, WHY_AWARENESS, WHY_SHIRT, WHY_COUNT }; enum { WHY_UNKNOWN, WHY_RAISED, WHY_UNDERSTOOD, WHY_YES, WHY_NO }; /* (how far each is: World.why) */ +/* The big old tree (game_bigtree.c, ai_jobs.c): how far its felling has got. */ +enum { BT_STANDING, BT_ROPE, BT_NOTCH, BT_PULL, BT_CUT, BT_FALLING, BT_DOWN }; +typedef struct { + int step; /* BT_*: standing (nobody's looked at it), the rope up, the notch, on the rope, the back cut, falling, down */ + float work; /* person-hours into the notch; real s into the back cut */ + float rope_h; /* m up the tree the rope's caught over a branch (0: not up) */ + float rope_wait; /* game s Enrico's waited for somebody to throw it up */ + int pullers; /* on the rope when it went */ + float off; /* degrees it fell off where it should */ + bool lines_down; /* it took the phone line down */ + bool told; /* (how it went: said) */ +} BigTree; +/* Something heavy on top of somebody, a bad fall: a broken arm or leg (world/injury.c). */ +enum { INJ_NONE, INJ_ARM, INJ_LEG }; + /* The festival weekend (game_festival.c): what's on, for the people (ai_fest.c). */ enum { FEST_NONE, FEST_SETUP, FEST_OPEN, FEST_WARNED, FEST_STORM, FEST_LEAVING, FEST_OVER }; #define FEST_FLOORS 4 @@ -109,6 +124,7 @@ typedef struct World { int why[WHY_COUNT]; /* the why questions: WHY_UNKNOWN .. WHY_NO (game_why.c; talk.c answers them) ... */ int why_heard; /* ... bit k: came up at the Herberge (not seen on the ground) */ Fest fest; /* the festival weekend (set by the game every frame) */ + BigTree bigtree; /* the big old tree's felling */ } World; /* A sound made here (dropped when the list is full: nobody's listening). */