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/world/car.c 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;
}
}