/* * The story, as far as it goes: the hours on the ground counted; the crew * (world/cast.c) turns up when they're due - in from the gravel road, over to * the fire to say hello, then on with the day. */ #include #include #include "game/game_internal.h" #include "world/cast.h" #include "world/site.h" #include "world/stage.h" #include "world/field.h" #define STORY_CHECK 1.0f /* real s between looks at who's due */ #define WALK_IN_AT 4 /* where they turn up: this point of the way in by car (the gravel road by the main building) */ #define WALK_IN_GAP 1.2f /* m between people turning up together */ #define HELLO_TIME 90.0f /* s at the fire saying hello before they get on with things */ #define CREW_PAGES_SHOWN 3 /* the overview: who's who, now, the projects (game_dev.c CREW_PAGES the same) */ /* On the ground (not waiting in Berlin). */ static bool here(const Game *game, int id) { for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->is_character && game->world.objects[i]->cast_id == id) return !Game_inBerlin(game->world.objects[i]); return false; } static Object *inBerlin(const Game *game, int id) { for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o->is_character && o->cast_id == id && Game_inBerlin(o)) return o; } return NULL; } /* The regulatory office: warned of the day before and on the day; then it comes and looks at where you build. */ #define FINE_LEFT 40 /* EUR a snail still out there */ #define FINE_DEAD 80 /* ... one mown over */ #define FINE_MOST 600 /* ... at most */ static void inspection(Game *game) { World *w = &game->world; if (w->inspected) return; float left = FIELD_INSPECTION - game->story_hours; Object *doro = NULL; for (int i = 0; i < w->count && !doro; i++) if (w->objects[i]->is_character && w->objects[i]->leads == PROJECT_SNAILS) doro = w->objects[i]; if (left <= 24.0f && !(game->snail_warned & 1)) { game->snail_warned |= 1; if (doro) Ai_say(doro, "The regulatory office comes tomorrow! Every snail out of where we build by then."); say(game, "Tomorrow the regulatory office comes: the vineyard snails have to be out of the building places"); } if (left <= 3.0f && !(game->snail_warned & 2)) { game->snail_warned |= 2; if (doro) Ai_say(doro, "Three hours till the office is here - how many snails are still out there?"); snprintf(game->story_line, sizeof game->story_line, "The regulatory office comes in 3 hours - %d snail%s still out there", Field_snailsLeft(w), Field_snailsLeft(w) == 1 ? "" : "s"); say(game, game->story_line); } if (left > 0.0f) return; w->inspected = true; int out = Field_snailsLeft(w), dead = w->snails_killed, fine = FINE_LEFT * out + FINE_DEAD * dead; if (fine > FINE_MOST) fine = FINE_MOST; if (fine == 0) { snprintf(game->story_line, sizeof game->story_line, "The man from the regulatory office walks the field: not a snail where you build. All fine - well done!"); for (int i = 0; i < w->count; i++) { Object *o = w->objects[i]; if (o->is_character && (o->leads == PROJECT_SNAILS || o->helps == PROJECT_SNAILS)) o->commitment = fminf(1.0f, o->commitment + 0.1f); } if (doro) Ai_say(doro, "He's happy! Not a single snail where we build. Thanks, everybody!"); } else { w->cash -= (float)fine; snprintf(game->story_line, sizeof game->story_line, "The regulatory office: %d snail%s still where you build, %d dead - a fine of %d EUR from the cash box", out, out == 1 ? "" : "s", dead, fine); for (int i = 0; i < w->count; i++) { Object *o = w->objects[i]; if (!o->is_character || o->cast_id < 0) continue; o->commitment = clampf(o->commitment - (o->leads == PROJECT_SNAILS ? 0.12f : 0.03f), 0.0f, 1.0f); } if (doro) Ai_say(doro, dead ? "A fine - and dead snails. That's on all of us." : "A fine. We should have been quicker with the snails."); } say(game, game->story_line); } void game_story(Game *game, float game_dt) { if (game->arrival.stage == ARRIVAL_DRIVING) return; /* (the clock starts once you're there) */ game->story_hours += game_dt / 3600.0f; inspection(game); if ((game->story_check -= game->dt) > 0.0f) return; game->story_check = STORY_CHECK; Vec3 path[16]; int n = 0; float yaw = 0.0f; Site_arrivalPath(path, &n, &yaw); if (n <= WALK_IN_AT) return; char names[96] = ""; int come = 0; for (int id = 1; id < Cast_count(); id++) { const CastMember *c = Cast_get(id); if (c->arrives <= 0.0f || game->story_hours < c->arrives || here(game, id)) continue; Vec3 at = path[WALK_IN_AT]; at.x += WALK_IN_GAP * (float)come; at.y = World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f); Object *o = inBerlin(game, id); /* (waiting in Berlin since the Herberge: over they come) */ if (o) { o->sitting = false; o->seat = NULL; o->ai.meeting = false; o->position = at; o->body.replant = true; o->velocity = o->movement = vec3_zero(); Object_syncColliders(o); } else { o = Cast_create(id, at); if (!o) continue; if (!World_add(&game->world, o)) { Object_destroy(o); continue; } } o->ai.mode = AI_IDLE; o->ai.greet = true; /* (over to the fire, hello) */ o->ai.greet_left = HELLO_TIME; o->ai.side = (float)(come % 3 - 1); if (c->leads == PROJECT_SNAILS) Ai_say(o, "Hi all! Mind the vineyard snails - they're protected, and the office comes soon!"); else if (c->leads == PROJECT_BIG_TREE) Ai_say(o, "Servus! Enrico, Go-Darmstadt. Where's this big tree you told me about?"); else Ai_say(o, come == 0 ? "Hello everybody - we're here!" : "Hi! Where's the fire?"); size_t len = 0; while (names[len]) len++; snprintf(names + len, sizeof names - len, "%s%s", come ? " and " : "", c->name); come++; } if (come) { snprintf(game->story_line, sizeof game->story_line, "%s %s turned up", names, come > 1 ? "have" : "has"); say(game, game->story_line); } } /* ---------- The crew overview (F2) ---------- */ static Object *personOf(const Game *game, int id) { for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->is_character && game->world.objects[i]->cast_id == id) return game->world.objects[i]; return NULL; } /* What they're up to, said plainly. */ static void doing(const Object *o, char *out, size_t n) { const Ai *ai = &o->ai; static const char *const JOBS[ORDER_COUNT] = { "-", "firewood", "trees", "the trash", "the fire" }; if (o->type == OBJ_PLAYER) { snprintf(out, n, "(you)"); return; } if (Game_inBerlin(o)) { snprintf(out, n, ai->meeting && o->sitting ? "in Berlin (at the table)" : "in Berlin"); return; } if (Body_isDown(&o->body)) snprintf(out, n, o->body.asleep ? "asleep" : "down on the ground"); else if (o->in_tent) snprintf(out, n, "in the tent"); else if (o->riding) snprintf(out, n, "in the car"); else if (o->indoors > 0.0f) snprintf(out, n, "in the house"); else if (ai->greet) snprintf(out, n, "saying hello at the fire"); else if (ai->share) snprintf(out, n, "under %s's umbrella", ai->share->name); else if (ai->sheltering) snprintf(out, n, o->sitting ? "sitting out the rain" : "out of the rain"); else if (ai->mode == AI_WORK) snprintf(out, n, "working: %s", JOBS[ai->order >= 0 && ai->order < ORDER_COUNT ? ai->order : 0]); else if (ai->mode == AI_HELP) snprintf(out, n, "helping %s", ai->leader ? ai->leader->name : "somebody"); else if (ai->mode == AI_FOLLOW) snprintf(out, n, "with you"); else if (ai->mode == AI_PEE) snprintf(out, n, "off for a pee"); else if (ai->mode == AI_DRINK || ai->mode == AI_SUPPLY) snprintf(out, n, "eating or drinking"); else if (ai->mode == AI_TABLE) snprintf(out, n, "at a table"); else if (ai->mode == AI_WAKE) snprintf(out, n, "waking somebody"); else if (ai->mode == AI_APPROACH) snprintf(out, n, "coming over to you"); else if (ai->mode == AI_FETCH) snprintf(out, n, "fetching something"); else if (ai->mode == AI_WANDER) snprintf(out, n, "strolling about"); else snprintf(out, n, o->sitting ? "sitting" : "standing about"); } int Game_crewLines(const Game *game, char lines[][128], int max, const char **title) { static char head[128]; int n = 0, page = ((game->crew_page % CREW_PAGES_SHOWN) + CREW_PAGES_SHOWN) % CREW_PAGES_SHOWN; int day = 1 + (int)(game->story_hours / 24.0f); if (page == 0) { snprintf(head, sizeof head, "Crew - who's who (day %d on the ground) Right: what they do now", day); for (int id = 0; id < Cast_count() && n < max; id++) { const CastMember *c = Cast_get(id); snprintf(lines[n++], 128, "%s: %s", c->name, c->role); } } else if (page == 1) { snprintf(head, sizeof head, "Crew - now (%.1f h on the ground) into it / annoyed / energy", (double)game->story_hours); for (int id = 0; id < Cast_count() && n < max; id++) { const CastMember *c = Cast_get(id); const Object *o = personOf(game, id); if (!o) { snprintf(lines[n++], 128, "%s: not here yet (turns up in %.0f h)", c->name, (double)fmaxf(0.0f, c->arrives - game->story_hours)); continue; } char what[64]; doing(o, what, sizeof what); snprintf(lines[n++], 128, "%s: %s - %.0f%% / %.0f%% / %.0f%%", c->name, what, (double)(o->commitment * 100.0f), (double)(o->ai.annoyed * 100.0f), (double)(o->needs.stamina * 100.0f)); } } else { snprintf(head, sizeof head, "Projects - who leads, who helps cash box %d EUR%s (Left: back)", (int)game->world.cash, game->errand.stage != ERRAND_NONE ? ", shopping run on" : ""); for (int p = PROJECT_ORGA; p < PROJECT_COUNT && n < max; p++) { char lead[48] = "nobody yet", helpers[80] = ""; size_t len = 0; for (int id = 0; id < Cast_count(); id++) { const CastMember *c = Cast_get(id); const Object *o = personOf(game, id); int leads = o ? o->leads : (int)c->leads, helps = o ? o->helps : (int)c->helps; if (leads == p) snprintf(lead, sizeof lead, "%s%s", c->name, o ? "" : " (not here yet)"); else if (helps == p && len < sizeof helpers - 12) { snprintf(helpers + len, sizeof helpers - len, "%s%s", len ? ", " : "", c->name); while (helpers[len]) len++; } } char built[112] = ""; const World *w = &game->world; if (p == PROJECT_MOWING) snprintf(built, sizeof built, " [%d%% mown]", (int)(Field_done(w) * 100.0f)); if (p == PROJECT_SNAILS) { if (w->inspected) snprintf(built, sizeof built, " [safe %d, dead %d, office: been]", w->snails_rescued, w->snails_killed); else snprintf(built, sizeof built, " [safe %d, out %d, dead %d, office in %.0f h]", w->snails_rescued, Field_snailsLeft(w), w->snails_killed, (double)fmaxf(0.0f, FIELD_INSPECTION - game->story_hours)); } if (p == PROJECT_WOOD_ART) snprintf(built, sizeof built, " [%d figure%s; %s]", Field_artPieces(w), Field_artPieces(w) == 1 ? "" : "s", Field_artLog(w) ? "a log there" : "no log"); if (p == PROJECT_BIG_TREE) snprintf(built, sizeof built, " [%s]", game_bigTreeStatus(game)); const Object *s = Stage_of(game->world.objects, game->world.count, (Project)p); if (s && s->build_stage >= BUILD_DONE) snprintf(built, sizeof built, " [built]"); else if (s) { char what[64]; bool needs = Stage_missing(&game->world, s, what, sizeof what) > 0; snprintf(built, sizeof built, " [%s %d%%%s%s]", Stage_stepName(s->build_stage), (int)(Stage_part(s) * 100.0f), needs ? ", needs " : "", needs ? what : ""); } snprintf(lines[n++], 128, "%s%s: %s%s%s", Cast_projectName((Project)p), built, lead, len ? " helping: " : "", helpers); } } *title = head; return n; }