Nimbin[12]?Games / Entropie / entropie/src/game/game_shop.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_shop.c 8.7 KB · 204 lines raw
/*
 * The shopping run: asked in a talk (talk.c NODE_SHOP), the one with the car
 * keys walks over to the car, backs it out of the yard, turns and drives off
 * along the way in; an hour later (game time) the car comes back the same way
 * with the boot full - boards, nails, screws, paint, food and drink - parks
 * where it stood and the boot opens: the driver and whoever is idle nearby
 * unload it (ai.c unloadCar; material in front of the car room, food into the
 * kitchen: car.c Car_dropSpot). The money comes out of the cash box as they
 * leave.
 */

#include <math.h>

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

#define SHOP_AWAY     3600.0f /* game s at the shops (there and back included) */
#define TO_CAR_MAX    120.0f  /* s walking to the car: then they're in it anyway (stuck somewhere) */
#define BACK_OUT      10.0f   /* m backed out of the parking spot ... */
#define BACK_SPEED    1.6f    /* ... this fast ... */
#define TURN_TIME     3.0f    /* ... turned round in this long (s) ... */
#define DRIVE_TOP     6.0f    /* ... then off (m/s) */
#define DRIVE_ACCEL   1.6f
#define DRIVE_BRAKE   1.4f
#define HIDDEN_DEPTH  300.0f  /* m under the ground while it's away (out of sight, out of the way) */
#define HELPERS       2       /* idle people near who come and unload too */
#define HELPER_NEAR   45.0f

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

static int total(const int list[SHOP_ITEMS]) {
    int sum = 0;
    for (int k = 0; k < SHOP_ITEMS; k++) sum += list[k] * SHOP_PRICE[k];
    return sum;
}

void game_shopStart(Game *game, Object *driver, const int list[SHOP_ITEMS]) {
    Errand *e = &game->errand;
    Object *car = theCar(game);
    if (!car || !driver || e->stage != ERRAND_NONE || !game_drivePath(game)) return;
    memset(e, 0, sizeof *e);
    for (int k = 0; k < SHOP_ITEMS; k++) e->list[k] = list[k];
    e->driver = driver;
    e->stage = ERRAND_TO_CAR;
    if (Ai_isHelping(driver)) Ai_stopHelping(driver);
    driver->ai.mode = AI_IDLE;
    for (int h = 0; h < 2; h++) if (driver->hands.item[h] && driver->hands.state[h] == HAND_HOLDING) Hands_drop(driver, h); /* (hands free to drive) */
    driver->ai.errand = true;
    driver->ai.errand_with = NULL;
    driver->ai.errand_there = false;
    driver->ai.errand_to = Car_door(car, 0);
    game->world.shopping = true;
}

/* Out of sight while it's away: down under the ground (and the driver in it). */
static void hide(Game *game, Object *car) {
    Car_place(&game->world, car, game->arrival.path[0], car->rotation.y);
    car->position.y -= HIDDEN_DEPTH;
    Object_syncColliders(car);
    for (int i = 0; i < game->world.count; i++) {
        Object *o = game->world.objects[i];
        if (o->riding != car) continue;
        o->position.y -= HIDDEN_DEPTH;
        o->sit_at = o->position;
        Object_syncColliders(o);
    }
}

/* The boot full of what was on the list. */
static void loadUp(Object *car, const int list[SHOP_ITEMS]) {
    for (int k = 0; k < list[SHOP_FOOD]; k++) { /* (food and drink at the bottom, the material on top) */
        Car_load(car, CARGO_WATER, NULL);
        Car_load(car, CARGO_BEER, NULL);
        Car_load(car, CARGO_SAUSAGES, NULL);
        Car_load(car, CARGO_FOOD, NULL);
    }
    for (int k = 0; k < list[SHOP_PAINT]; k++) Car_load(car, CARGO_PAINT, NULL);
    for (int k = 0; k < list[SHOP_FIXINGS]; k++) { Car_load(car, CARGO_SCREWS, NULL); Car_load(car, CARGO_NAILS, NULL); }
    for (int k = 0; k < list[SHOP_BOARDS]; k++) Car_load(car, CARGO_BOARDS, NULL);
}

/* Parked again: out, the boot up, unloading (the driver and a couple of idle people near). */
static void arrived(Game *game, Object *car) {
    Errand *e = &game->errand;
    Object *d = e->driver;
    Car_place(&game->world, car, game->arrival.path[ARRIVAL_SAMPLES - 1], game->arrival.park_yaw);
    if (d && d->riding) Car_getOut(d);
    Car_openBoot(car, true);
    int n = 0;
    for (int i = 0; i < game->world.count; i++) {
        Object *o = game->world.objects[i];
        bool idle = o->ai.mode == AI_IDLE || o->ai.mode == AI_WANDER;
        if (o->type != OBJ_HUMAN || o->riding || o->lives_here || Body_isDown(&o->body) || o->in_tent || o->indoors > 0.0f) continue;
        if (o != d && (n >= HELPERS || !idle || o->ai.on_project || hypotf(o->position.x - car->position.x, o->position.z - car->position.z) > HELPER_NEAR)) continue;
        o->ai.unload = true;
        o->ai.unload_car = car;
        o->ai.unload_time = 0.0f;
        o->ai.side = (float)(n % 3 - 1);
        if (o != d) n++;
    }
    if (d) Ai_say(d, "We're back! Come on, help me unload - the boot's full.");
    static char line[96];
    snprintf(line, sizeof line, "%s is back from the hardware store - help unload the car", d ? d->name : "The car");
    say(game, line);
    game->world.shopping = false;
    memset(e, 0, sizeof *e);
}

void game_shop(Game *game, float dt) {
    Errand *e = &game->errand;
    if (e->stage == ERRAND_NONE) return;
    Object *car = theCar(game), *d = e->driver;
    Arrival *a = &game->arrival;
    if (!car || !game_drivePath(game)) { memset(e, 0, sizeof *e); game->world.shopping = false; return; }
    e->time += e->stage == ERRAND_AWAY ? game->world.game_dt : dt;
    Vec3 pos, dir;
    static char line[96];
    switch (e->stage) {
    case ERRAND_TO_CAR: /* walking over (ai_project.c ai_errand); then in, the money out of the box, off */
        if (!d || Body_isDown(&d->body)) {
            if (d) d->ai.errand = false;
            memset(e, 0, sizeof *e);
            game->world.shopping = false;
            return;
        }
        if (!d->ai.errand_there && e->time < TO_CAR_MAX) return;
        d->ai.errand = d->ai.errand_there = false;
        Car_openBoot(car, false);
        Car_getIn(car, d, 0);
        game->world.cash -= (float)total(e->list);
        e->stage = ERRAND_LEAVING;
        e->time = 0.0f;
        e->s = a->length;
        e->speed = 0.0f;
        snprintf(line, sizeof line, "%s drives off to the hardware store (%d EUR from the cash box)", d->name, total(e->list));
        say(game, line);
        break;
    case ERRAND_LEAVING: {
        float back_to = a->length - BACK_OUT;
        if (e->s > back_to) { /* backing out, still nose to the barn */
            e->s = fmaxf(back_to, e->s - BACK_SPEED * dt);
            game_driveAt(a, e->s, &pos, &dir);
            Car_place(&game->world, car, pos, yaw_facing(dir));
            e->time = 0.0f;
            return;
        }
        game_driveAt(a, e->s, &pos, &dir);
        if (e->time < TURN_TIME) { /* turning round where it is */
            float t = smoothstepf(0.0f, TURN_TIME, e->time);
            Car_place(&game->world, car, pos, yaw_facing(dir) + PI_F * t);
            return;
        }
        e->speed = fminf(e->speed + DRIVE_ACCEL * dt, DRIVE_TOP);
        e->s = fmaxf(0.0f, e->s - e->speed * dt);
        game_driveAt(a, e->s, &pos, &dir);
        Car_place(&game->world, car, pos, yaw_facing(vec3_scale(dir, -1.0f)));
        if (e->s <= 0.0f) { hide(game, car); e->stage = ERRAND_AWAY; e->time = 0.0f; }
        break;
    }
    case ERRAND_AWAY:
        if (e->time < SHOP_AWAY) return;
        loadUp(car, e->list);
        e->stage = ERRAND_BACK;
        e->time = 0.0f;
        e->s = 0.0f;
        e->speed = 3.0f;
        break;
    case ERRAND_BACK: { /* in again the way it came the first time, braking for its spot */
        float left = a->length - e->s;
        float brake = sqrtf(fmaxf(0.0f, 2.0f * DRIVE_BRAKE * left));
        e->speed = fminf(fminf(e->speed + DRIVE_ACCEL * dt, DRIVE_TOP), brake + 0.15f);
        e->s = fminf(a->length, e->s + e->speed * dt);
        game_driveAt(a, e->s, &pos, &dir);
        float yaw = yaw_facing(dir);
        if (left < 4.0f) yaw = lerpf(yaw, a->park_yaw, smoothstepf(4.0f, 0.0f, left));
        Car_place(&game->world, car, pos, yaw);
        if (e->s >= a->length - 0.01f) arrived(game, car);
        break;
    }
    default: break;
    }
}

void game_shopLoaded(Game *game) {
    Errand *e = &game->errand;
    Object *car = theCar(game);
    game->world.shopping = e->stage != ERRAND_NONE;
    if (e->stage == ERRAND_NONE || !car || !game_drivePath(game)) return;
    if (e->stage == ERRAND_TO_CAR && e->driver) { /* (on the way over again) */
        e->driver->ai.errand = true;
        e->driver->ai.errand_there = false;
        e->driver->ai.errand_to = Car_door(car, 0);
    } else if (e->stage == ERRAND_LEAVING) { /* (gone, as good as) */
        hide(game, car);
        e->stage = ERRAND_AWAY;
        e->time = 0.0f;
    } else if (e->stage == ERRAND_AWAY) hide(game, car);
    else if (e->stage == ERRAND_BACK) arrived(game, car);
}