/* * 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 #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; }