Nimbin[12]?Games / Entropie / entropie/src/world/car.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/world/car.c 16.9 KB · 415 lines raw
#include "world/car.h"
#include "world/site.h"
#include "render/shapes.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SEAT_X     0.38f  /* seats this far left (+X: the driver's side) or right of the middle */
#define SEAT_Z    -0.95f  /* the back seats this far back from the front ones */
#define EYE_UP     1.30f  /* the eyes this high, sitting */
#define DOOR_OUT   0.6f   /* out of the door: standing this far beside the car */
#define BOOT_BACK  0.8f   /* standing this far behind it to reach into the boot */
#define SET_DOWN   0.3f   /* what comes out is set down this far behind it */
#define BOTTLE_KG  0.6f
#define CAR_FLOOR  0.35f  /* the floor inside this high */
#define SEAT_UP    0.18f  /* the seats this high over it (heads under the roof) */

static const Vec3 CRATE_BOTTLE = { 0.07f, 0.25f, 0.0f };

/* Local (nose to +Z, the driver's side +X) to world, on the car's ground. */
static Vec3 local(const Object *car, Vec3 p) {
    return vec3_add(car->position, mat4_mul_dir(mat4_rotate_y(car->rotation.y), p));
}

Object *Car_create(Vec3 position, float yaw) {
    Object *car = Object_create(OBJ_CAR, PALETTE_STANDARD, position, vec3(CAR_WIDTH, 1.45f, CAR_LENGTH), 1300.0f);
    if (!car) return NULL;
    car->load = calloc(1, sizeof(*car->load));
    snprintf(car->name, sizeof(car->name), "Car");
    Object_setRotation(car, vec3(0.0f, yaw, 0.0f));
    return car;
}

bool Car_load(Object *car, CargoKind kind, Object *owner) {
    CarLoad *l = car->load;
    if (!l || l->n >= CAR_LOAD_MAX) return false;
    l->kind[l->n] = kind;
    l->owner[l->n] = owner;
    l->n++;
    return true;
}

static const char *const CARGO_NAME[] = {
    [CARGO_BEER] = "a crate of beer", [CARGO_WATER] = "a crate of water", [CARGO_SLEEPING_BAG] = "a sleeping bag",
    [CARGO_MAT] = "a sleeping mat", [CARGO_TENT_SMALL] = "a tent", [CARGO_TENT_BIG] = "the big tent",
    [CARGO_SODA] = "a crate of soda", [CARGO_MATE] = "a crate of mate",
    [CARGO_BOARDS] = "a bundle of boards", [CARGO_NAILS] = "a box of nails", [CARGO_SCREWS] = "a box of screws",
    [CARGO_PAINT] = "a bucket of paint", [CARGO_FOOD] = "a crate of food", [CARGO_SAUSAGES] = "a cool box of sausages",
};

int Crate_bottles(int variant) { return variant == 1 || variant == 2 ? 12 : 20; }
float Crate_bottleKg(int variant) { return variant == 1 || variant == 2 ? 1.2f : 0.85f; }
const char *Crate_name(int variant) {
    static const char *const NAME[] = { "beer", "water", "soda", "mate" };
    return NAME[variant & 3];
}

static bool isCrate(CargoKind k) { return k == CARGO_BEER || k == CARGO_WATER || k == CARGO_SODA || k == CARGO_MATE; }
static int crateOf(CargoKind k) { return k == CARGO_WATER ? 1 : k == CARGO_SODA ? 2 : k == CARGO_MATE ? 3 : 0; }

const char *Car_nextName(const Object *car) {
    const CarLoad *l = car->load;
    return l && l->n > 0 ? CARGO_NAME[l->kind[l->n - 1]] : NULL;
}

bool Car_nextHeavy(const Object *car) {
    const CarLoad *l = car->load;
    CargoKind k = l && l->n > 0 ? l->kind[l->n - 1] : CARGO_MAT;
    return l && l->n > 0 && (isCrate(k) || k == CARGO_TENT_BIG || k == CARGO_BOARDS || k == CARGO_FOOD || k == CARGO_SAUSAGES);
}

/* Everybody's own colours for their things (by name: you, Volkram, Jana, Toni, anybody else). */
static int whose(const Object *owner) {
    if (!owner || owner->type == OBJ_PLAYER) return 0;
    if (!strcmp(owner->name, "Volkram")) return 1;
    if (!strcmp(owner->name, "Jana")) return 2;
    if (!strcmp(owner->name, "Toni")) return 3;
    return 4;
}

static Vec3 gearColor(GearKind kind, const Object *owner) {
    static const Vec3 BAGS[] = { { 0.70f, 0.18f, 0.16f }, { 0.20f, 0.36f, 0.22f }, { 0.48f, 0.26f, 0.58f }, { 0.88f, 0.48f, 0.12f }, { 0.4f, 0.4f, 0.45f } };
    static const Vec3 MATS[] = { { 0.20f, 0.40f, 0.75f }, { 0.85f, 0.75f, 0.20f }, { 0.25f, 0.65f, 0.60f }, { 0.75f, 0.30f, 0.45f }, { 0.5f, 0.5f, 0.5f } };
    switch (kind) {
    case GEAR_SLEEPING_BAG: return BAGS[whose(owner)];
    case GEAR_MAT:          return MATS[whose(owner)];
    case GEAR_TENT_SMALL:   return vec3(0.30f, 0.45f, 0.25f);
    case GEAR_TENT_BIG:     return vec3(0.42f, 0.42f, 0.24f);
    }
    return vec3(0.5f, 0.5f, 0.5f);
}

Object *Gear_create(GearKind kind, Object *owner, Vec3 at) {
    Object *item = Object_create(OBJ_GEAR, PALETTE_STANDARD, at, vec3_zero(), (float)kind);
    if (!item) return NULL;
    item->owner = owner;
    Mesh_destroy(&item->mesh);
    item->mesh = Shapes_gear((int)kind, gearColor(kind, owner));
    return item;
}

void Gear_refresh(Object *gear) {
    if (gear->type != OBJ_GEAR) return;
    Mesh mesh = Shapes_gear(gear->variant, gearColor((GearKind)gear->variant, gear->owner));
    if (!mesh.vao) return;
    Mesh_destroy(&gear->mesh);
    gear->mesh = mesh;
}

Object *Car_takeOut(World *world, Object *car) {
    CarLoad *l = car->load;
    if (!l || l->n == 0 || !car->boot_open) return NULL;
    CargoKind kind = l->kind[l->n - 1];
    Object *owner = l->owner[l->n - 1];
    Vec3 at = local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - SET_DOWN));
    at.y = World_groundHeight(world, at.x, at.z, at.y + 1.0f) + 0.02f;
    Object *item;
    if (isCrate(kind)) {
        item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), (float)crateOf(kind));
    } else if (kind >= CARGO_BOARDS && kind <= CARGO_PAINT) {
        item = Object_create(OBJ_MATERIAL, PALETTE_STANDARD, at, vec3_zero(), (float)(kind - CARGO_BOARDS));
    } else if (kind == CARGO_FOOD || kind == CARGO_SAUSAGES) { /* (bought: carried in, it runs out) */
        bool food = kind == CARGO_FOOD;
        item = Object_create(OBJ_SUPPLY, food ? PALETTE_WARM : PALETTE_STANDARD, at, food ? vec3(0.5f, 0.32f, 0.36f) : vec3(0.5f, 0.34f, 0.32f),
                             food ? -13.0f : -9.0f);
        if (item) item->stock = food ? FOOD_PORTIONS : SAUSAGE_PORTIONS;
    } else {
        GearKind g = kind == CARGO_SLEEPING_BAG ? GEAR_SLEEPING_BAG : kind == CARGO_MAT ? GEAR_MAT
                   : kind == CARGO_TENT_SMALL ? GEAR_TENT_SMALL : GEAR_TENT_BIG;
        item = Gear_create(g, owner, at);
    }
    if (!item) return NULL;
    item->rotation.y = car->rotation.y + PI_F * 0.5f; /* (lying across the back of it) */
    Object_syncColliders(item);
    if (!World_add(world, item)) return NULL;
    l->n--;
    return item;
}

Vec3 Car_bootSpot(const Object *car) {
    return local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - BOOT_BACK));
}

static Vec3 seatAt(int seat) {
    return vec3(seat % 2 == 0 ? SEAT_X : -SEAT_X, 0.0f, seat >= 2 ? SEAT_Z : 0.0f);
}

Vec3 Car_eye(const Object *car, int seat) {
    Vec3 s = seatAt(seat);
    s.y = EYE_UP;
    return local(car, s);
}

Vec3 Car_door(const Object *car, int seat) {
    Vec3 s = seatAt(seat);
    s.x = (s.x > 0.0f ? 1.0f : -1.0f) * (CAR_WIDTH * 0.5f + DOOR_OUT);
    return local(car, s);
}

void Car_place(World *world, Object *car, Vec3 at, float yaw) {
    at.y = world->has_terrain ? Terrain_height(&world->terrain, at.x, at.z) : at.y;
    car->position = at;
    car->rotation = vec3(0.0f, yaw, 0.0f);
    Object_syncColliders(car);
    for (int i = 0; i < world->count; i++) {
        Object *o = world->objects[i];
        if (o->riding != car) continue;
        Vec3 p = local(car, vec3_add(seatAt(o->ride_seat), vec3(0.0f, 0.0f, 0.18f))); /* (the feet under the dashboard) */
        o->position = vec3(p.x, at.y + CAR_FLOOR, p.z);
        o->sit_at = o->position;
        o->sit_yaw = yaw + PI_F; /* (a person's yaw faces the other way round from a thing's: see Object_facing / yaw_front) */
        o->rotation.y = yaw + PI_F;
        o->velocity = vec3_zero();
        Object_syncColliders(o);
    }
}

void Car_getIn(Object *car, Object *who, int seat) {
    who->riding = car;
    who->ride_seat = seat;
    who->velocity = vec3_zero();
    who->movement = vec3_zero();
    who->sitting = true; /* (sitting in it: seen through the windows) */
    who->seat = NULL;
    who->sit_moving = false;
    who->sit_style = SIT_SEAT;
    who->body.seat_height = SEAT_UP;
}

void Car_getOut(Object *who) {
    Object *car = who->riding;
    if (!car) return;
    Vec3 door = Car_door(car, who->ride_seat);
    who->riding = NULL;
    who->sitting = false;
    who->position = vec3(door.x, car->position.y, door.z);
    Vec3 out = vec3_sub(door, car->position);
    who->rotation.y = atan2f(out.z, out.x) + PI_F / 2.0f; /* (facing away from the car) */
    who->velocity = vec3_zero();
    who->body.replant = true;
    Object_syncColliders(who);
}

void Car_openBoot(Object *car, bool open) {
    if (car->boot_open == open) return;
    car->boot_open = open;
    Mesh_destroy(&car->mesh);
    car->mesh = Shapes_car(CAR_COLOR, open);
}

/* Where the camping things of this many go: by whose tent (you, Volkram and Jana, Toni). */
static int campOf(const Object *owner) {
    int w = whose(owner);
    return w == 1 || w == 2 ? 1 : w == 3 ? 2 : 0;
}

/* How many of that type lie by a spot already (not this one, not carried). */
static int lyingAt(const World *world, const Object *item, Vec3 base, float r) {
    int n = 0;
    for (int i = 0; i < world->count; i++) {
        const Object *o = world->objects[i];
        if (o != item && o->type == item->type && !o->is_held && !o->broken && hypotf(o->position.x - base.x, o->position.z - base.z) < r) n++;
    }
    return n;
}

Vec3 Car_dropSpot(const World *world, const Object *item) {
    Vec3 along = Site_campAlong(), across = Car_campDoor();
    if (item->type == OBJ_MATERIAL) { /* the store: rows in front of the car room, out from its wall */
        Vec3 base = Site_materialSpot();
        int n = lyingAt(world, item, base, 4.5f);
        return vec3_add(base, vec3_add(vec3_scale(along, 0.6f * (float)(n % 6) - 1.5f), vec3_scale(across, -1.3f * (float)(n / 6 % 3))));
    }
    if (item->type == OBJ_SUPPLY) { /* the food: into the kitchen, beside the drinks */
        Vec3 base = vec3_sub(Site_drinksSpot(), vec3_scale(along, 1.4f));
        return vec3_sub(base, vec3_scale(along, 0.6f * (float)(lyingAt(world, item, base, 2.5f) % 4)));
    }
    if (item->type == OBJ_CRATE) {
        Vec3 base = Site_drinksSpot();
        int n = 0;
        for (int i = 0; i < world->count; i++) {
            const Object *o = world->objects[i];
            if (o != item && o->type == OBJ_CRATE && !o->is_held && vec3_length(vec3_sub(o->position, base)) < 1.6f) n++;
        }
        return vec3_add(base, vec3_scale(along, 0.5f * (float)n));
    }
    Vec3 at = Site_campSpot(campOf(item->owner));
    if (item->variant >= GEAR_TENT_SMALL) return at; /* (the tent where it'll stand, the rest in front of its door) */
    float a = (item->variant == GEAR_MAT ? 0.55f : 0.0f) + (whose(item->owner) == 2 ? 1.2f : 0.0f) - 0.6f; /* (Jana's beside Volkram's) */
    return vec3_add(at, vec3_add(vec3_scale(along, a), vec3_scale(across, CAMP_BEDS_OUT)));
}

Vec3 Car_campDoor(void) { return Site_campDoor(); }

#define EMPTY_KG 0.35f /* an empty bottle */

static int crateSlots(const Object *crate) { return Crate_bottles(crate->variant) < CRATE_SLOTS ? Crate_bottles(crate->variant) : CRATE_SLOTS; }
static bool bigKind(int kind) { return kind == BOTTLE_KIND_WATER || kind == BOTTLE_KIND_COLA || kind == BOTTLE_KIND_ORANGE; }

void Crate_fill(Object *crate) {
    static const int OWN[4] = { BOTTLE_KIND_BEER, BOTTLE_KIND_WATER, BOTTLE_KIND_COLA, BOTTLE_KIND_MATE };
    int v = crate->variant & 3;
    memset(crate->crate_slot, 0, sizeof crate->crate_slot);
    for (int k = 0; k < crateSlots(crate); k++) {
        int kind = v == 2 ? (k % 2 ? BOTTLE_KIND_ORANGE : BOTTLE_KIND_COLA) : OWN[v]; /* (soda: cola and orange) */
        crate->crate_slot[k] = CRATE_SLOT(kind, BOTTLE_SIPS);
    }
    Crate_recount(crate);
}

void Crate_recount(Object *crate) {
    int n = 0, empties = 0;
    float kg = 2.0f;
    for (int k = 0; k < crateSlots(crate); k++) {
        int code = crate->crate_slot[k], kind = CRATE_KIND(code);
        if (!kind) continue;
        n++;
        if (CRATE_SIPS(code) == 0) { empties++; kg += EMPTY_KG; }
        else kg += bigKind(kind) ? 1.2f : 0.85f;
    }
    crate->stored = n;
    crate->crate_empties = empties;
    crate->weight = kg;
}

int Crate_look(const Object *crate) {
    int h = 17;
    for (int k = 0; k < CRATE_SLOTS; k++) h = h * 31 + crate->crate_slot[k];
    return h;
}

int Crate_bottleKind(const Object *b) {
    if (b->type == OBJ_BOTTLE) return BOTTLE_KIND_BEER;
    if (b->type != OBJ_WATER) return BOTTLE_KIND_NONE;
    static const int DRINK[4] = { BOTTLE_KIND_WATER, BOTTLE_KIND_COLA, BOTTLE_KIND_ORANGE, BOTTLE_KIND_MATE };
    return DRINK[b->variant & 3];
}

bool Crate_belongs(const Object *crate, int kind) {
    switch (crate->variant & 3) {
    case 0:  return kind == BOTTLE_KIND_BEER;
    case 1:  return kind == BOTTLE_KIND_WATER;
    case 2:  return kind == BOTTLE_KIND_COLA || kind == BOTTLE_KIND_ORANGE;
    default: return kind == BOTTLE_KIND_MATE;
    }
}

int Crate_wrong(const Object *crate) {
    int n = 0;
    for (int k = 0; k < crateSlots(crate); k++) {
        int kind = CRATE_KIND(crate->crate_slot[k]);
        if (kind && !Crate_belongs(crate, kind)) n++;
    }
    return n;
}

/* The bottle in slot k, lying on top of the crate (the slot empty now). */
static Object *outOfSlot(World *world, Object *crate, int k) {
    int code = crate->crate_slot[k], kind = CRATE_KIND(code);
    Vec3 top = vec3_add(Object_colliderCenter(crate, 0), vec3(0.0f, CRATE_SIZE_Y * 0.5f + 0.16f, 0.0f));
    Object *b = Object_create(kind == BOTTLE_KIND_BEER ? OBJ_BOTTLE : OBJ_WATER, PALETTE_WARM, top, CRATE_BOTTLE, BOTTLE_KG);
    if (!b) return NULL;
    if (kind != BOTTLE_KIND_BEER) Object_setDrink(b, kind == BOTTLE_KIND_WATER ? 0 : kind == BOTTLE_KIND_COLA ? 1 : kind == BOTTLE_KIND_ORANGE ? 2 : 3);
    b->sips = CRATE_SIPS(code);
    if (!World_add(world, b)) { Object_destroy(b); return NULL; }
    crate->crate_slot[k] = 0;
    Crate_recount(crate);
    return b;
}

Object *Crate_takeBottle(World *world, Object *crate) {
    if (crate->type != OBJ_CRATE) return NULL;
    int best = -1, best_score = -1;
    for (int k = 0; k < crateSlots(crate); k++) { /* (its own, the fullest; else a full one of another kind) */
        int code = crate->crate_slot[k];
        if (!CRATE_KIND(code) || CRATE_SIPS(code) == 0) continue;
        int score = CRATE_SIPS(code) + (Crate_belongs(crate, CRATE_KIND(code)) ? 100 : 0);
        if (score > best_score) { best_score = score; best = k; }
    }
    return best >= 0 ? outOfSlot(world, crate, best) : NULL;
}

int Crate_nextWrong(const Object *crate) {
    for (int k = crateSlots(crate) - 1; k >= 0; k--) {
        int kind = CRATE_KIND(crate->crate_slot[k]);
        if (kind && !Crate_belongs(crate, kind)) return kind;
    }
    return BOTTLE_KIND_NONE;
}

Object *Crate_takeWrong(World *world, Object *crate) {
    if (crate->type != OBJ_CRATE) return NULL;
    for (int k = crateSlots(crate) - 1; k >= 0; k--) {
        int kind = CRATE_KIND(crate->crate_slot[k]);
        if (kind && !Crate_belongs(crate, kind)) return outOfSlot(world, crate, k);
    }
    return NULL;
}

bool Crate_putBottle(Object *crate, Object *bottle) {
    if (!crate || crate->type != OBJ_CRATE || !bottle || bottle->broken) return false;
    int kind = Crate_bottleKind(bottle);
    if (!kind) return false;
    for (int k = 0; k < crateSlots(crate); k++) {
        if (crate->crate_slot[k]) continue;
        crate->crate_slot[k] = CRATE_SLOT(kind, bottle->sips < 0 ? 0 : bottle->sips > 31 ? 31 : bottle->sips);
        Crate_recount(crate);
        Object_useUp(bottle);
        return true;
    }
    return false;
}

static bool hasRoom(const Object *c) { return c->type == OBJ_CRATE && !c->is_held && !c->broken && c->stored < crateSlots(c); }

Object *Crate_rightFor(Object *const *objects, int count, int kind, Vec3 at, float range) {
    Object *best = NULL;
    for (int i = 0; i < count; i++) {
        Object *c = objects[i];
        if (!hasRoom(c) || !Crate_belongs(c, kind)) continue;
        float d = hypotf(c->position.x - at.x, c->position.z - at.z);
        if (d < range) { range = d; best = c; }
    }
    return best;
}

Object *Crate_forBottle(Object *const *objects, int count, const Object *bottle, Vec3 at, float range) {
    int kind = Crate_bottleKind(bottle);
    if (!kind) return NULL;
    Object *own = Crate_rightFor(objects, count, kind, at, range);
    return own ? own : Crate_withRoom(objects, count, at, range);
}

Object *Crate_withRoom(Object *const *objects, int count, Vec3 at, float range) {
    Object *best = NULL;
    for (int i = 0; i < count; i++) {
        Object *c = objects[i];
        if (!hasRoom(c)) continue;
        float d = hypotf(c->position.x - at.x, c->position.z - at.z);
        if (d < range) { range = d; best = c; }
    }
    return best;
}

void Car_serve(World *world) {
    for (int i = 0; i < world->count; i++) {
        Object *o = world->objects[i];
        if (!o->is_character || !o->ai.take_from) continue;
        Object *from = o->ai.take_from;
        o->ai.take_from = NULL;
        Object *got = from->type == OBJ_CRATE ? Crate_takeBottle(world, from) : from->type == OBJ_CAR ? Car_takeOut(world, from) : NULL;
        if (got) o->ai.target = got;
    }
}