Nimbin[12]?Games / Entropie / entropie/src/game/game_arrival.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

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.gz
entropie/src/game/game_arrival.c 22.3 KB · 498 lines raw
/*
 * The start: you, Volkram, Jana and Toni come in by car along the gravel road
 * and park in the yard (you at the wheel, the car drives itself: a short
 * scene), get out one after the other, the boot opens and the three start
 * unloading: crates of beer and water into the garages' kitchen, the camping things up to
 * the orchard where the tents will go. You help: left click at the boot.
 */

#include "game/game_internal.h"
#include "world/car.h"
#include "world/site.h"
#include "world/tent.h"

#define DRIVE_TOP     5.5f  /* m/s on the gravel road ... */
#define DRIVE_ACCEL   1.6f  /* ... speeding up ... */
#define DRIVE_BRAKE   1.4f  /* ... and slowing down to park (m/s^2) */
#define PATH_SAMPLES  ARRIVAL_SAMPLES

/* Catmull-Rom through the points (the ends doubled). */
static Vec3 along(const Vec3 *p, int n, float u) {
    float f = clampf(u, 0.0f, 1.0f) * (float)(n - 1);
    int i = (int)f;
    if (i >= n - 1) i = n - 2;
    float t = f - (float)i;
    Vec3 p0 = p[i > 0 ? i - 1 : 0], p1 = p[i], p2 = p[i + 1], p3 = p[i + 2 < n ? i + 2 : n - 1];
    float t2 = t * t, t3 = t2 * t;
    Vec3 r = vec3_zero();
    r = vec3_add(r, vec3_scale(p1, 2.0f));
    r = vec3_add(r, vec3_scale(vec3_sub(p2, p0), t));
    r = vec3_add(r, vec3_scale(vec3_add(vec3_sub(vec3_scale(p0, 2.0f), vec3_scale(p1, 5.0f)), vec3_sub(vec3_scale(p2, 4.0f), p3)), t2));
    r = vec3_add(r, vec3_scale(vec3_add(vec3_sub(vec3_scale(p1, 3.0f), p0), vec3_sub(p3, vec3_scale(p2, 3.0f))), t3));
    return vec3_scale(r, 0.5f);
}

static Object *person(Game *game, const char *name) {
    for (int i = 0; i < game->world.count; i++) {
        Object *o = game->world.objects[i];
        if (o->type == OBJ_HUMAN && !strcmp(o->name, name)) return o;
    }
    return NULL;
}

static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" };

static void chat(Game *game, float dt);
static void settled(Game *game);

/* While you're still to say hello and have a chat, and till Kurti has shown you where to camp: Kurti and his sons stay out. */
static void hostsStay(Game *game) {
    if (game->arrival.task != TASK_HELLO && game->arrival.task != TASK_CHAT && !(game->arrival.task == TASK_UNLOAD && game->arrival.guide < 2)) return;
    for (int i = 0; i < game->world.count; i++) {
        Object *o = game->world.objects[i];
        if (o->type == OBJ_HUMAN && o->lives_here) { o->ai.going_in = false; o->indoors = 0.0f; }
    }
}

static Object *fireplace(Game *game) {
    for (int i = 0; i < game->world.count; i++)
        if (game->world.objects[i]->type == OBJ_FIREPLACE) return game->world.objects[i];
    return NULL;
}

bool game_drivePath(Game *game) {
    Arrival *a = &game->arrival;
    if (a->length > 0.0f) return true;
    Vec3 pts[ARRIVAL_POINTS];
    int n = 0;
    Site_arrivalPath(pts, &n, &a->park_yaw);
    if (n < 2) return false;
    a->length = 0.0f;
    for (int k = 0; k < PATH_SAMPLES; k++) { /* (the curve, sampled; how far along each sample is) */
        a->path[k] = along(pts, n, (float)k / (float)(PATH_SAMPLES - 1));
        if (k > 0) a->length += vec3_length(vec3_sub(vec3(a->path[k].x, 0.0f, a->path[k].z), vec3(a->path[k - 1].x, 0.0f, a->path[k - 1].z)));
        a->dist[k] = a->length;
    }
    return a->length > 0.0f;
}

void game_arrivalStart(Game *game) {
    Arrival *a = &game->arrival;
    if (!game_drivePath(game)) return;
    Vec3 head = vec3_sub(a->path[1], a->path[0]);
    Object *car = Car_create(a->path[0], yaw_facing(vec3_normalize(vec3(head.x, 0.0f, head.z))));
    if (!car || !World_add(&game->world, car)) return;
    a->car = car;
    Object *friends[3];
    for (int k = 0; k < 3; k++) friends[k] = person(game, FRIENDS[k]);
    /* the boot, loaded last first: the camping things on top, the crates under them */
    Car_load(car, CARGO_MATE, NULL);
    Car_load(car, CARGO_SODA, NULL);
    Car_load(car, CARGO_WATER, NULL);
    Car_load(car, CARGO_WATER, NULL);
    Car_load(car, CARGO_BEER, NULL);
    Car_load(car, CARGO_BEER, NULL);
    Car_load(car, CARGO_TENT_BIG, friends[0]);
    Car_load(car, CARGO_TENT_SMALL, friends[2]);
    Car_load(car, CARGO_TENT_SMALL, game->player);
    Object *everybody[4] = { game->player, friends[0], friends[1], friends[2] };
    for (int k = 0; k < 4; k++) if (everybody[k]) Car_load(car, CARGO_MAT, everybody[k]);
    for (int k = 0; k < 4; k++) if (everybody[k]) Car_load(car, CARGO_SLEEPING_BAG, everybody[k]);
    /* Volkram drives (his car), Jana beside him; you and Toni in the back */
    Object *seats[4] = { friends[0], friends[1], game->player, friends[2] };
    for (int k = 0; k < 4; k++) if (seats[k]) Car_getIn(car, seats[k], k);
    Car_place(&game->world, car, a->path[0], car->rotation.y);
    a->stage = ARRIVAL_DRIVING;
    a->speed = 3.0f;
    a->s = 0.0f;
    a->time = 0.0f;
    game->third_person = false;
    float yaw = atan2f(head.z, head.x) * 180.0f / PI_F;
    Camera_init(&game->camera, Car_eye(car, 0), yaw, -4.0f, game->camera.fov);
}

/* Where the car is s meters along, and which way it heads. */
void game_driveAt(const Arrival *a, float s, Vec3 *pos, Vec3 *dir) {
    int k = 1;
    while (k < PATH_SAMPLES - 1 && a->dist[k] < s) k++;
    float span = fmaxf(a->dist[k] - a->dist[k - 1], 1e-4f), t = clampf((s - a->dist[k - 1]) / span, 0.0f, 1.0f);
    *pos = vec3_lerp(a->path[k - 1], a->path[k], t);
    Vec3 d = vec3_sub(a->path[k], a->path[k - 1]);
    *dir = vec3_normalize(vec3(d.x, 0.0f, d.z));
}

static void getOut(Game *game, const char *name, const char *line) {
    Object *o = name ? person(game, name) : game->player;
    if (!o || !o->riding) return;
    Car_getOut(o);
    Body_stretch(&o->body); /* (a long drive: a good stretch first) */
    if (line && o->type == OBJ_HUMAN) Ai_say(o, line);
}

/* Over at the fire: Kurti and his sons welcome you, one after the other. */
#define WELCOME_NEAR 4.5f
static void welcome(Game *game, float dt) {
    Arrival *a = &game->arrival;
    static const struct { const char *who, *line; } LINES[] = {
        { "Kurti",  "Welcome, welcome! Good to see you all - sit down, warm up." },
        { "Martin", "You brought your own beer? Smart. Ours is gone." },
        { "Jonas",  "Make yourselves at home. Dad will show you where the tents go." },
        { "Kurti",  "The trees and bushes up there stay, mind. The toilet's in the house. Sit down a moment!" },
    };
    const int n = (int)(sizeof LINES / sizeof LINES[0]);
    if (a->task != TASK_HELLO || a->welcome >= n || game->player->riding) return;
    Object *kurti = person(game, "Kurti"), *p = game->player;
    if (a->welcome == 0) {
        if (!kurti || kurti->indoors > 0.0f || hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) > WELCOME_NEAR) return;
        a->welcome_t = 0.0f;
    }
    if ((a->welcome_t -= dt) > 0.0f) return;
    Object *o = person(game, LINES[a->welcome].who);
    if (o && o->indoors <= 0.0f) {
        Ai_say(o, LINES[a->welcome].line);
        o->body.look_dir = vec3_normalize(vec3_sub(vec3_add(p->position, vec3(0.0f, 1.6f, 0.0f)), vec3_add(o->position, vec3(0.0f, 1.4f, 0.0f))));
    }
    a->welcome++;
    a->welcome_t = 3.2f;
    if (a->welcome >= n) { a->task = TASK_CHAT; a->chat = 0; a->chat_wait = 0.0f; a->welcome_t = 0.0f; }
}

/* ---------- Kurti shows you where to camp ---------- */

#define GUIDE_NEAR  4.0f  /* m: by Kurti with your things, he calls everybody over */
#define GATHER_NEAR 5.5f  /* m from where he stands: there */
#define GATHER_WAIT 45.0f /* s he waits there for the others (then he starts anyway) */
#define GUIDE_LINE  3.8f  /* s between his lines */
#define MARK_ABOVE  0.75f /* m: the marker over a head */

static bool carrying(const Object *p) {
    return p->hands.state[HAND_LEFT] == HAND_HOLDING || p->hands.state[HAND_RIGHT] == HAND_HOLDING;
}

/* Nothing of yours left in the car. */
static bool yourThingsOut(const Game *game) {
    const Object *car = game->arrival.car;
    if (!car || !car->load) return true;
    for (int k = 0; k < car->load->n; k++) if (car->load->owner[k] == game->player) return false;
    return true;
}

static void sendTo(Object *o, Vec3 to, Object *with) {
    if (Ai_isHelping(o)) Ai_stopHelping(o);
    o->ai.mode = AI_IDLE;
    o->ai.errand = true;
    o->ai.errand_there = false;
    o->ai.errand_to = to;
    o->ai.errand_with = with;
}

static float flatGap(const Object *a, Vec3 b) { return hypotf(a->position.x - b.x, a->position.z - b.z); }

/* Everybody to the foot of the path up behind your garages: Kurti shows the way up, says the rest. */
static void guide(Game *game, float dt) {
    static const char *const SHOW[] = {
        "All here? Up this path - the meadow behind the garages is yours for the tents.",
        "Pitch them where you like up there. The trees and bushes stay, mind.",
        "Toilet's in the house, water in the kitchen. Make yourselves at home!",
    };
    const int lines = (int)(sizeof SHOW / sizeof SHOW[0]);
    Arrival *a = &game->arrival;
    if (a->task != TASK_UNLOAD || a->guide >= 2) return;
    Object *kurti = person(game, "Kurti"), *p = game->player;
    if (!kurti) { a->guide = 2; return; }
    Vec3 foot = Site_campPath(), up = vec3_scale(Car_campDoor(), -1.0f), side = vec3(-up.z, 0.0f, up.x);
    if (a->guide == 0) {
        if (!carrying(p) && !yourThingsOut(game)) return;
        if (flatGap(kurti, p->position) > GUIDE_NEAR || p->riding) return;
        Ai_say(kurti, "Got your things? Everybody, come along - I'll show you where you camp!");
        a->guide = 1;
        a->guide_line = 0;
        a->guide_t = 0.0f;
        say(game, "Follow Kurti - he shows you all where you can camp");
    }
    /* (off they go - again after loading): Kurti to the foot of the path, the others round him */
    if (!kurti->ai.errand) sendTo(kurti, foot, p);
    for (int k = 0; k < 3; k++) {
        Object *o = person(game, FRIENDS[k]);
        if (!o || o->ai.errand || o->riding) continue;
        sendTo(o, vec3_add(foot, vec3_add(vec3_scale(up, -2.2f), vec3_scale(side, 1.4f * (float)(k - 1)))), NULL);
    }
    if (!kurti->ai.errand_there) return;
    kurti->ai.errand_with = NULL; /* (there: no more looking back for you - up the path he looks, while he talks) */
    float yaw = atan2f(up.z, up.x) + PI_F / 2.0f, turn = remainderf(yaw - kurti->rotation.y, 2.0f * PI_F);
    kurti->rotation.y += clampf(turn, -3.0f * dt, 3.0f * dt);
    bool all = flatGap(p, foot) < GATHER_NEAR;
    for (int k = 0; k < 3 && all; k++) {
        Object *o = person(game, FRIENDS[k]);
        if (o && !Body_isDown(&o->body) && flatGap(o, foot) > GATHER_NEAR) all = false;
    }
    a->guide_t += dt;
    if (a->guide_line == 0 && !all && a->guide_t < GATHER_WAIT) return; /* (waiting for the others) */
    if (a->guide_line > 0 && a->guide_t < GUIDE_LINE) return;
    if (a->guide_line < lines) {
        Ai_say(kurti, SHOW[a->guide_line]);
        if (a->guide_line == 0) Body_talkGesture(&kurti->body, TALK_POINT);
        a->guide_line++;
        a->guide_t = 0.0f;
        return;
    }
    /* said: on with the day */
    kurti->ai.errand = false;
    kurti->ai.errand_with = NULL;
    for (int k = 0; k < 3; k++) {
        Object *o = person(game, FRIENDS[k]);
        if (o) o->ai.errand = false;
    }
    a->guide = 2;
    say(game, "Up the path behind the garages: put your tent up on the meadow (hold left click on it)");
}

/* Your tent: standing, or where it's to go up. */
static const Object *yourTent(const Game *game, bool *up) {
    *up = false;
    for (int i = 0; i < game->world.count; i++) {
        const Object *o = game->world.objects[i];
        if (o->type == OBJ_TENT && o->owner == game->player && !o->broken) { *up = o->tent_stage >= TENT_UP; return o; }
    }
    return NULL;
}

bool Game_questMarker(const Game *game, Vec3 *at) {
    const Arrival *a = &game->arrival;
    const Object *p = game->player, *kurti = NULL;
    if (a->stage == ARRIVAL_DRIVING || p->riding) return false;
    if (game->festival.clean == CLEAN_TO_CAR && a->car) { *at = vec3_add(Car_door(a->car, 2), vec3(0.0f, 2.0f, 0.0f)); return true; } /* (home: the car) */
    for (int i = 0; i < game->world.count && !kurti; i++)
        if (game->world.objects[i]->type == OBJ_HUMAN && !strcmp(game->world.objects[i]->name, "Kurti")) kurti = game->world.objects[i];
    bool to_kurti = false;
    switch (a->task) {
    case TASK_HELLO: case TASK_CHAT: to_kurti = true; break;
    case TASK_UNLOAD:
        if (a->guide == 1 || (a->guide == 0 && (carrying(p) || yourThingsOut(game)))) to_kurti = true;
        else if (a->guide == 0 && a->car) { /* your things are still in the car */
            *at = vec3_add(Car_bootSpot(a->car), vec3(0.0f, 2.3f, 0.0f));
            return true;
        } else if (a->guide == 2) { /* your tent, till it's up */
            bool up;
            const Object *t = yourTent(game, &up);
            if (up) return false;
            Vec3 spot = t ? t->position : Site_campSpot(0);
            spot.y = World_groundHeight(&game->world, spot.x, spot.z, spot.y + 3.0f);
            *at = vec3_add(spot, vec3(0.0f, 1.6f, 0.0f));
            return true;
        }
        break;
    default: return false;
    }
    if (!to_kurti || !kurti || kurti->indoors > 0.0f) return false;
    *at = vec3_add(kurti->position, vec3(0.0f, kurti->look.height + MARK_ABOVE, 0.0f));
    return true;
}

void game_arrival(Game *game, float dt) {
    Arrival *a = &game->arrival;
    Object *car = a->car;
    hostsStay(game);
    guide(game, dt);
    welcome(game, dt);
    chat(game, dt);
    settled(game);
    if (a->stage == ARRIVAL_NONE || a->stage == ARRIVAL_DONE || !car) return;
    a->time += dt;
    if (a->stage == ARRIVAL_DRIVING) {
        float left = a->length - a->s;
        float brake = sqrtf(fmaxf(0.0f, 2.0f * DRIVE_BRAKE * left));
        a->speed = fminf(fminf(a->speed + DRIVE_ACCEL * dt, DRIVE_TOP), brake + 0.15f);
        a->s = fminf(a->length, a->s + a->speed * dt);
        Vec3 pos, dir;
        game_driveAt(a, a->s, &pos, &dir);
        float yaw = yaw_facing(dir);
        if (a->length - a->s < 4.0f) /* (the last bit: straight in to its spot) */
            yaw = lerpf(yaw, a->park_yaw, smoothstepf(4.0f, 0.0f, a->length - a->s));
        Car_place(&game->world, car, pos, yaw);
        if (a->s >= a->length - 0.01f) { a->stage = ARRIVAL_PARKED; a->time = 0.0f; }
        return;
    }
    /* parked: out one after the other, the boot up, unloading */
    static const struct { float at; int who; const char *line; } STEPS[] = {
        { 0.6f, -1, NULL },
        { 1.2f, 0, "Made it! Right - beer first." },
        { 1.9f, 1, "Oh, it's lovely here." },
        { 2.6f, 2, "My back... that seat is a crime." },
    };
    for (int k = 0; k < 4; k++)
        if (a->step == k && a->time >= STEPS[k].at) { getOut(game, STEPS[k].who < 0 ? NULL : FRIENDS[STEPS[k].who], STEPS[k].line); a->step++; }
    if (a->step == 1 && !a->greeted) {
        Object *kurti = person(game, "Kurti");
        if (kurti) Ai_say(kurti, "Ha - there they are!");
        say(game, "You're here. Kurti and his sons are at the fire - go and say hello");
        game->third_person = true;
        a->greeted = true;
    }
    if (a->step == 4 && a->time >= 7.0f) { /* first: a stretch, then over to the fire together, hello to Kurti and his sons */
        Object *fire = fireplace(game);
        for (int k = 0; k < 3; k++) {
            Object *o = person(game, FRIENDS[k]);
            if (!o || !fire) continue;
            o->ai.mode = AI_IDLE;
            o->ai.greet = true;
            o->ai.side = (float)(k - 1);
        }
        a->task = TASK_HELLO;
        a->stage = ARRIVAL_DONE;
    }
}

/* Hello said and a chat had: the car unloaded, the tents put up. */
static void startUnloading(Game *game) {
    Arrival *a = &game->arrival;
    if (a->car) Car_openBoot(a->car, true);
    for (int k = 0; k < 3; k++) {
        Object *o = person(game, FRIENDS[k]);
        if (!o) continue;
        o->ai.greet = false;
        o->ai.mode = AI_IDLE;
        o->ai.unload = true;
        o->ai.unload_car = a->car;
        o->ai.unload_time = 0.0f;
        o->ai.side = (float)(k - 1);
    }
    a->task = TASK_UNLOAD;
}

/* Sitting with them (or a while after the hello): a little chat with Kurti, then the work. */
#define CHAT_WAIT 25.0f /* s after the hello: the chat starts anyway */
static void chat(Game *game, float dt) {
    Arrival *a = &game->arrival;
    if (a->task != TASK_CHAT) return;
    static const struct { const char *who, *line; } LINES[] = {
        { "Kurti",   "So. Four of you this year - and a whole festival to build." },
        { "Kurti",   "Build what you like, just keep the fire in its ring. And mind the old trees." },
        { "Volkram", "Deal. Right - let's get the car empty and the tents up before it gets dark." },
        { "Kurti",   "Go and get your things from the car - then come to me, I'll show you where you can camp." },
    };
    const int n = (int)(sizeof LINES / sizeof LINES[0]);
    Object *p = game->player, *kurti = person(game, "Kurti");
    a->chat_wait += dt;
    if (a->chat == 0) {
        bool sat = p->sitting && kurti && hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) < 5.0f;
        for (int k = 0; k < 3 && sat; k++) { /* (and the others: sat down too, or standing with them - the last one isn't left out) */
            const Object *o = person(game, FRIENDS[k]);
            if (o && !o->sitting && !o->ai.greet_stand) sat = false;
        }
        if (!sat && a->chat_wait < CHAT_WAIT) return;
        a->welcome_t = 0.0f;
    }
    if ((a->welcome_t -= dt) > 0.0f) return;
    if (a->chat >= n) { startUnloading(game); return; }
    Object *o = person(game, LINES[a->chat].who);
    if (o) Ai_say(o, LINES[a->chat].line);
    a->chat++;
    a->welcome_t = 3.6f;
}

/* What to do now (the hint line when nothing else is said there), or NULL. */
/* The car empty and all the tents up: settled in. */
static void settled(Game *game) {
    Arrival *a = &game->arrival;
    if (a->task != TASK_UNLOAD) return;
    bool done = a->car && a->car->load && a->car->load->n == 0;
    for (int i = 0; i < game->world.count && done; i++) {
        const Object *o = game->world.objects[i];
        if ((o->type == OBJ_TENT && o->tent_stage < TENT_UP) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken)) done = false;
    }
    if (done) a->task = TASK_NONE;
}

const char *game_task(Game *game) {
    Arrival *a = &game->arrival;
    const char *meeting = game_meetingTask(game);
    if (meeting) return meeting;
    const char *fest = game_festivalTask(game);
    if (fest) return fest;
    switch (a->task) {
    case TASK_HELLO:  return "To do: say hello to Kurti and his sons at the fire";
    case TASK_CHAT:   return "To do: sit down with them (C) and have a chat";
    case TASK_UNLOAD:
        if (a->guide == 0) return "To do: get your things from the car (left click at the boot), then over to Kurti - he shows you where to camp";
        if (a->guide == 1) return "To do: follow Kurti to the end of the garages - he shows you all where to camp";
        return "To do: put your tent up (hold left click on it), help the others empty the car";
    default:          return NULL;
    }
}

/* Dev: the start skipped - parked, everybody out, hello and chat said, the car unloaded and the tents up. */
void game_skipArrival(Game *game) {
    Arrival *a = &game->arrival;
    if (game->weekend == 0) game_meetingSkipStart(game); /* (the meetings in Berlin first) */
    if (!a->car) return;
    Car_place(&game->world, a->car, a->path[ARRIVAL_SAMPLES - 1], a->park_yaw);
    for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->riding) Car_getOut(game->world.objects[i]);
    a->stage = ARRIVAL_DONE;
    a->step = 4;
    a->greeted = true;
    a->welcome = 99;
    a->chat = 99;
    game->third_person = true;
    Car_openBoot(a->car, true);
    Object *drop[CAR_LOAD_MAX];
    int n = 0;
    while (a->car->load && a->car->load->n > 0 && n < CAR_LOAD_MAX) {
        Object *it = Car_takeOut(&game->world, a->car);
        if (!it) break;
        drop[n++] = it;
    }
    for (int k = 0; k < n; k++) { /* (each where it goes; the tents up there) */
        Object *it = drop[k];
        Vec3 at = Car_dropSpot(&game->world, it);
        it->position = vec3(at.x, World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f) + 0.3f, at.z);
        Object_syncColliders(it);
        if (it->type == OBJ_GEAR && it->variant >= GEAR_TENT_SMALL) {
            Object *tent = Tent_unpack(&game->world, it, Car_campDoor());
            while (tent && tent->tent_stage < TENT_UP) Tent_work(tent, game->player, 1.0f);
        }
    }
    for (int k = 0; k < 3; k++) {
        Object *o = person(game, FRIENDS[k]);
        if (o) { o->ai.greet = false; o->ai.unload = false; o->sitting = false; }
    }
    a->task = TASK_NONE;
    a->guide = 2;
    say(game, "Dev: the start skipped");
}

bool game_arriving(const Game *game) {
    return game->arrival.stage == ARRIVAL_DRIVING || (game->arrival.stage == ARRIVAL_PARKED && game->player->riding);
}

/* Left click at the open boot: the next thing out, into your hands. */
bool game_unloadCar(Game *game) {
    Object *car = game->arrival.car, *p = game->player;
    if (!car || !car->boot_open) return false;
    Vec3 boot = Car_bootSpot(car);
    if (hypotf(boot.x - p->position.x, boot.z - p->position.z) > 1.1f) return false;
    const char *next = Car_nextName(car);
    if (!next) { say(game, "The boot is empty"); return true; }
    bool heavy = Car_nextHeavy(car);
    bool both = p->hands.state[HAND_LEFT] == HAND_FREE && p->hands.state[HAND_RIGHT] == HAND_FREE;
    int hand = Hands_freeHand(p);
    if (heavy ? !both : hand < 0) {
        snprintf(game->info, sizeof(game->info), "Next is %s - %s", next, heavy ? "both hands free for it" : "a hand free for it");
        game->info_about = NULL;
        say(game, game->info);
        return true;
    }
    Object *item = Car_takeOut(&game->world, car);
    if (!item) return true;
    item->position = vec3_add(p->position, vec3_add(vec3_scale(Object_facing(p), 0.45f), vec3(0.0f, 0.5f, 0.0f))); /* (right at your hands) */
    Object_syncColliders(item);
    Hands_pickUpObject(p, hand < 0 ? HAND_RIGHT : hand, item);
    snprintf(game->info, sizeof(game->info), "You take out %s - %s", next,
             item->type == OBJ_CRATE ? "the crates go into the kitchen in your garages"
             : item->type == OBJ_SUPPLY ? "the food goes into the kitchen in your garages"
             : item->type == OBJ_MATERIAL ? "the material goes in front of the car room of your garages (the store)"
                                       : "the camping things go up to the meadow behind your garages");
    game->info_about = NULL;
    say(game, game->info);
    return true;
}