/* * The festival (see the story: chapter 4) - the last weekend on the ground. * * Friday morning everybody's there for the last bits; at six the gates open * and the guests come in from the chicken farm's yard (two waves: Friday * evening, Saturday late morning), up to the camping beyond the strip of * wood, tents up, then off to whatever floor is playing. A floor plays (its * own music, world/../core/audio.c) only if its stage got far enough - its * deck at least; the better built, the more fun. Each has its times. * * Saturday at two the storm warning: a heavy thunderstorm at three. The crew * know; a floor with a proper stage announces it; everybody else has to be * told - by you (X, then 1: shout it) and the crew (the awareness team, if * there is one, goes round everywhere, the tents too). Inside is safe: the * main building, the barn, your garages' kitchen. A tent or a pavilion is * not. At a quarter past three it's at its worst: who's still out in it is * counted (and some get knocked over). At half past four it's over, and the * music starts again an hour later. * * Sunday from ten the guests pack up and go. At four everybody at the fire: * how it went (the guests' fun, the storm, the awareness team) - the result. */ #include #include "game/game_internal.h" #include "world/cast.h" #include "world/guests.h" #include "world/stage.h" #include "world/weather.h" #include "world/why.h" /* Hours from the weekend's Friday midnight */ /* (everybody is on the ground from Friday at ten: game_meeting.c FESTIVAL_ARRIVE) */ #define H_GATES 18.0f /* the gates open */ #define H_WAVE1_END 23.0f /* the first guests in by then ... */ #define FIRST_WAVE 24 #define H_WAVE2 34.0f /* ... the rest Saturday late morning */ #define H_WAVE2_END 37.0f #define H_WARN 38.0f /* Saturday two o'clock: the storm warning */ #define H_CLOUDS 38.66f /* ... the first rain */ #define H_STORM 39.0f /* ... three: the storm */ #define H_WORST 39.25f /* ... at its worst: who's out in it is counted */ #define H_CLEAR 40.5f /* ... over */ #define H_LEAVE 58.0f /* Sunday ten: the guests go */ #define H_REVIEW 64.0f /* Sunday four: at the fire, how it went */ #define REVIEW_GAP 4.5f /* s between the lines */ #define ANNOUNCE 45.0f /* m: a stage with a sound system tells those this near about the storm */ #define DANCE_OUT 6.0f /* m in front of a stage: the dance floor's middle */ static const float FLOOR_BPM[FEST_FLOORS] = { 118.0f, 144.0f, 100.0f, 130.0f }; /* (as the music: core/audio.c FLOOR_BPM) */ static const int FLOOR_PROJECT[FEST_FLOORS] = { PROJECT_HOFBUEHNE, PROJECT_OBSTWIESE, PROJECT_KARTOFFELACKER, PROJECT_HIDDEN }; static const char *const FLOOR_NAME[FEST_FLOORS] = { "the Hofbuehne", "the Obstwiese", "the Kartoffelacker", "the hidden floor" }; /* When each floor plays (weekend hours): the bands in the evenings, psytrance through the nights, the Kartoffelacker by day, techno late at night */ static const float PLAYS[FEST_FLOORS][3][2] = { { { 19.0f, 25.0f }, { 36.5f, 38.0f }, { 41.5f, 49.0f } }, { { 22.0f, 32.0f }, { 45.0f, 58.0f }, { 0.0f, 0.0f } }, { { 34.0f, 38.0f }, { 41.5f, 44.5f }, { 0.0f, 0.0f } }, { { 24.5f, 31.0f }, { 49.0f, 56.0f }, { 0.0f, 0.0f } }, }; static float hours(const Game *game) { return game->story_hours - game->weekend_from; } bool game_festivalWeekend(const Game *game) { return game->weekend >= FESTIVAL_WEEKEND; } 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 onGround(const Object *o) { return o->type == OBJ_HUMAN && !o->gone_home && !Game_inBerlin(o); } /* How good a floor is: what got built (the deck at least, else it doesn't play). */ static float goodness(const Object *stage) { if (!stage) return 0.0f; switch (stage->build_stage) { case BUILD_FLOOR: return 0.45f; case BUILD_ROOF: return 0.65f; case BUILD_DECOR: return 0.85f; case BUILD_DONE: return 1.0f; default: return 0.0f; } } static bool playsAt(int k, float h) { for (int s = 0; s < 3; s++) if (h >= PLAYS[k][s][0] && h < PLAYS[k][s][1]) return true; return false; } /* What the people need to know (the world's Fest), every frame. */ static void tellWorld(Game *game, float dt) { World *w = &game->world; Fest *f = &w->fest; Festival *fe = &game->festival; float h = hours(game); f->stage = fe->stage; f->on = 0; f->clock += dt; if (f->clock > 3600.0f) f->clock -= 3600.0f; for (int k = 0; k < FEST_FLOORS; k++) { const Object *s = Stage_of(w->objects, w->count, (Project)FLOOR_PROJECT[k]); f->good[k] = goodness(s); f->bpm[k] = FLOOR_BPM[k]; if (s) { f->front[k] = yaw_front(s->rotation.y); /* (a stage's front: its local +Z) */ f->dance[k] = vec3_add(s->position, vec3_scale(f->front[k], DANCE_OUT)); f->dance[k].y = World_groundHeight(w, f->dance[k].x, f->dance[k].z, s->position.y + 3.0f); } if (fe->stage == FEST_OPEN && f->good[k] > 0.0f && playsAt(k, h)) f->on |= 1u << k; } fe->played |= f->on; f->shelter[0] = Site_houseDoor(); f->shelter[1] = Site_barnDoor(); f->shelter[2] = Site_rainRoom(); f->awareness = w->why[WHY_AWARENESS] == WHY_YES; f->shirts = w->why[WHY_SHIRT] == WHY_YES; if (f->helped > fe->helped) fe->helped = f->helped; /* (counted by the people) */ f->helped = fe->helped; } /* ---------- The guests ---------- */ static float dueAt(int k) { if (k <= FIRST_WAVE) return H_GATES + (H_WAVE1_END - H_GATES) * (float)((k - 1) / 2 * 2) / (float)FIRST_WAVE; return H_WAVE2 + (H_WAVE2_END - H_WAVE2) * (float)((k - FIRST_WAVE - 1) / 2 * 2) / (float)(GUESTS - FIRST_WAVE); } static void comeIn(Game *game, int k) { World *w = &game->world; Vec3 gate = Guest_gate(); gate.x += (k % 2 ? 0.7f : -0.7f); gate.y = World_groundHeight(w, gate.x, gate.z, 50.0f); Object *o = Guest_create(k, gate); if (!o) return; /* what the Herberge said yes to */ if (w->why[WHY_URINELLA] == WHY_YES && o->female) { o->urinella = true; o->urinellas = 3; o->urinella_tried = 1; o->fun += 0.04f; } if (w->why[WHY_SHIRT] == WHY_YES) { if (o->female) o->fun += 0.04f; else o->look.keeps_shirt = true; } if (w->why[WHY_AWARENESS] == WHY_YES) o->fun += 0.02f; Vec3 to = vec3_sub(Guest_campSpot(k), gate); o->rotation.y = atan2f(to.z, to.x) + PI_F / 2.0f; /* (a person's yaw: Object_facing) */ Object_syncColliders(o); if (!World_add(w, o)) return; game->festival.came = k; } static void guestsIn(Game *game, float h) { Festival *fe = &game->festival; while (fe->came < GUESTS && h >= dueAt(fe->came + 1)) { comeIn(game, fe->came + 1); if (fe->came == 2) say(game, "The first guests are coming in - from the chicken farm's yard, up to the camping beyond the wood"); if (fe->came == FIRST_WAVE + 2) say(game, "More guests coming in for Saturday"); } } /* ---------- The storm ---------- */ static int tellStorm(Game *game, Vec3 from, float within, bool heard_only) { int n = 0; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (!onGround(o) || o->storm_told || o->indoors > 0.0f) continue; if (heard_only && (o->body.asleep || o->in_tent)) continue; if (hypotf(o->position.x - from.x, o->position.z - from.z) > within) continue; o->storm_told = true; o->ai.talking = false; n++; } return n; } int game_shoutStorm(Game *game) { say(game, "\"Storm! Everybody inside - the barn, the house, the garages!\""); return tellStorm(game, game->player->position, 35.0f, true); } static void warn(Game *game, float h) { World *w = &game->world; for (int i = 0; i < w->count; i++) { /* the crew know (Volkram's phone) */ Object *o = w->objects[i]; if (onGround(o) && o->cast_id > 0) o->storm_told = true; } for (int k = 0; k < FEST_FLOORS; k++) { /* a stage with a sound system: announced from it */ if (!playsAt(k, h - 0.02f) || w->fest.good[k] < 0.65f) continue; tellStorm(game, w->fest.dance[k], ANNOUNCE, true); Object *lead = Cast_leadOf(w->objects, w->count, (Project)FLOOR_PROJECT[k]); if (lead && lead != game->player && onGround(lead)) Ai_say(lead, "Storm warning! Everybody off the floor and inside - the barn, the house!"); } Object *v = named(game, "Volkram"); if (v) Ai_say(v, "Storm warning - a big one at three! Everybody inside, tell the guests!"); say(game, "Storm warning: a heavy thunderstorm at three. Get everybody inside - the main building, the barn, " "your garages (X, then 1: shout it). Not the tents, not the pavilions!"); game->message_time = 9.0f; } static void worst(Game *game) { Festival *fe = &game->festival; World *w = &game->world; fe->caught = fe->inside = 0; int knocked = 0; for (int i = 0; i < w->count; i++) { Object *o = w->objects[i]; if (!onGround(o)) continue; if (o->indoors > 0.0f) { fe->inside++; continue; } fe->caught++; if (o->guest) o->fun = fmaxf(0.0f, o->fun - 0.3f); else o->commitment = fmaxf(0.0f, o->commitment - 0.1f); if (!o->in_tent && !o->body.asleep && !Body_isDown(&o->body) && ((i * 7 + fe->caught) % 4) == 0) { /* (a gust, flying things: down) */ Body_fall(&o->body, vec3_normalize(vec3(w->wind.x + 0.01f, 0.0f, w->wind.z))); knocked++; } } if (game->player->indoors <= 0.0f && !game->player->in_tent) fe->caught++; static char line[160]; if (fe->caught == 0) snprintf(line, sizeof line, "The storm's at its worst - and everybody's inside. Well done!"); else snprintf(line, sizeof line, "The storm's at its worst: %d still out in it%s", fe->caught, knocked ? " - people knocked over by the gusts!" : ""); say(game, line); game->message_time = 7.0f; fe->counted = true; } static void allClear(Game *game) { World *w = &game->world; for (int i = 0; i < w->count; i++) { Object *o = w->objects[i]; o->storm_told = false; if (o->type == OBJ_HUMAN && !o->gone_home && o->indoors > 3.0f && vec3_length(o->inside_door) > 0.1f) o->indoors = 2.0f + 0.4f * (float)(i % 20); /* (out again, a few at a time) */ } say(game, "The storm's passed. Out you come - the music's back at half past five"); } /* ---------- Sunday: how it went ---------- */ static float guestFun(const Game *game, int *n) { float sum = 0.0f; *n = 0; for (int i = 0; i < game->world.count; i++) { const Object *o = game->world.objects[i]; if (o->type == OBJ_HUMAN && o->guest) { sum += o->fun; (*n)++; } } return *n ? sum / (float)*n : 0.0f; } static int score(const Game *game) { int n; float fun = guestFun(game, &n); int played = 0; for (int k = 0; k < FEST_FLOORS; k++) if (game->festival.played & (1u << k)) played++; float s = 0.6f * fun + 0.1f * (float)played / (float)FEST_FLOORS + 0.15f * (n ? (float)n / (float)GUESTS : 0.0f); s += game->festival.caught == 0 ? 0.15f : 0.15f * fmaxf(0.0f, 1.0f - (float)game->festival.caught / 10.0f); return (int)lroundf(clampf(s, 0.0f, 1.0f) * 100.0f); } static void line(Game *game, const char *who, const char *text) { Object *o = named(game, who); if (o && onGround(o)) Ai_say(o, text); snprintf(game->story_line, sizeof game->story_line, "%s: %s", who, text); say(game, game->story_line); game->message_time = REVIEW_GAP; } static bool review(Game *game, int step) { Festival *fe = &game->festival; World *w = &game->world; char text[160]; switch (step) { case 0: snprintf(text, sizeof text, "That was %s! %d guests came - and we built it all ourselves.", Game_festivalName(game), fe->came); line(game, "Volkram", text); return true; case 1: { int best = -1, missing = -1; for (int k = 0; k < FEST_FLOORS; k++) { if (!(fe->played & (1u << k))) { if (missing < 0) missing = k; continue; } if (best < 0 || w->fest.good[k] > w->fest.good[best]) best = k; } if (best < 0) snprintf(text, sizeof text, "Not one floor was ready to play. People brought their own music..."); else if (missing >= 0) snprintf(text, sizeof text, "The %s rocked. The %s never played - not built.", FLOOR_NAME[best] + 4, FLOOR_NAME[missing] + 4); else snprintf(text, sizeof text, "All four floors played - %s was the best.", FLOOR_NAME[best]); line(game, "Jana", text); return true; } case 2: if (!fe->counted) return false; if (fe->caught == 0) snprintf(text, sizeof text, "The storm: everybody got inside in time. That's what I call organised."); else snprintf(text, sizeof text, "The storm, though: %d were out in it. That could have gone badly.", fe->caught); line(game, "Volkram", text); return true; case 3: if (w->fest.awareness) snprintf(text, sizeof text, "The awareness team looked after %d who'd had too much.", fe->helped); else snprintf(text, sizeof text, "People lay drunk in the grass and nobody looked. Next time: an awareness team."); line(game, w->fest.awareness ? "Stella" : "Julia", text); return true; case 4: if (fe->harassed == 0) { snprintf(text, sizeof text, "And nobody got bothered or groped all weekend. That's the festival I want."); line(game, "Jana", text); return true; } snprintf(text, sizeof text, "%d times somebody got groped or bothered. %d times somebody stepped in.", fe->harassed, fe->stepped); line(game, "Jana", text); return true; case 5: { int n; float fun = guestFun(game, &n); fe->result = score(game); snprintf(text, sizeof text, "The guests? %s (%d%%)", fun > 0.8f ? "They loved it - they want to come back!" : fun > 0.6f ? "They had a good time." : fun > 0.4f ? "It was okay for them. Just okay." : "Honestly, most were disappointed.", fe->result); line(game, "Toni", text); 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; } } /* ---------- Every frame ---------- */ void game_festival(Game *game, float dt) { Festival *fe = &game->festival; World *w = &game->world; if (!game_festivalWeekend(game) || game->meeting.kind) { if (fe->stage == FEST_NONE) { w->fest.stage = FEST_NONE; w->fest.on = 0; } return; } float h = hours(game); int was = fe->stage; if (fe->stage == FEST_NONE) fe->stage = FEST_SETUP; if (fe->stage == FEST_SETUP && h >= H_GATES) { fe->stage = FEST_OPEN; say(game, "Six o'clock: the gates are open! The festival's on"); game->message_time = 6.0f; } if (fe->stage == FEST_OPEN && h >= H_WARN && h < H_CLEAR && !fe->counted) { fe->stage = FEST_WARNED; } if (fe->stage == FEST_WARNED && h >= H_STORM) fe->stage = FEST_STORM; if (fe->stage == FEST_STORM && h >= H_WORST && !fe->counted) worst(game); if (fe->stage == FEST_STORM && h >= H_CLEAR) { fe->stage = FEST_OPEN; allClear(game); } if (fe->stage == FEST_OPEN && h >= H_LEAVE) { fe->stage = FEST_LEAVING; say(game, "Sunday: the guests start packing up and going home"); } if (fe->stage == FEST_LEAVING && h >= H_REVIEW) { fe->stage = FEST_OVER; fe->review = 0; fe->review_t = 1.0f; } tellWorld(game, dt); if (was < FEST_WARNED && (fe->stage == FEST_WARNED || fe->stage == FEST_STORM)) warn(game, h); /* the weather: fine, but for the storm */ if (fe->stage == FEST_WARNED) { w->weather = h >= H_CLOUDS ? WEATHER_SHOWER : WEATHER_DRY; w->wind_kind = WIND_BREEZE; } else if (fe->stage == FEST_STORM) { w->weather = WEATHER_RAIN; w->wind_kind = WIND_STORM; } else if (fe->stage == FEST_OVER) { w->weather = WEATHER_AUTO; w->wind_kind = WIND_AUTO; } else { w->weather = WEATHER_DRY; w->wind_kind = h >= H_CLEAR && h < H_CLEAR + 1.0f ? WIND_BREEZE : WIND_AUTO; } if (fe->stage >= FEST_OPEN && fe->stage <= FEST_LEAVING) guestsIn(game, h); if (fe->stage == FEST_OVER && fe->review >= 0 && (fe->review_t -= dt) <= 0.0f) { if (review(game, fe->review)) { fe->review_t = REVIEW_GAP; fe->review++; } else if (fe->review < 7) fe->review++; else { fe->review = -1; 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; } } } /* Friday morning: everybody's there (the meetings' nextWeekend did the rest). */ 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, 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", "Monday: the clean-up" }; const char *game_festivalJumpText(const Game *game) { if (!game_festivalWeekend(game)) return "go to it ->"; float h = hours(game); static char text[64]; for (int k = 0; k < (int)(sizeof JUMPS / sizeof JUMPS[0]); k++) if (h < JUMPS[k]) { snprintf(text, sizeof text, "on to %s ->", JUMP_NAME[k]); return text; } return "over"; } void game_festivalJump(Game *game) { if (!game_festivalWeekend(game)) { /* (the Herberge before it: straight on to the festival weekend) */ if (game->meeting.kind || game->weekend <= 0) return; game->weekend = FESTIVAL_WEEKEND - 1; game->story_hours = game->weekend_from + 66.0f; /* (Sunday six: back to Berlin, the last Herberge) */ return; } float h = hours(game); for (int k = 0; k < (int)(sizeof JUMPS / sizeof JUMPS[0]); k++) { if (h >= JUMPS[k]) continue; float add = JUMPS[k] - h; game->story_hours += add; game->daycycle.seconds = fmodf(game->daycycle.seconds + add * 3600.0f, 24.0f * 3600.0f); return; } } 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)"; case FEST_STORM: return "The storm: stay inside till it's over"; default: return NULL; } } int game_festivalNotes(const Game *game, char lines[][128], int max) { const Festival *fe = &game->festival; const World *w = &game->world; int n = 0; if (!game_festivalWeekend(game) || n >= max) return 0; snprintf(lines[n++], 128, " "); if (n < max) snprintf(lines[n++], 128, "The festival:"); if (n < max) { int here = 0; for (int i = 0; i < w->count; i++) if (w->objects[i]->type == OBJ_HUMAN && w->objects[i]->guest && !w->objects[i]->gone_home) here++; snprintf(lines[n++], 128, "- Guests: %d came, %d here now", fe->came, here); } char play[128] = "- Playing now:"; size_t len = strlen(play); bool any = false; for (int k = 0; k < FEST_FLOORS; k++) if (w->fest.on & (1u << k)) { snprintf(play + len, sizeof play - len, "%s %s", any ? "," : "", FLOOR_NAME[k]); len = strlen(play); any = true; } if (!any) snprintf(play + len, sizeof play - len, " nothing"); if (n < max) snprintf(lines[n++], 128, "%s", play); for (int k = 0; k < FEST_FLOORS && n < max; k++) if (w->fest.good[k] <= 0.0f) snprintf(lines[n++], 128, "- %c%s doesn't play: its stage hasn't got its deck", FLOOR_NAME[k][0] - 32, FLOOR_NAME[k] + 1); 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; }