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_cleanup.c raw
/*
* 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;
}