Nimbin[12]?Games / Entropie / entropie/src/world/scene.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/scene.c 8.9 KB · 174 lines raw
#include "world/scene.h"
#include "world/site.h"
#include "world/cast.h"

#include <SDL3/SDL.h>

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;
}