#include "world/car.h" #include "world/site.h" #include "render/shapes.h" #include #include #include #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; } }