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.gz2.50.0: the clean-up, the drive home, the end
- Guests drop litter all weekend (less where people look out for the place); the ones who didn't enjoy it leave some at their tent - Supporters (one guest pair in four): two shifts for their free ticket, Sunday 10-16 and Monday's clean-up; no fun, no show - Monday 10: the crew still keen and the supporters pick up, each sent to the nearest litter nobody's on; the others go home - Kurti's verdict: clean, a bit left, or a mess (300 EUR waste removal) - Into Volkram's car (marker at the door), out the way you came in, back to Grossbeerenstrasse: the four go through it, "next year?", THE END - Dev menu: "Hurt" line heals sprain, cast, breaks, hospital, scrapes - Fix: pee and splashes no longer reach hidden people (gone home, inside, in a car) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018d6Y6HzDXzEhrHvYRZo3D8
commit_message.txt | 25 +-- entropie/ARCHITECTURE.md | 2 +- entropie/Makefile | 2 +- entropie/README.md | 8 +- entropie/STATE.md | 40 ++++- entropie/commit_message.txt | 25 +-- entropie/src/game/game.c | 1 + entropie/src/game/game.h | 14 +- entropie/src/game/game_arrival.c | 1 + entropie/src/game/game_cleanup.c | 345 ++++++++++++++++++++++++++++++++++++++ entropie/src/game/game_dev.c | 18 +- entropie/src/game/game_festival.c | 13 +- entropie/src/game/game_injury.c | 18 ++ entropie/src/game/game_internal.h | 9 + entropie/src/game/game_meeting.c | 75 ++++++++- entropie/src/game/save.c | 10 +- entropie/src/people/ai_fest.c | 28 +++- entropie/src/world/camp.c | 4 +- entropie/src/world/guests.c | 1 + entropie/src/world/injury.c | 11 ++ entropie/src/world/injury.h | 2 + entropie/src/world/object.h | 5 +- entropie/src/world/site.c | 11 ++ entropie/src/world/site.h | 2 + entropie/src/world/world.h | 1 + 25 files changed, 617 insertions(+), 54 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index 3701b46..698828e 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,16 +1,17 @@ -2.49.0: holding on, and people +2.50.0: the clean-up, the drive home, the end -- E / Q with nothing in front: the hand reaches out and waits, then takes - hold of whatever comes in reach (a loose thing, something fixed, somebody) -- Holding somebody by a body part; how they take it follows friendship and - love (Object.like / love, saved): welcome, tolerated, unwelcome, harassment -- 1 - 5 while holding: pull closer, push away, walk with me (hand in hand), - calm them, let go -- Witnesses: kind acts raise the place's awareness (World.aware, saved), - harassment lowers it; somebody may step in (awareness team more likely) -- Festival: the odd very drunk guest bothers somebody dancing; the review - counts it and who stepped in -- New: people/touch.c, people/ai_touch.c, game/game_touch.c +- Guests drop litter all weekend (less where people look out for the + place); the ones who didn't enjoy it leave some at their tent +- Supporters (one guest pair in four): two shifts for their free ticket, + Sunday 10-16 and Monday's clean-up; no fun, no show +- Monday 10: the crew still keen and the supporters pick up, each sent to + the nearest litter nobody's on; the others go home +- Kurti's verdict: clean, a bit left, or a mess (300 EUR waste removal) +- Into Volkram's car (marker at the door), out the way you came in, back + to Grossbeerenstrasse: the four go through it, "next year?", THE END +- Dev menu: "Hurt" line heals sprain, cast, breaks, hospital, scrapes +- Fix: pee and splashes no longer reach hidden people (gone home, inside, + in a car) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018d6Y6HzDXzEhrHvYRZo3D8 diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index 45bae2e..2c06573 100644 --- a/entropie/ARCHITECTURE.md +++ b/entropie/ARCHITECTURE.md @@ -12,7 +12,7 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL | `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`), 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`, held, hand in hand, stepping in: `ai_touch.c`), touching somebody and what it means (`touch.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`), reaching out, holding people and who saw it (`game_touch.c`), saves (`save.c`), what you hear (`sound.c`) | all | +| `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 supporters' shifts, the clean-up and the drive home (`game_cleanup.c`), the big tree's rope and fall (`game_bigtree.c`), the ambulance and the hospital (`game_injury.c`), reaching out, holding people and who saw it (`game_touch.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 c1ef305..31bbd44 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.49.0 +VERSION := 2.50.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index b905e33..e7f4ea9 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -37,6 +37,10 @@ people living there. dance at every floor that got built, each with its own music. Saturday afternoon a storm: get everybody inside in time (X, then 1: shout it). Sunday they leave, and the crew sums it up at the fire +- **The clean-up and home:** the guests leave litter. Supporters work two + shifts for their free ticket. Monday everyone still keen picks up, Kurti + has a look (clean, or a bill), then it's into Volkram's car and back to + Berlin - the end - **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 @@ -117,7 +121,7 @@ people living there. | I | Show all keys in game | | Tab | Your stats (Left / Right: other page) | | J | Your notebook: the festival, the why questions | -| F1 | Dev menu: pick anyone, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard; time of day, game speed, weather, wind, skip the start | +| F1 | Dev menu: pick anyone, heal them, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard; time of day, game speed, weather, wind, skip the start | | F2 | Dev: the crew overview - who's who, what they do now, the projects (Left / Right) | | Esc | Menu: resume, save, load, keys, quit (the game waits meanwhile) | | F5 / F9 | Quick save / quick load | @@ -165,5 +169,5 @@ Dependencies point downward: `main → game → world/people → render → core ## Version -`make version` prints e.g. `entropie 2.49.0 (build 7776609)`. The build id is +`make version` prints e.g. `entropie 2.50.0 (build 6409893)`. The build id is an md5 of all sources and assets. diff --git a/entropie/STATE.md b/entropie/STATE.md index 76c228c..071eb89 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -92,7 +92,7 @@ things the way they do. back and forth between weekends. (2.44) 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. + (2.46), the clean-up with the supporters, the drive home (2.50). In between: the big old tree and injuries (2.47). ## People @@ -438,6 +438,8 @@ Painted in the rotoscope style of *Waking Life* (2001): 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 +- F1 dev menu, "Hurt": what the picked person has; Left / Right heals it + all (the sprain, the cast, a break, the hospital, scrapes) ### Holding on, and people @@ -518,7 +520,28 @@ Painted in the rotoscope style of *Waking Life* (2001): - F1 dev menu, "The festival": Right before the festival weekend jumps to the last Herberge; on the weekend, each Right jumps to the next moment (the gates, the warning, the storm at its worst, the storm over, Sunday - leaving, the review) + leaving, the review, Monday's clean-up) + +### The clean-up and the drive home + +- The guests drop litter all weekend: cups, cans, paper (more when drunk, + less the more aware the place is). The ones who didn't enjoy it leave + some at their tent +- Supporters: one guest pair in four has a free ticket for two 6 h shifts. + Sunday 10 - 16: they pick up round the camping and the floors while the + others leave. Who had no fun doesn't turn up (and goes home) +- Monday at ten: the clean-up. The crew still happy to help (Volkram, Jana + and Toni always), the supporters' second shift. Each gets a bag and goes + where the most litter is: the camping, the floors that played, the yard. + The rest of the crew goes home in the morning. You can pick up too (a bag + from the box by the container) +- Clean (a tenth left) or four o'clock: Kurti has a look. Clean: you're + welcome back. A bit left: his sons do the rest. A mess: 300 EUR from the + cash box for the waste removal +- Then everybody goes home. Walk up to Volkram's car (the marker) to get + in; once all four are in, it drives off the way you came +- The end: Grossbeerenstrasse, Monday night. The four of you go through how + it went, next year? - then "The end". You can still walk round the kitchen ### Arriving @@ -1678,6 +1701,15 @@ Round 2.49 (holding on, and people): - `World.aware` (saved): eases to the place's mood; +0.01 a kind act seen, -0.03 harassment. Witnesses within 16 m; the nearest one who dares steps in. Festival `harassed` / `stepped` (saved) in the review. - Checked (harness): a shoulder taken and calmed (Toni), led 7.8 m hand in hand (Jana kept 0.6 m beside), a waist unwelcome (Stella pulls away), Kilian after Doro (she says so, he gets the 2.5 h pause), save / load of like, love, aware. +Round 2.50 (the clean-up, the drive home, the end): +- Litter (`ai_fest.c` litter): a guest awake drops LITTER_RATE 0.45 pieces a game hour x (1.25 - aware) x (1 + 0.6 drunk) (supporters x 0.3), none while LITTER_MOST 160 lie about (`Fest.litter`, counted by the game); packing up, (1 - fun) x (1.2 - aware) a piece, three tries. `Site_litter` (owner: the guest - what the clean-up counts). +- Supporters (`Object.supporter`, saved): one guest pair in four (`guests.c`). `game_cleanup.c`: Sunday 10 - 16 (H_SHIFT .. H_SHIFT_END) on ORDER_TRASH; fun under SHOW_UP 0.3: they go home instead (`Object.heading_home`, saved: `ai_fest.c` leave packs the tent and walks them out at the gate - the crew too). +- Monday 10 (H_CLEAN): crew under KEEN 0.35 commitment go home (not Volkram, Jana, Toni); the rest and the supporters each sent to the nearest litter nobody's working within SEND_NEAR 30 m of (again after SEND_AGAIN). Clean (3 or a tenth left) or 16:00: Kurti's verdict (`Festival.verdict`: clean; a bit (12 or 40 %); a mess: MESS_FINE 300 EUR, Kurti's commitment -0.2). Everybody else heads home; the three walk to their car doors (ai.errand), you get in within CAR_REACH 1.8 m of the back door (the quest marker shows it), then it backs out, turns and drives the way in backwards. Saves: a `cleanup` line; never in the car. +- The end (`game_meeting.c` MEET_END): Grossbeerenstrasse, Monday 22:00, the four of you: the result, Kurti, the big tree, harassment / the supporters, "next year?" (Q_AGAIN), THE END. `Meeting.ended`: nothing more; you can walk about. +- `camp.c`: pee and splashes no longer reach people indoors, gone home or in a car (the guests gone home all stand hidden at the gate: they complained in chorus). +- Dev: F1 "Hurt" (`game_injuryHeal`, `Injury_heal`). +- Checked (harness): supporters' Sunday shift 10 of 10, litter 53 -> 7; Monday 11 crew + 10 supporters, 7 -> 4 (the last ones out of reach), verdict "a bit"; you in at the door, the three already there, out along the way in; saved by the car and loaded, the end's lines through to THE END. Healing (dev): a sprain + a leg cast off (speed, jump, pace back exactly), Toni's broken arm while the ambulance was called (up again, the ambulance called off). + ## Open tasks Most important first. @@ -1690,7 +1722,7 @@ Most important first. - Side jobs, still to do: decoration and light as jobs of their own; the man from the regulatory office as a person walking the field; Kerry's figures placed where they go; helpers raking the mown grass - Berlin, still to do: the streets and the way between (the rooms are closed boxes); ordering at the bar; more on the agenda (the "why" questions); the crew missing a weekend now and then - Why questions, still to do: more of them (deposit on cups, free water, a quiet place, ...); what each decision does at the festival -- Next: the clean-up (with the supporters: a free ticket for 2 x 6 h of work), the drive home, the end +- The end, still to do: NPCs taking the stages down; a proper drive through Brandenburg; what "next year?" leads to - 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 @@ -1728,4 +1760,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.49.0 (build 7776609)` +State of `entropie 2.50.0 (build 6409893)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 3701b46..698828e 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,16 +1,17 @@ -2.49.0: holding on, and people +2.50.0: the clean-up, the drive home, the end -- E / Q with nothing in front: the hand reaches out and waits, then takes - hold of whatever comes in reach (a loose thing, something fixed, somebody) -- Holding somebody by a body part; how they take it follows friendship and - love (Object.like / love, saved): welcome, tolerated, unwelcome, harassment -- 1 - 5 while holding: pull closer, push away, walk with me (hand in hand), - calm them, let go -- Witnesses: kind acts raise the place's awareness (World.aware, saved), - harassment lowers it; somebody may step in (awareness team more likely) -- Festival: the odd very drunk guest bothers somebody dancing; the review - counts it and who stepped in -- New: people/touch.c, people/ai_touch.c, game/game_touch.c +- Guests drop litter all weekend (less where people look out for the + place); the ones who didn't enjoy it leave some at their tent +- Supporters (one guest pair in four): two shifts for their free ticket, + Sunday 10-16 and Monday's clean-up; no fun, no show +- Monday 10: the crew still keen and the supporters pick up, each sent to + the nearest litter nobody's on; the others go home +- Kurti's verdict: clean, a bit left, or a mess (300 EUR waste removal) +- Into Volkram's car (marker at the door), out the way you came in, back + to Grossbeerenstrasse: the four go through it, "next year?", THE END +- Dev menu: "Hurt" line heals sprain, cast, breaks, hospital, scrapes +- Fix: pee and splashes no longer reach hidden people (gone home, inside, + in a car) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018d6Y6HzDXzEhrHvYRZo3D8 diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 3f8f66a..4412b76 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -380,6 +380,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); + game_cleanup(game, dt); if (!game->meeting.kind) { game_bigTree(game, dt); game_injury(game, dt); } Ai_setSettled(game->arrival.task == TASK_NONE); tooHeavy(game); diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h index 4d8a8bf..be45abc 100644 --- a/entropie/src/game/game.h +++ b/entropie/src/game/game.h @@ -47,7 +47,7 @@ typedef struct { } Errand; /* A meeting in Berlin (game_meeting.c): Grossbeerenstrasse at the start, the Herberge before every weekend. */ -typedef enum { MEET_NONE, MEET_KREUZBERG, MEET_HERBERGE } MeetingKind; +typedef enum { MEET_NONE, MEET_KREUZBERG, MEET_HERBERGE, MEET_END } MeetingKind; /* (the end: back in Grossbeerenstrasse) */ typedef struct { MeetingKind kind; int number; /* the Herberge: which (1: before the first weekend - who leads what) */ @@ -64,6 +64,7 @@ typedef struct { int fading; /* ... +1 getting dark, -1 light again */ int after; /* what happens once it's dark (AFTER_*: game_meeting.c) */ bool skipping; /* Backspace: the rest of it at once, the first answers */ + bool ended; /* the end: all said (the game is over: you can still walk about) */ } Meeting; /* The ambulance (game_injury.c): called, on its way in, parked (the paramedics), on its way out. */ @@ -90,6 +91,17 @@ typedef struct { int helped; /* the awareness team saw to people (kept here: the world's counter is set every frame) */ int harassed; /* somebody bothered (harassment) ... */ int stepped; /* ... and somebody stepped in */ + /* the clean-up and the drive home (game_cleanup.c) */ + int clean; /* CLEAN_*: not yet, the supporters' Sunday shift, Monday's clean-up, Kurti's look, to the car, driving, home */ + int litter_start; /* the guests' litter lying about when Monday's clean-up started ... */ + int litter_left; /* ... and now (counted every few seconds) */ + int helpers; /* the crew still happy to help on Monday ... */ + int left_early; /* ... and the ones who went home instead */ + int supported; /* supporters who did their Sunday shift ... */ + int supported2; /* ... and Monday's */ + int verdict; /* Kurti on how you leave his land: 0 clean, 1 a bit left, 2 a mess (-1 not yet) */ + float clean_t; /* real s: till the next count; in the car: the drive's clock */ + float drive_s, drive_speed; /* the drive home: m along the way in (backwards), how fast */ } Festival; typedef struct { diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c index fb83b95..d971fe8 100644 --- a/entropie/src/game/game_arrival.c +++ b/entropie/src/game/game_arrival.c @@ -261,6 +261,7 @@ bool Game_questMarker(const Game *game, Vec3 *at) { const Arrival *a = &game->arrival; const Object *p = game->player, *kurti = NULL; if (a->stage == ARRIVAL_DRIVING || p->riding) return false; + if (game->festival.clean == CLEAN_TO_CAR && a->car) { *at = vec3_add(Car_door(a->car, 2), vec3(0.0f, 2.0f, 0.0f)); return true; } /* (home: the car) */ for (int i = 0; i < game->world.count && !kurti; i++) if (game->world.objects[i]->type == OBJ_HUMAN && !strcmp(game->world.objects[i]->name, "Kurti")) kurti = game->world.objects[i]; bool to_kurti = false; diff --git a/entropie/src/game/game_cleanup.c b/entropie/src/game/game_cleanup.c new file mode 100644 index 0000000..19f48db --- /dev/null +++ b/entropie/src/game/game_cleanup.c @@ -0,0 +1,345 @@ +/* + * The clean-up and the drive home (see the story: chapters 5 and 6). + * + * The guests drop litter all weekend (ai_fest.c: cups, cans, paper - less + * where people look out for the place), and the ones who didn't care leave + * some at their tent. One guest pair in four are supporters: a free ticket + * for two shifts of work. The first is Sunday from ten to four, while the + * others pack up: the camping and the floors. Who had no fun doesn't turn up. + * + * Monday at ten the clean-up: the crew still happy to help (commitment; + * Volkram, Jana and Toni always - it's their car home) and the supporters' + * second shift, each with a bag to where the most litter is (the camping, + * the floors that played, the yard). The others go home first thing. Clean + * (a tenth left) or four o'clock: Kurti has a look round - clean, a bit + * left (his sons do the rest), or a mess (the waste removal: 300 EUR from + * the cash box). Then home: Volkram, Jana, Toni and you in Volkram's car + * (walk up to it), out the way you came in, and back in Grossbeerenstrasse + * the end (game_meeting.c, MEET_END). + */ + +#include <math.h> + +#include "game/game_internal.h" +#include "world/car.h" +#include "world/site.h" +#include "world/tent.h" + +/* Hours from the weekend's Friday midnight */ +#define H_SHIFT 58.0f /* Sunday ten: the supporters' first shift ... */ +#define H_SHIFT_END 64.0f /* ... till four */ +#define H_CLEAN 82.0f /* Monday ten: the clean-up ... */ +#define H_CLEAN_END 88.0f /* ... four at the latest */ +#define CLEAN_ENOUGH 0.1f /* of the litter left: clean */ +#define COUNT_EVERY 5.0f /* real s between counts */ +#define KEEN 0.35f /* commitment: still happy to help on Monday */ +#define SHOW_UP 0.3f /* fun: a supporter turns up for a shift */ +#define MESS_FINE 300.0f /* EUR: the waste removal */ +#define CAR_REACH 1.8f /* m from your door: you get in */ +#define CAR_WAIT 90.0f /* real s the others walk to the car (then they're in anyway) */ +#define BACK_OUT 10.0f /* the drive: backed out this far ... */ +#define BACK_SPEED 1.6f +#define TURN_TIME 3.0f /* ... turned round ... */ +#define DRIVE_TOP 6.0f /* ... and off */ +#define DRIVE_ACCEL 1.6f + +static const char *const CAR_CREW[] = { "Volkram", "Jana", "Toni" }; +static const int CAR_SEATS[] = { 0, 1, 3 }; /* (you: 2, in the back) */ + +static float hours(const Game *game) { return game->story_hours - game->weekend_from; } + +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; +} + +static bool carCrew(const Object *o) { + for (int k = 0; k < 3; k++) if (!strcmp(o->name, CAR_CREW[k])) return true; + return false; +} + +/* Here and able: not gone, not in Berlin, not in hospital. */ +static bool here(const Object *o) { + return o->type == OBJ_HUMAN && !o->gone_home && !o->heading_home && !Game_inBerlin(o) && o->hospital_until <= 0.0f && !o->injured && !o->riding; +} + +static bool litter(const Object *o) { + return o->type == OBJ_TRASH && o->mesh.vao && !o->is_held && o->owner && o->owner->guest > 0 && !Game_inBerlin(o); +} + +static int countLitter(const Game *game) { + int n = 0; + for (int i = 0; i < game->world.count; i++) if (litter(game->world.objects[i])) n++; + return n; +} + +static bool onIt(const Object *o) { return o->ai.mode == AI_WORK && o->ai.order == ORDER_TRASH; } + +#define SEND_NEAR 30.0f /* m: a helper working this near a piece already has it */ +#define SEND_AGAIN 0.05f /* game h (a real minute) before somebody done (or stuck) is sent again */ + +/* Everybody on it who isn't yet: to the litter nobody's working near, the nearest first. */ +static void sendOut(Game *game, bool (*who)(const Object *)) { + World *w = &game->world; + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (!here(o) || !who(o) || onIt(o) || Body_isDown(&o->body) || o->ai.talking) continue; + if (o->clean_sent > 0.0f && game->story_hours - o->clean_sent < SEND_AGAIN) continue; + Object *best = NULL; + float best_d = 1e9f; + for (int t = 0; t < w->count; t++) { + Object *l = w->objects[t]; + if (!litter(l)) continue; + bool taken = false; + for (int j = 0; j < w->count && !taken; j++) { + const Object *h = w->objects[j]; + taken = h != o && h->type == OBJ_HUMAN && onIt(h) && hypotf(h->ai.work_area.x - l->position.x, h->ai.work_area.z - l->position.z) < SEND_NEAR; + } + float d = hypotf(l->position.x - o->position.x, l->position.z - o->position.z) + (taken ? 200.0f : 0.0f); + if (d < best_d) { best_d = d; best = l; } + } + if (!best) return; /* (nothing left anywhere) */ + if (o->in_tent) { o->rain_nap = false; Tent_crawlOut(o); } + if (o->body.asleep) Body_getUp(&o->body); + o->needs.sleepiness = fminf(o->needs.sleepiness, 0.5f); + o->drunk = fminf(o->drunk, 0.3f); + o->sitting = false; + Ai_order(o, ORDER_TRASH, best->position); + o->clean_sent = game->story_hours; + } +} + +static bool supporter(const Object *o) { return o->supporter; } +static bool helper(const Object *o) { return o->supporter || (o->cast_id > 0 && !o->lives_here); } + +static void goHome(Object *o, const char *line) { + if (onIt(o)) Ai_stopWork(o); + o->ai.mode = AI_IDLE; + o->heading_home = true; + if (line) Ai_say(o, line); +} + +static void stopAll(Game *game, bool (*who)(const Object *)) { + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (o->type == OBJ_HUMAN && who(o) && onIt(o)) Ai_stopWork(o); + } +} + +/* ---------- The stages ---------- */ + +static void shiftStart(Game *game) { + int came = 0, no = 0; + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (!o->supporter || !here(o)) continue; + if (o->fun < SHOW_UP) { goHome(o, "My shift? After that weekend? No thanks - I'm off."); no++; continue; } + came++; + Ai_say(o, (came % 2) ? "My shift. Right - a bag, and the camping." : "Free ticket, fair deal. Where are the bags?"); + } + game->festival.supported = came; + sendOut(game, supporter); + snprintf(game->story_line, sizeof game->story_line, no ? "Sunday ten: %d supporters start their shift (picking up) - %d didn't turn up" + : "Sunday ten: %d supporters start their first shift - picking up the camping and the floors", came, no); + say(game, game->story_line); + game->message_time = 6.0f; +} + +static void cleanStart(Game *game) { + Festival *fe = &game->festival; + fe->litter_start = fe->litter_left = countLitter(game); + fe->helpers = fe->left_early = fe->supported2 = 0; + for (int i = 0; i < game->world.count; i++) { + Object *o = game->world.objects[i]; + if (!here(o)) continue; + if (o->supporter) { + if (o->fun < SHOW_UP) { goHome(o, "Sorry - I'm going home. Find somebody else."); continue; } + fe->supported2++; + } else if (o->cast_id > 0 && !o->lives_here) { + if (!carCrew(o) && o->commitment < KEEN) { goHome(o, "Sorry, I've got to get back to Berlin - work tomorrow."); fe->left_early++; continue; } + fe->helpers++; + } + } + Object *v = named(game, "Volkram"); + if (v && here(v)) Ai_say(v, "Right, everybody - bags from the box by the container. We leave it like we found it!"); + sendOut(game, helper); + snprintf(game->story_line, sizeof game->story_line, "Monday ten: the clean-up - %d of the crew and %d supporters, %d pieces of litter to pick up", + fe->helpers, fe->supported2, fe->litter_start); + say(game, game->story_line); + game->message_time = 7.0f; +} + +/* Kurti has a look round. */ +static void look(Game *game) { + Festival *fe = &game->festival; + World *w = &game->world; + fe->litter_left = countLitter(game); + float left = fe->litter_start > 0 ? (float)fe->litter_left / (float)fe->litter_start : 0.0f; + fe->verdict = fe->litter_left <= 3 || left <= CLEAN_ENOUGH ? 0 : fe->litter_left <= 12 || left <= 0.4f ? 1 : 2; + stopAll(game, helper); + Object *kurti = named(game, "Kurti"); + static const char *const SAYS[] = { "Clean as you found it - cleaner! You're welcome back next year.", + "A bit's still lying about. The boys will see to the rest.", + "Look at this mess! The waste removal comes out of your box - 300 euros." }; + if (kurti) { + Ai_say(kurti, SAYS[fe->verdict]); + kurti->commitment = clampf(kurti->commitment + (fe->verdict == 0 ? 0.15f : fe->verdict == 2 ? -0.2f : 0.0f), 0.0f, 1.0f); + } + if (fe->verdict == 2) w->cash -= MESS_FINE; + snprintf(game->story_line, sizeof game->story_line, "Kurti: %s", SAYS[fe->verdict]); + say(game, game->story_line); + game->message_time = 7.0f; + /* everybody off home; the four of you to the car */ + for (int i = 0; i < w->count; i++) { + Object *o = w->objects[i]; + if (!here(o) || o->lives_here || !(o->supporter || o->cast_id > 0)) continue; + if (carCrew(o)) continue; + goHome(o, o->supporter ? "Shift done - see you next year!" : "Bye! What a summer."); + } + Object *car = game->arrival.car; + for (int k = 0; k < 3; k++) { + Object *o = named(game, CAR_CREW[k]); + if (!o || !here(o) || !car) continue; + if (onIt(o)) Ai_stopWork(o); + for (int h = 0; h < 2; h++) if (o->hands.item[h]) Hands_drop(o, h); + o->ai.mode = AI_IDLE; + o->ai.errand = true; + o->ai.errand_with = NULL; + o->ai.errand_there = false; + o->ai.errand_to = Car_door(car, CAR_SEATS[k]); + } + Object *v = named(game, "Volkram"); + if (v && here(v)) Ai_say(v, "That's it. Into the car - home!"); + fe->clean = CLEAN_TO_CAR; + fe->clean_t = 0.0f; +} + +static void toCar(Game *game, float dt) { + Festival *fe = &game->festival; + Object *car = game->arrival.car, *p = game->player; + if (!car || game->errand.stage != ERRAND_NONE) return; /* (off at the shops: wait) */ + fe->clean_t += dt; + bool all = true; + for (int k = 0; k < 3; k++) { + Object *o = named(game, CAR_CREW[k]); + if (!o || o->gone_home || Game_inBerlin(o) || o->hospital_until > 0.0f || o->injured) continue; + if (o->riding) continue; + if (o->ai.errand_there || fe->clean_t > CAR_WAIT) { + o->ai.errand = o->ai.errand_there = false; + Car_getIn(car, o, CAR_SEATS[k]); + } else all = false; + } + if (!p->riding) { + Vec3 door = Car_door(car, 2); + if (hypotf(door.x - p->position.x, door.z - p->position.z) > CAR_REACH || Body_isBusy(&p->body) || p->on_ladder) return; + for (int h = 0; h < 2; h++) if (p->hands.item[h] && p->hands.state[h] == HAND_HOLDING) Hands_drop(p, h); + for (int h = 0; h < 2; h++) if (p->hands.state[h] != HAND_FREE) Hands_letGo(p, h); + Car_openBoot(car, false); + Car_getIn(car, p, 2); + game->third_person = false; + say(game, "In you get. Last look at Kurti's farm..."); + game->message_time = 5.0f; + } + if (!all) return; + fe->clean = CLEAN_DRIVING; + fe->clean_t = 0.0f; + fe->drive_s = game->arrival.length; + fe->drive_speed = 0.0f; + Object *kurti = named(game, "Kurti"); + if (kurti) Ai_say(kurti, "Drive safely! And come back!"); +} + +static void drive(Game *game, float dt) { + Festival *fe = &game->festival; + Arrival *a = &game->arrival; + Object *car = a->car; + if (!car) { fe->clean = CLEAN_HOME; game_meetingHome(game); return; } + Vec3 pos, dir; + fe->clean_t += dt; + float back_to = a->length - BACK_OUT; + if (fe->drive_s > back_to) { /* backing out, still nose to the barn */ + fe->drive_s = fmaxf(back_to, fe->drive_s - BACK_SPEED * dt); + game_driveAt(a, fe->drive_s, &pos, &dir); + Car_place(&game->world, car, pos, yaw_facing(dir)); + fe->clean_t = 0.0f; + return; + } + game_driveAt(a, fe->drive_s, &pos, &dir); + if (fe->clean_t < TURN_TIME) { /* turning round */ + Car_place(&game->world, car, pos, yaw_facing(dir) + PI_F * smoothstepf(0.0f, TURN_TIME, fe->clean_t)); + return; + } + fe->drive_speed = fminf(fe->drive_speed + DRIVE_ACCEL * dt, DRIVE_TOP); + fe->drive_s = fmaxf(0.0f, fe->drive_s - fe->drive_speed * dt); + game_driveAt(a, fe->drive_s, &pos, &dir); + Car_place(&game->world, car, pos, yaw_facing(vec3_scale(dir, -1.0f))); + if (fe->drive_s <= 0.0f) { fe->clean = CLEAN_HOME; game_meetingHome(game); } +} + +/* ---------- Every frame ---------- */ + +void game_cleanup(Game *game, float dt) { + Festival *fe = &game->festival; + World *w = &game->world; + if (!game_festivalWeekend(game) || game->meeting.kind || fe->stage < FEST_OPEN) return; + float h = hours(game); + bool counting = fe->clean < CLEAN_TO_CAR && (fe->clean_t -= dt) <= 0.0f; /* (in the car: clean_t is the drive's clock) */ + if (counting) { fe->clean_t = COUNT_EVERY; w->fest.litter = countLitter(game); } + switch (fe->clean) { + case CLEAN_NONE: + if (h >= H_SHIFT && fe->stage >= FEST_LEAVING) { fe->clean = CLEAN_SHIFT; shiftStart(game); } + break; + case CLEAN_SHIFT: + if (h >= H_SHIFT_END) { stopAll(game, supporter); fe->clean = CLEAN_WAIT; break; } + if (counting) sendOut(game, supporter); + break; + case CLEAN_WAIT: + if (h >= H_CLEAN) { fe->clean = CLEAN_ON; cleanStart(game); } + break; + case CLEAN_ON: + if (!counting) break; + fe->litter_left = w->fest.litter; + if (fe->litter_left <= 3 || fe->litter_left <= (int)((float)fe->litter_start * CLEAN_ENOUGH) || h >= H_CLEAN_END) { + if (h >= H_CLEAN_END && fe->litter_left > 3) { Object *v = named(game, "Volkram"); if (v && here(v)) Ai_say(v, "That'll have to do - we have to get going."); } + fe->clean = CLEAN_LOOK; + fe->clean_t = 4.0f; + say(game, fe->litter_left <= 3 ? "All picked up! Kurti comes over for a look" : "Kurti comes over for a look"); + break; + } + sendOut(game, helper); + break; + case CLEAN_LOOK: + if (fe->clean_t <= 0.0f || counting) look(game); + break; + case CLEAN_TO_CAR: toCar(game, dt); break; + case CLEAN_DRIVING: drive(game, dt); break; + default: break; + } +} + +const char *game_cleanupTask(const Game *game) { + const Festival *fe = &game->festival; + static char text[128]; + switch (fe->clean) { + case CLEAN_WAIT: return "Monday at ten: the clean-up"; + case CLEAN_ON: + snprintf(text, sizeof text, "The clean-up: %d of %d pieces of litter left (a bag from the box by the container)", fe->litter_left, fe->litter_start); + return text; + case CLEAN_TO_CAR: return game->player->riding ? "In the car - waiting for the others" : "Home: get into Volkram's car (walk up to the back door)"; + case CLEAN_DRIVING: return "Off home to Berlin"; + default: return NULL; + } +} + +int game_cleanupNotes(const Game *game, char lines[][128], int max) { + const Festival *fe = &game->festival; + int n = 0; + if (fe->clean >= CLEAN_SHIFT && n < max) snprintf(lines[n++], 128, "- Supporters: %d did the Sunday shift%s", fe->supported, fe->clean >= CLEAN_ON ? "" : " (the second: Monday's clean-up)"); + if (fe->clean >= CLEAN_ON && n < max) snprintf(lines[n++], 128, "- The clean-up: %d of the crew, %d supporters; %d of %d pieces left", fe->helpers, fe->supported2, fe->litter_left, fe->litter_start); + if (fe->clean >= CLEAN_TO_CAR && n < max) + snprintf(lines[n++], 128, "- Kurti: %s", fe->verdict == 0 ? "clean - welcome back" : fe->verdict == 1 ? "a bit left (his sons do the rest)" : "a mess (300 EUR for the waste removal)"); + return n; +} diff --git a/entropie/src/game/game_dev.c b/entropie/src/game/game_dev.c index a015fae..7ef781d 100644 --- a/entropie/src/game/game_dev.c +++ b/entropie/src/game/game_dev.c @@ -5,12 +5,14 @@ */ #include <math.h> #include <stdio.h> +#include <string.h> #include "game/game_internal.h" +#include "world/injury.h" #include "world/weather.h" typedef enum { - 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_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_WIND, DEV_START, DEV_FESTIVAL, DEV_BIGTREE, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_HURT, 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_LIKE, DEV_LOVE, DEV_LIKES, /* likes for each job ... */ @@ -148,6 +150,7 @@ void game_dev(Game *game, const Input *input) { case DEV_FEMALE: o->female = !o->female; break; case DEV_URINELLA: o->urinella = !o->urinella; o->urinellas = o->urinella ? POCKET_URINELLAS : 0; break; case DEV_SPORTY: setFitness(o, step01(o->fitness, dir, 0.05f)); break; + case DEV_HURT: game_injuryHeal(game, o); break; case DEV_DRUNK: o->drunk = fminf(1.5f, fmaxf(0.0f, o->drunk + (float)dir * 0.1f)); break; case DEV_ENERGY: n->stamina = step01(n->stamina, dir, 0.1f); break; case DEV_BLADDER: n->bladder = step01(n->bladder, dir, 0.1f); break; @@ -200,6 +203,19 @@ static void devLine(const Game *game, const Object *o, int row, char *out) { case DEV_FEMALE: snprintf(out, 96, "Female: %s", o->female ? "yes" : "no"); break; case DEV_URINELLA: snprintf(out, 96, "Urinella: %s (%d left)", o->urinella ? "yes" : "no", o->urinellas); break; case DEV_SPORTY: snprintf(out, 96, "Sporty: %.2f", o->fitness); break; + case DEV_HURT: { + char what[56]; + if (o->injured) snprintf(what, sizeof what, "a broken %s (lying there)", o->injured == INJ_ARM ? "arm" : "leg"); + else if (o->hospital_until > 0.0f) snprintf(what, sizeof what, "in the hospital"); + else if (o->look.cast) snprintf(what, sizeof what, "%s in a cast", Injury_castKind(o) == INJ_ARM ? "arm" : "leg"); + else snprintf(what, sizeof what, "%s", o->sprain > 0.0f || o->scrapes ? "" : "nothing"); + size_t len = strlen(what); + if (o->sprain > 0.0f) snprintf(what + len, sizeof what - len, "%sa twisted ankle (%.0f h)", len ? ", " : "", (double)(o->sprain / 3600.0f)); + len = strlen(what); + if (o->scrapes) snprintf(what + len, sizeof what - len, "%s%d scrapes", len ? ", " : "", o->scrapes); + snprintf(out, 96, "Hurt: %s%s", what, strcmp(what, "nothing") ? " (<- / ->: heal)" : ""); + break; + } case DEV_DRUNK: snprintf(out, 96, "Drunk: %.1f", o->drunk); break; case DEV_ENERGY: snprintf(out, 96, "Energy: %.0f%%", n->stamina * 100.0f); break; case DEV_BLADDER: snprintf(out, 96, "Bladder: %.0f%%", n->bladder * 100.0f); break; diff --git a/entropie/src/game/game_festival.c b/entropie/src/game/game_festival.c index b1db91d..d4c4c6e 100644 --- a/entropie/src/game/game_festival.c +++ b/entropie/src/game/game_festival.c @@ -312,7 +312,7 @@ static bool review(Game *game, int step) { line(game, "Toni", text); return true; } - case 6: line(game, "Volkram", "Tomorrow we clean up. But tonight - to us!"); return true; + case 6: line(game, "Volkram", "Tomorrow at ten we clean up - all of it. But tonight - to us!"); return true; default: return false; } } @@ -346,7 +346,7 @@ void game_festival(Game *game, float dt) { else if (fe->review < 7) fe->review++; else { fe->review = -1; - snprintf(game->story_line, sizeof game->story_line, "%s is over - %d%%. The clean-up and the drive home are still to come", + snprintf(game->story_line, sizeof game->story_line, "%s is over - %d%%. Monday at ten: the clean-up, then home", Game_festivalName(game), fe->result); say(game, game->story_line); game->message_time = 10.0f; @@ -358,14 +358,15 @@ void game_festival(Game *game, float dt) { void game_festivalStart(Game *game) { memset(&game->festival, 0, sizeof game->festival); game->festival.result = -1; + game->festival.verdict = -1; game->festival.stage = FEST_SETUP; game->world.fest.helped = 0; } /* Dev: the next thing in it. */ -static const float JUMPS[] = { H_GATES - 0.05f, H_WARN - 0.05f, H_STORM + 0.2f, H_CLEAR - 0.05f, H_LEAVE - 0.05f, H_REVIEW - 0.05f }; +static const float JUMPS[] = { H_GATES - 0.05f, H_WARN - 0.05f, H_STORM + 0.2f, H_CLEAR - 0.05f, H_LEAVE - 0.05f, H_REVIEW - 0.05f, 81.95f }; static const char *const JUMP_NAME[] = { "the gates open", "the storm warning", "the storm at its worst", "the storm over", - "Sunday: leaving", "the review" }; + "Sunday: leaving", "the review", "Monday: the clean-up" }; const char *game_festivalJumpText(const Game *game) { if (!game_festivalWeekend(game)) return "go to it ->"; @@ -395,6 +396,9 @@ void game_festivalJump(Game *game) { const char *game_festivalTask(const Game *game) { const Festival *fe = &game->festival; + if (!game_festivalWeekend(game)) return NULL; + const char *clean = game_cleanupTask(game); + if (clean) return clean; switch (fe->stage) { case FEST_SETUP: return "The festival weekend: the last bits before the gates open at six"; case FEST_WARNED: return "The storm's coming at three: get everybody inside (X, then 1: shout it)"; @@ -427,5 +431,6 @@ int game_festivalNotes(const Game *game, char lines[][128], int max) { if (fe->counted && n < max) snprintf(lines[n++], 128, "- The storm: %d inside, %d out in it", fe->inside, fe->caught); if (w->fest.awareness && n < max) snprintf(lines[n++], 128, "- The awareness team helped %d people", fe->helped); if (fe->stage == FEST_OVER && (fe->review < 0 || fe->review > 5) && n < max) snprintf(lines[n++], 128, "- How it went: %d%%", fe->result); + n += game_cleanupNotes(game, lines + n, max - n); return n; } diff --git a/entropie/src/game/game_injury.c b/entropie/src/game/game_injury.c index 1d0aef2..203b5c6 100644 --- a/entropie/src/game/game_injury.c +++ b/entropie/src/game/game_injury.c @@ -209,4 +209,22 @@ void game_injury(Game *game, float dt) { } } +/* Dev: all well again - out of the ambulance, back from the hospital, the cast and the sprain off. */ +void game_injuryHeal(Game *game, Object *o) { + Ambulance *a = &game->ambulance; + if (a->who == o) { + if (a->car && o->riding == a->car) Car_getOut(o); + a->who = NULL; + if (a->stage == AMB_CALLED) { a->stage = AMB_NONE; a->t = 0.0f; } /* (not on its way yet: called off) */ + } + if (o == game->player) game->you_hurt_t = 0.0f; + if (o->hospital_until > 0.0f) { + o->hospital_until = 0.0f; + o->ai.meeting = false; + if (Game_inBerlin(o) && !game->meeting.kind) putOnGround(game, o); + } + Injury_heal(o); + game->sprain_told = false; +} + 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 d489ab9..b54435a 100644 --- a/entropie/src/game/game_internal.h +++ b/entropie/src/game/game_internal.h @@ -58,6 +58,8 @@ 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); +/* Dev: somebody all well again (whatever they had: the ambulance, the hospital, the cast, the sprain). */ +void game_injuryHeal(Game *game, Object *o); bool game_ambulanceHere(const Game *game); /* game_touch.c: your hands and other people - E / Q (used: handled there), holding somebody (the answers 1 - 5), every frame (catching, holding on, who saw what, the place's awareness). */ @@ -67,6 +69,13 @@ void game_touchAnswer(Game *game, int choice); void game_touch(Game *game, float dt); #define FESTIVAL_WEEKEND 6 void game_festival(Game *game, float dt); +/* game_cleanup.c: the supporters' shifts, Monday's clean-up, Kurti's look round, the drive home. */ +enum { CLEAN_NONE, CLEAN_SHIFT, CLEAN_WAIT, CLEAN_ON, CLEAN_LOOK, CLEAN_TO_CAR, CLEAN_DRIVING, CLEAN_HOME }; +void game_cleanup(Game *game, float dt); +const char *game_cleanupTask(const Game *game); +int game_cleanupNotes(const Game *game, char lines[][128], int max); +/* game_meeting.c: the drive home's over - back in Grossbeerenstrasse, the end. */ +void game_meetingHome(Game *game); bool game_festivalWeekend(const Game *game); void game_festivalStart(Game *game); void game_festivalJump(Game *game); diff --git a/entropie/src/game/game_meeting.c b/entropie/src/game/game_meeting.c index dbd3f78..7d9becc 100644 --- a/entropie/src/game/game_meeting.c +++ b/entropie/src/game/game_meeting.c @@ -39,8 +39,8 @@ #define START_HOUR_FIRST 13.0f /* (the first: in by car at one, game.c START_HOUR) */ #define TOP_UP 400.0f /* EUR: another 20 each */ -enum { AFTER_NONE, AFTER_KREUZBERG, AFTER_HERBERGE, AFTER_FIRST_WEEKEND, AFTER_NEXT_WEEKEND }; -enum { Q_NONE, Q_JOIN, Q_NAME, Q_FLOOR, Q_CASH, Q_FOCUS, Q_TOP_UP, Q_WHY }; +enum { AFTER_NONE, AFTER_KREUZBERG, AFTER_HERBERGE, AFTER_FIRST_WEEKEND, AFTER_NEXT_WEEKEND, AFTER_END }; +enum { Q_NONE, Q_JOIN, Q_NAME, Q_FLOOR, Q_CASH, Q_FOCUS, Q_TOP_UP, Q_WHY, Q_AGAIN }; static const char *const NAMES[] = { "Entropie", "Kartoffelrave", "Wriezen Calling" }; static const int FLOORS[] = { PROJECT_OBSTWIESE, PROJECT_HOFBUEHNE, PROJECT_KARTOFFELACKER, PROJECT_HIDDEN }; @@ -82,7 +82,7 @@ static void line(Game *game, const char *name, const char *text) { /* ---------- Sitting down at the table ---------- */ static Vec3 tableAt(MeetingKind kind) { - return kind == MEET_KREUZBERG ? vec3(BERLIN_X, 0.0f, 0.0f) : vec3(BERLIN_X + BERLIN_BAR + 0.6f, 0.0f, 0.0f); + return kind == MEET_KREUZBERG || kind == MEET_END ? vec3(BERLIN_X, 0.0f, 0.0f) : vec3(BERLIN_X + BERLIN_BAR + 0.6f, 0.0f, 0.0f); } static void unseat(Object *o) { @@ -114,7 +114,7 @@ static void sitAt(Game *game, Object *o, int k) { Vec3 table = tableAt(kind); Object *seat = NULL; Vec3 at = table; - if (kind == MEET_KREUZBERG) { + if (kind == MEET_KREUZBERG || kind == MEET_END) { int n = 0; for (int i = 0; i < game->world.count && !seat; i++) { Object *c = game->world.objects[i]; @@ -158,7 +158,7 @@ static void gather(Game *game) { Meeting *m = &game->meeting; int k = 0; sitAt(game, game->player, k++); - if (m->kind == MEET_KREUZBERG) { + if (m->kind == MEET_KREUZBERG || m->kind == MEET_END) { static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" }; for (int f = 0; f < 3; f++) { Object *o = named(game, FRIENDS[f]); if (o) { freshen(o); sitAt(game, o, k++); } } } else { @@ -177,7 +177,10 @@ static void gather(Game *game) { freshen(game->player); game->daycycle.seconds = MEETING_HOUR * 3600.0f; game->third_person = true; - if (m->kind == MEET_KREUZBERG) + if (m->kind == MEET_END) { + game->daycycle.seconds = 22.0f * 3600.0f; + snprintf(game->story_line, sizeof game->story_line, "Grossbeerenstrasse 37 - Monday night, home again"); + } else if (m->kind == MEET_KREUZBERG) snprintf(game->story_line, sizeof game->story_line, "Kreuzberg, Grossbeerenstrasse 37, 3rd floor - a Wednesday evening in March"); else if (m->number == 1) snprintf(game->story_line, sizeof game->story_line, "The Herberge - a week later, everybody's there"); @@ -349,6 +352,9 @@ static void answer(Game *game, int pick) { line(game, NULL, text); break; } + case Q_AGAIN: + line(game, NULL, text); + break; case Q_TOP_UP: if (v) { game->world.cash += TOP_UP; @@ -528,8 +534,55 @@ static bool herbergeLater(Game *game, int step) { } } +/* Back home after the drive: how it all went, and next year? */ +static bool ending(Game *game, int step) { + const Festival *fe = &game->festival; + const World *w = &game->world; + char text[160]; + switch (step) { + case 0: line(game, "Volkram", "Home. I still can't believe we actually did it."); return true; + case 1: + snprintf(text, sizeof text, "%s: %d guests, %d%%. %s", Game_festivalName(game), fe->came, fe->result < 0 ? 0 : fe->result, + fe->result >= 70 ? "Not bad for a bunch of amateurs." : fe->result >= 45 ? "Not perfect - but ours." : "Well... we learned a lot."); + line(game, "Jana", text); + return true; + case 2: + line(game, "Toni", fe->verdict == 0 ? "And Kurti says we're welcome back. Cleanest field he's ever had." + : fe->verdict == 1 ? "Kurti's boys are picking up the last bits. He wasn't cross." + : fe->verdict == 2 ? "Kurti made us pay for the waste removal. Fair enough, honestly." : "We never even cleaned up..."); + return true; + case 3: + if (w->bigtree.step == BT_DOWN) + line(game, "Volkram", w->bigtree.lines_down ? "And the phone line... Enrico still owes the Telekom an apology." + : "And Enrico's tree came down right where it should. I'll never forget that sound."); + else line(game, "Volkram", "And the big old tree's still standing. Next year, Enrico."); + return true; + case 4: + if (fe->harassed > fe->stepped) line(game, "Jana", "Next time we look out for each other better. Nobody gets groped at our festival."); + else if (fe->supported2 > 0) line(game, "Toni", "And the supporters - without them we'd still be picking up cups."); + else line(game, "Toni", "I'm going to sleep for a week."); + return true; + case 5: + line(game, "Jana", "So... next year again?"); + ask(game, Q_AGAIN); + option(game, 0, "Yes! Next year, same place."); + option(game, 1, "Let me sleep for a week first."); + option(game, 2, "Never again. ...Well. Maybe."); + return true; + case 6: line(game, "Volkram", "Ha. We'll talk about it at the Herberge. To us!"); return true; + case 7: + snprintf(game->story_line, sizeof game->story_line, "THE END - %s, %d%%. Thanks for playing!", Game_festivalName(game), fe->result < 0 ? 0 : fe->result); + say(game, game->story_line); + game->message_time = 30.0f; + game->meeting.wait = 2.0f; + return true; + default: return false; + } +} + static bool agenda(Game *game) { Meeting *m = &game->meeting; + if (m->kind == MEET_END) return ending(game, m->step); if (m->kind == MEET_KREUZBERG) return kreuzberg(game, m->step); if (m->number == 1) return herbergeFirst(game, m->step); return herbergeLater(game, m->step); @@ -602,6 +655,7 @@ static void dark(Game *game) { case AFTER_HERBERGE: m->kind = MEET_HERBERGE; m->number++; gather(game); break; case AFTER_FIRST_WEEKEND: firstWeekend(game); break; case AFTER_NEXT_WEEKEND: nextWeekend(game); break; + case AFTER_END: m->kind = MEET_END; gather(game); break; default: break; } m->after = AFTER_NONE; @@ -631,10 +685,13 @@ static void weekendEnd(Game *game) { fadeTo(game, AFTER_HERBERGE); } -/* The agenda's done: off to the ground (or on to the Herberge from Grossbeerenstrasse). */ +void game_meetingHome(Game *game) { fadeTo(game, AFTER_END); } + +/* The agenda's done: off to the ground (or on to the Herberge from Grossbeerenstrasse; the end: that's it). */ static void finish(Game *game) { Meeting *m = &game->meeting; m->skipping = false; + if (m->kind == MEET_END) { m->ended = true; m->speaker = NULL; return; } fadeTo(game, m->kind == MEET_KREUZBERG ? AFTER_HERBERGE : m->number == 1 ? AFTER_FIRST_WEEKEND : AFTER_NEXT_WEEKEND); } @@ -643,7 +700,7 @@ void game_meeting(Game *game, const Input *input, float dt) { weekendEnd(game); if (m->fading > 0 && (m->fade += dt / FADE_TIME) >= 1.0f) { m->fade = 1.0f; dark(game); } else if (m->fading < 0 && (m->fade -= dt / FADE_TIME) <= 0.0f) { m->fade = 0.0f; m->fading = 0; } - if (m->kind == MEET_NONE || m->fading > 0) return; + if (m->kind == MEET_NONE || m->fading > 0 || m->ended) return; if (input->cancel && !m->skipping) { m->skipping = true; say(game, "Skipping the meeting (the first answers)"); } /* everybody looks at who's speaking */ if (m->speaker) @@ -694,7 +751,9 @@ int Game_meetingChoices(const Game *game, const char *out[TALK_CHOICES]) { const char *game_meetingTask(const Game *game) { const Meeting *m = &game->meeting; if (!m->kind) return NULL; + if (m->ended) return "The end. Thanks for playing! (Esc: the menu)"; if (m->asking) return "Your turn: answer with 1 - 5 (Backspace: skip the meeting)"; + if (m->kind == MEET_END) return "Home again: listen (Backspace: skip it)"; return m->kind == MEET_KREUZBERG ? "The first meeting: listen (Backspace: skip it)" : "At the Herberge: listen (Backspace: skip the meeting)"; } diff --git a/entropie/src/game/save.c b/entropie/src/game/save.c index 8fa604c..6801928 100644 --- a/entropie/src/game/save.c +++ b/entropie/src/game/save.c @@ -79,7 +79,7 @@ static const Field PERSON[] = { 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("injured", injured), IN("injury_side", injury_side), FL("hospital_until", hospital_until), IN("cast_until", cast_until), - IN("cast_limb", look.cast), FL("sprain", sprain), FL("like", like), FL("love", love), FL("harass_t", harass_t), VE("last_ground", last_ground), IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("storm_told", storm_told), + IN("cast_limb", look.cast), FL("sprain", sprain), FL("like", like), FL("love", love), FL("harass_t", harass_t), VE("last_ground", last_ground), IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("supporter", supporter), BO("heading_home", heading_home), BO("storm_told", storm_told), VE("inside_door", inside_door), }; @@ -199,6 +199,8 @@ bool Save_write(const Game *game, const char *dir, const char *name, char *err, fprintf(f, "festival %d %d %d %d %d %u %d %.9g %d %d %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, fe->harassed, fe->stepped); fprintf(f, "aware %.9g\n", (double)w->aware); + fprintf(f, "cleanup %d %d %d %d %d %d %d %d\n", fe->clean, fe->litter_start, fe->litter_left, fe->helpers, fe->left_early, fe->supported, + fe->supported2, fe->verdict); fprintf(f, "told %d %d\n", game->rain_told, game->wet_told); fprintf(f, "story %.9g\n", (double)game->story_hours); fprintf(f, "cash %.9g\n", (double)w->cash); @@ -469,6 +471,12 @@ static void applyGame(Game *game, const Parsed *p) { 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, "aware")) w->aware = numF(&s); + else if (!strcmp(key, "cleanup")) { + Festival *fe = &game->festival; + fe->clean = numI(&s); fe->litter_start = numI(&s); fe->litter_left = numI(&s); fe->helpers = numI(&s); fe->left_early = numI(&s); + fe->supported = numI(&s); fe->supported2 = numI(&s); fe->verdict = numI(&s); + if (fe->clean >= CLEAN_DRIVING) fe->clean = CLEAN_TO_CAR; /* (saved before: never in the car) */ + } 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); diff --git a/entropie/src/people/ai_fest.c b/entropie/src/people/ai_fest.c index 1246686..a867ca3 100644 --- a/entropie/src/people/ai_fest.c +++ b/entropie/src/people/ai_fest.c @@ -23,6 +23,7 @@ #include "people/ai_internal.h" #include "world/car.h" #include "world/guests.h" +#include "world/site.h" #include "world/tent.h" #include "world/why.h" @@ -38,6 +39,8 @@ #define HELP_LOOK 45.0f /* m: the awareness team sees somebody lying drunk this near */ #define GATE_REACH 1.5f #define HARASS_EVERY 900.0f /* real s: a drunk party guest dancing gets too close to somebody about this often */ +#define LITTER_RATE 0.45f /* pieces a guest drops a game hour awake (x 1.25 - the place's awareness, more drunk) ... */ +#define LITTER_MOST 160 /* ... no more while this much lies about */ static bool crewMember(const Object *o) { return o->type == OBJ_HUMAN && o->cast_id > 0; } @@ -203,12 +206,25 @@ static bool camp(Object *self, Object *const *objects, int count, float dt) { return true; } +/* A cup, a can, a bit of paper: dropped where they stand (less where people look out for the place). */ +static void litter(Object *self) { + const Fest *f = &g_world->fest; + if (f->stage < FEST_OPEN || f->stage > FEST_LEAVING || f->litter >= LITTER_MOST || self->heading_home) return; + float rate = LITTER_RATE * (1.25f - g_world->aware) * (1.0f + 0.6f * fminf(self->drunk, 1.0f)) * (self->supporter ? 0.3f : 1.0f); + if (random01(&self->ai) >= rate * g_world->game_dt / 3600.0f) return; + Vec3 f2 = Object_facing(self); + Vec3 at = vec3_add(self->position, vec3(-f2.z * 0.3f, 0.0f, f2.x * 0.3f)); + at.y = World_groundHeight(g_world, at.x, at.z, self->position.y + 1.0f); + Site_litter(g_world, at, self->guest + (int)(random01(&self->ai) * 7.0f), self); +} + static float leaveHour(const Object *self) { return 10.0f + 4.5f * (float)((self->guest - 1) / 2) / (float)GUEST_SPOTS; } /* (two by two, from ten) */ /* Sunday: off home - the tent down and gone, out at the gate. */ static bool leave(Object *self, Object *const *objects, int count, float dt) { - if (g_world->fest.stage != FEST_LEAVING) return false; - if (g_world->hour < leaveHour(self)) return false; + bool sunday = self->guest && !self->supporter && g_world->fest.stage == FEST_LEAVING && g_world->hour >= leaveHour(self); + if (!sunday && !self->heading_home) return false; + if (self->ai.mode == AI_WORK) Ai_stopWork(self); stopDancing(self); self->sitting = false; if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); } @@ -217,6 +233,9 @@ static bool leave(Object *self, Object *const *objects, int count, float dt) { if (tent) { /* (packed up and away with them) */ if (!ai_goToPoint(self, Tent_door(tent), 1.2f, objects, count, dt)) return true; Tent_takeDown(objects, count, tent); + float messy = (1.0f - self->fun) * (1.2f - g_world->aware); /* (left behind: the ones who didn't care) */ + for (int k = 0; k < 3 && g_world->fest.litter < LITTER_MOST + 30; k++) + if (self->guest && random01(&self->ai) < messy) Site_litter(g_world, vec3_add(Tent_door(tent), vec3(0.4f * (float)k - 0.2f, 0.0f, 0.5f)), self->guest + k * 3, self); Object_useUp(tent); Ai_say(self, "That's the tent packed. What a weekend!"); return true; @@ -297,10 +316,11 @@ bool ai_festival(Object *self, Object *const *objects, int count, float dt) { self->needs.hunger = fminf(self->needs.hunger, 0.3f); self->needs.thirst = fminf(self->needs.thirst, 0.3f); partyDrinks(self); + if (!self->body.asleep && !self->in_tent && !Body_isDown(&self->body)) litter(self); if (!f->on && !self->body.asleep && g_world->hour > 9.0f && f->stage == FEST_OPEN) self->fun = fmaxf(0.0f, self->fun - FUN_BORED * dt); } - if (self->guest && f->stage == FEST_LEAVING && (self->in_tent || self->body.asleep) && g_world->hour >= leaveHour(self)) { + if (((self->guest && !self->supporter && f->stage == FEST_LEAVING && g_world->hour >= leaveHour(self)) || self->heading_home) && (self->in_tent || self->body.asleep)) { if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); } /* (time to go: up, even after a long night) */ if (self->body.asleep) { self->drunk = fminf(self->drunk, 0.5f); self->needs.sleepiness = fminf(self->needs.sleepiness, 0.6f); Body_getUp(&self->body); } return true; @@ -311,8 +331,8 @@ bool ai_festival(Object *self, Object *const *objects, int count, float dt) { AiMode m = self->ai.mode; if (m != AI_IDLE && m != AI_WANDER && m != AI_TABLE) { stopDancing(self); return false; } /* (busy with something) */ if (seeTo(self, objects, count, dt)) return true; + if ((self->guest || self->heading_home) && leave(self, objects, count, dt)) return true; if (self->guest) { - if (leave(self, objects, count, dt)) return true; if (camp(self, objects, count, dt)) return true; Object *tent = Tent_of(objects, count, self); if (tent && tent->tent_stage < TENT_UP) { stopDancing(self); return false; } /* (the tent first: ai.c pitchTents) */ diff --git a/entropie/src/world/camp.c b/entropie/src/world/camp.c index 9bf5a78..12cf0af 100644 --- a/entropie/src/world/camp.c +++ b/entropie/src/world/camp.c @@ -479,7 +479,7 @@ void Camp_water(Object *const *objects, int count, const Object *by, Vec3 point, if (o->type == OBJ_PUDDLE && o->mesh.vao && o->size.x > 0.0f && d < WASH_RADIUS + o->size.x && o->work > 0.0f && o->variant == 0) { o->work -= WASH_RATE * dt; /* washed away (see the world's drying puddles) */ - } else if (o->is_character && o != by && d < 1.0f) { + } else if (o->is_character && o != by && d < 1.0f && o->indoors <= 0.0f && !o->riding) { o->dirt = fmaxf(0.0f, o->dirt - 0.5f * dt); if (o->type == OBJ_HUMAN && o->ai.speech_time <= 0.0f) Ai_say(o, SPLASHED[(unsigned)(o->position.x * 10.0f + (float)i) % 4u]); @@ -498,7 +498,7 @@ void Camp_pee(Object *const *objects, int count, const Object *by, Vec3 point, f quench(objects, count, by, point, liters, dt, true); for (int i = 0; i < count; i++) { Object *o = objects[i]; - if (o == by || flatDist(o->position, point) > 0.9f) continue; + if (o == by || flatDist(o->position, point) > 0.9f || o->indoors > 0.0f || o->riding) continue; /* (inside, gone home, in a car: not there) */ if (o->is_character) { o->dirt = fminf(1.0f, o->dirt + 0.08f * dt); if (o->type == OBJ_HUMAN && o->ai.speech_time <= 0.0f) diff --git a/entropie/src/world/guests.c b/entropie/src/world/guests.c index 4abca85..658d780 100644 --- a/entropie/src/world/guests.c +++ b/entropie/src/world/guests.c @@ -37,6 +37,7 @@ Object *Guest_create(int k, Vec3 at) { Object *o = Object_create(OBJ_HUMAN, PALETTE_MODERN, at, vec3(0.5f, 1.75f, 0.25f), kg); if (!o) return NULL; o->guest = k; + o->supporter = ((k - 1) / 2) % 4 == 0; /* (one pair in four: a free ticket for 2 x 6 h of work) */ char name[32]; snprintf(name, sizeof name, "%s", female ? WOMEN[(int)(dice(k, 3) * COUNTOF(WOMEN)) % COUNTOF(WOMEN)] : MEN[(int)(dice(k, 3) * COUNTOF(MEN)) % COUNTOF(MEN)]); diff --git a/entropie/src/world/injury.c b/entropie/src/world/injury.c index 82e9a62..f230ef8 100644 --- a/entropie/src/world/injury.c +++ b/entropie/src/world/injury.c @@ -77,3 +77,14 @@ void Injury_update(Object *who, float game_dt) { sprainOn(who, false); if (who->type != OBJ_PLAYER) Ai_say(who, "Ankle's fine again."); } + +void Injury_heal(Object *who) { + if (who->sprain > 0.0f) { who->sprain = 0.0f; sprainOn(who, false); } + if (who->look.cast) Injury_setCast(who, Injury_castKind(who), (who->look.cast - 1) % 2, false); + who->cast_until = 0; + if (who->injured) { /* (lying there with it: up again) */ + who->injured = INJ_NONE; + who->body.down_time = 0.0f; + } + who->scrapes = 0; +} diff --git a/entropie/src/world/injury.h b/entropie/src/world/injury.h index 190a9cd..0edbe12 100644 --- a/entropie/src/world/injury.h +++ b/entropie/src/world/injury.h @@ -20,6 +20,8 @@ void Injury_setCast(Object *who, int kind, int side, bool on); bool Injury_sprain(World *world, Object *who); /* Every frame: the sprain wearing off. */ void Injury_update(Object *who, float game_dt); +/* All well again (dev): the sprain, the cast, a break they're lying with, the scrapes. */ +void Injury_heal(Object *who); /* What they have on: INJ_* of the cast (from look.cast). */ int Injury_castKind(const Object *who); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 45abcad..39b75ad 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -325,7 +325,10 @@ typedef struct Object { int guest; /* people: a guest at the festival (which: world/guests.c), 0 none ... */ float fun; /* ... 0..1 how much they enjoy it ... */ int camp_spot; /* ... got to their place at the camping (1; 0 not yet) ... */ - bool gone_home; /* ... left on Sunday (out of sight at the gate, nothing more) */ + bool gone_home; /* ... left on Sunday (out of sight at the gate, nothing more) ... */ + bool supporter; /* ... a supporter: a free ticket for two shifts of work (Sunday, the clean-up on Monday) ... */ + float clean_sent; /* people: sent to pick up litter at this story hour (game_cleanup.c: not again at once) */ + bool heading_home; /* people: off home now (their tent packed, out at the gate: ai_fest.c) - the crew after the clean-up too */ 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) ... */ diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index 107de95..a9dd171 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -874,6 +874,17 @@ static bool addTrash(World *world, Vec3 where, int n) { vec3(0.0f, (float)n * 2.3f, 0.0f)); } +/* A guest's litter: paper, a can, a plastic cup, there (by: whose it was). */ +Object *Site_litter(World *world, Vec3 at, int n, struct Object *by) { + static const Palette KINDS[] = { PALETTE_WARM, PALETTE_MODERN, PALETTE_STANDARD }; + Palette kind = KINDS[(unsigned)n % 3u]; + Object *o = Object_create(OBJ_TRASH, kind, vec3_add(at, vec3(0.0f, 0.15f, 0.0f)), vec3_zero(), TRASH_WEIGHT[kind]); + if (!o) return NULL; + Object_setRotation(o, vec3(0.0f, (float)n * 2.3f, 0.0f)); + o->owner = by; + return World_add(world, o) ? o : NULL; +} + /* The other garages: a row of eight under one roof, doors towards the orchard. The open ones are full of junk. */ static bool buildGarages(World *world) { diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h index b73d791..7981e15 100644 --- a/entropie/src/world/site.h +++ b/entropie/src/world/site.h @@ -61,6 +61,8 @@ bool Site_inHouse(float x, float z); void Site_arrivalPath(Vec3 *points, int *n, float *park_yaw); /* Where the yard is (world); the things to play with lie around it. */ Vec3 Site_yard(void); +/* A guest's litter dropped there (paper, a can, a cup; n picks which; by: whose - the clean-up counts those). */ +struct Object *Site_litter(World *world, Vec3 at, int n, struct Object *by); /* The main building of the courtyard: the first thing you look at. */ Vec3 Site_landmark(void); diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index ee9bb33..c5e483d 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -77,6 +77,7 @@ typedef struct { bool awareness; /* the why questions said yes to: an awareness team on shift ... */ bool shirts; /* ... no shirt, no service */ int helped; /* the awareness team saw to somebody lying drunk: this often (counted by them, ai_fest.c) */ + int litter; /* the guests' litter lying about now (counted by the game: they stop dropping more at LITTER_MOST) */ } Fest; typedef struct World {