Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/game/game_story.c raw
/*
* 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 <math.h>
#include <stdio.h>
#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;
}