#include "world/scene.h" #include "world/site.h" #include "world/cast.h" #include typedef struct { ObjectType type; Palette palette; Vec3 position; Vec3 size; float weight; Vec3 rotation_deg; } Spawn; #define HUMAN_SIZE { 0.5f, 1.75f, 0.25f } #define NO_ROTATION { 0, 0, 0 } /* The yard where it all starts, around Site_yard(). The player must be the first entry. Order also defines physics order. */ static const Spawn SCENE[] = { /* Characters */ { OBJ_PLAYER, PALETTE_MODERN, { 1, 1, 0 }, HUMAN_SIZE, 75, NO_ROTATION }, /* (dropped onto the ground: low, no hard landing) */ { OBJ_HUMAN, PALETTE_MODERN, { -1.5f, 1, -3 }, HUMAN_SIZE, 88, { 0, 180, 0 } }, /* Volkram, facing the player */ { OBJ_HUMAN, PALETTE_MODERN, { 2.5f, 1, -4 }, HUMAN_SIZE, 60, { 0, 160, 0 } }, /* Jana */ { OBJ_HUMAN, PALETTE_MODERN, { -5.0f, 1, 4 }, HUMAN_SIZE, 70, { 0, 120, 0 } }, /* Toni */ { OBJ_HUMAN, PALETTE_MODERN, { 6.0f, 1, 3 }, HUMAN_SIZE, 72, { 0, 250, 0 } }, /* Kurti */ { OBJ_HUMAN, PALETTE_MODERN, { 4.0f, 1, -6 }, HUMAN_SIZE, 92, { 0, 200, 0 } }, /* Martin */ { OBJ_HUMAN, PALETTE_MODERN, { -3.0f, 1, -6 }, HUMAN_SIZE, 76, { 0, 150, 0 } }, /* Jonas */ /* Props (the ground itself is the terrain, see terrain.c) */ { OBJ_TREE, PALETTE_WARM, { 3, 0, 2 }, { 3.0f, 3.0f, 0.0f }, 0, NO_ROTATION }, /* canopy, trunk */ /* (the tools are in the tool room of your garages, see site.c) */ /* A few apples (the drinks come in the crates from your car) */ { OBJ_APPLE, PALETTE_WARM, { 2.2f, 0.2f, 1.2f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 3.9f, 0.2f, 1.4f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 2.6f, 0.2f, 3.1f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, /* A log to cut and split */ { OBJ_LOG, PALETTE_WARM, { 5.8f, -0.25f, 0.2f }, { 0.36f, 1.4f, 0.0f }, 0, { 90, 20, 0 } }, }; /* Things to try things out with (carrying, pushing, lifting together), out of the way beside the obstacle course: positions from its test corner. */ static const Spawn TESTING[] = { /* Things to carry: one hand (small) or both hands (medium crate) */ { OBJ_BOX, PALETTE_WARM, { 0.0f, 0.5f, -2.0f }, { 0.40f, 0.40f, 0.40f }, 6, NO_ROTATION }, { OBJ_BOX, PALETTE_MODERN, { 1.2f, 0.5f, -2.2f }, { 0.35f, 0.35f, 0.35f }, 5, { 0, 30, 0 } }, { OBJ_CYLINDER, PALETTE_MODERN, { 2.0f, 0.5f, -1.8f }, { 0.30f, 0.45f, 0.30f }, 4, NO_ROTATION }, { OBJ_BOX, PALETTE_STANDARD, { 0.5f, 0.5f, -5.5f }, { 0.80f, 0.60f, 0.60f }, 20, NO_ROTATION }, /* Pushable objects */ { OBJ_CYLINDER, PALETTE_WARM, { -3, 0, 1 }, { 1, 3, 1 }, 1200, { 90, 0, 0 } }, /* takes two or more */ { OBJ_CYLINDER, PALETTE_MODERN, { -3, 0, 4 }, { 1, 3, 1 }, 100, NO_ROTATION }, { OBJ_BOX, PALETTE_MODERN, { 1, 0, 2 }, { 2, 1, 2 }, 20, NO_ROTATION }, }; /* Who they are, how they look and their part in it: world/cast.c (the people above, in that order: you, Volkram, Jana, Toni, Kurti, Martin, Jonas). */ /* Where the things to try out stand: beside the obstacle course, left of its lanes. */ #define TEST_ACROSS -9.5f /* m across from the middle lane (the lanes are 3.5 apart) */ #define TEST_ALONG 6.0f /* m down from its start */ static Vec3 testCorner(void) { Vec3 start, along; Site_courseFrame(&start, &along); Vec3 across = vec3(-along.z, 0.0f, along.x); return vec3_add(start, vec3_add(vec3_scale(across, TEST_ACROSS), vec3_scale(along, TEST_ALONG))); } /* A ramp to walk up: a plank 20 degrees up, its foot on the ground, its top resting on a block (a loose box left tilted would hang in the air: loose things don't turn in the physics). */ #define RAMP_AT { 4.0f, 0.0f, 8.0f } #define RAMP_SIZE { 1.2f, 0.15f, 3.2f } #define RAMP_SLOPE 20.0f static bool addRamp(World *world, Vec3 corner) { const Vec3 at = vec3_add(corner, (Vec3)RAMP_AT); Object *ramp = Object_create(OBJ_BOX_STATIC, PALETTE_MODERN, at, (Vec3)RAMP_SIZE, 100.0f); if (!ramp) return false; Object_setRotation(ramp, vec3(deg2rad(RAMP_SLOPE), 0.0f, 0.0f)); /* its lowest and highest corners: the low end goes onto the ground */ const Collider *c = &ramp->colliders[0]; Vec3 center = Object_colliderCenter(ramp, 0), low = center, high = center; for (int k = 0; k < 8; k++) { Vec3 p = center; p = vec3_add(p, vec3_scale(c->axes[0], (k & 1 ? 1.0f : -1.0f) * c->half_extents.x)); p = vec3_add(p, vec3_scale(c->axes[1], (k & 2 ? 1.0f : -1.0f) * c->half_extents.y)); p = vec3_add(p, vec3_scale(c->axes[2], (k & 4 ? 1.0f : -1.0f) * c->half_extents.z)); if (p.y < low.y) low = p; if (p.y > high.y) high = p; } ramp->position.y += Terrain_height(&world->terrain, low.x, low.z) - 0.03f - low.y; Object_syncColliders(ramp); if (!World_add(world, ramp)) return false; /* the block under its top end (up to the ramp's underside there) */ Vec3 up_end = vec3_add(center, vec3_scale(c->axes[2], vec3_dot(vec3_sub(high, center), c->axes[2]) > 0.0f ? c->half_extents.z : -c->half_extents.z)); up_end.y += Terrain_height(&world->terrain, low.x, low.z) - 0.03f - low.y; Vec3 out = vec3_normalize(vec3(c->axes[2].x, 0.0f, c->axes[2].z)); if (vec3_dot(out, vec3_sub(high, low)) < 0.0f) out = vec3_scale(out, -1.0f); Vec3 foot = vec3_sub(up_end, vec3_scale(out, 0.25f)); /* (a bit in from the end: the plank sticks out over it) */ float ground = Terrain_height(&world->terrain, foot.x, foot.z); float under = up_end.y - fabsf(c->axes[1].y) * c->half_extents.y - 0.12f - ground; /* (the underside, a bit lower there) */ if (under < 0.1f) return true; Object *block = Object_create(OBJ_BOX_STATIC, PALETTE_GROUND, vec3(foot.x, ground, foot.z), vec3(1.3f, under, 0.4f), 100.0f); return block && World_add(world, block); } bool Scene_finish(World *world) { /* Everybody knows where the firewood goes: the woodpile by the barn */ Object *pile = NULL; for (int i = 0; i < world->count && !pile; i++) if (world->objects[i]->type == OBJ_WOODPILE) pile = world->objects[i]; for (int i = 0; i < world->count && pile; i++) if (world->objects[i]->type == OBJ_HUMAN) Ai_setWoodpile(world->objects[i], pile); /* Kurti and his sons sit at the fire when you come (they welcome you when you go over) */ Object *fire = NULL, *benches[3] = { NULL, NULL, NULL }; int nb = 0; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type == OBJ_FIREPLACE && !fire) fire = o; } for (int i = 0; i < world->count && fire && nb < 3; i++) { Object *o = world->objects[i]; if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH && vec3_length(vec3_sub(o->position, fire->position)) < 5.0f) benches[nb++] = o; } for (int i = 0, k = 0; i < world->count && k < nb; i++) { Object *o = world->objects[i]; if (o->type != OBJ_HUMAN || !o->lives_here) continue; Object *bench = benches[k++]; Vec3 out = vec3_normalize(vec3_sub(fire->position, bench->position)); o->position = vec3_add(bench->position, vec3(out.x * 0.55f, 0.0f, out.z * 0.55f)); o->position.y = Terrain_height(&world->terrain, o->position.x, o->position.z); Object_syncColliders(o); Object_sit(o, bench, fire->position); o->ai.mode = AI_IDLE; o->ai.timer = 150.0f + 30.0f * (float)k; /* (sitting there a while) */ } return addRamp(world, testCorner()); } Object *Scene_load(World *world) { Object *player = NULL; int characters = 0; const Vec3 yard = Site_yard(); for (size_t i = 0; i < SDL_arraysize(SCENE); i++) { const Spawn *s = &SCENE[i]; bool person = s->type == OBJ_PLAYER || s->type == OBJ_HUMAN; Object *obj = person ? Cast_create(characters++, vec3_add(s->position, yard)) : Object_create(s->type, s->palette, vec3_add(s->position, yard), s->size, s->weight); if (!obj) return NULL; Vec3 r = s->rotation_deg; if (r.x != 0.0f || r.y != 0.0f || r.z != 0.0f) Object_setRotation(obj, vec3(deg2rad(r.x), deg2rad(r.y), deg2rad(r.z))); if (!World_add(world, obj)) return NULL; if (s->type == OBJ_PLAYER && !player) player = obj; } /* The things to try out, by the obstacle course */ const Vec3 corner = testCorner(); for (size_t i = 0; i < SDL_arraysize(TESTING); i++) { const Spawn *s = &TESTING[i]; Object *obj = Object_create(s->type, s->palette, vec3_add(s->position, corner), s->size, s->weight); if (!obj) return NULL; Vec3 r = s->rotation_deg; if (r.x != 0.0f || r.y != 0.0f || r.z != 0.0f) Object_setRotation(obj, vec3(deg2rad(r.x), deg2rad(r.y), deg2rad(r.z))); if (!World_add(world, obj)) return NULL; } /* Everything else: the farm, your buildings, the forest */ return Site_build(world) ? player : NULL; }