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

#include <math.h>
#include <stdio.h>

#define STAGE_W 6.0f  /* m wide (along its front) */
#define STAGE_D 4.0f  /* m deep */
#define LOOK_STEPS 8  /* the look changes in this many steps through each part of the work */

/* Person-hours each step takes (someone of middling skill). */
static const float NEED[BUILD_DONE] = {
    [BUILD_MARKED] = 1.5f, /* the ground cleared, levelled */
    [BUILD_FRAME]  = 4.0f, /* posts in, beams on (and the logs for it) */
    [BUILD_FLOOR]  = 4.0f, /* the deck */
    [BUILD_ROOF]   = 3.0f, /* the back wall, the roof */
    [BUILD_DECOR]  = 2.5f, /* paint, banners */
};

/* Each floor's colour (the paint on the back wall, the banners). */
static Vec3 accent(int project) {
    switch (project) {
    case PROJECT_HOFBUEHNE:      return vec3(0.78f, 0.18f, 0.16f); /* bands: red */
    case PROJECT_OBSTWIESE:      return vec3(0.52f, 0.20f, 0.70f); /* psytrance: purple */
    case PROJECT_KARTOFFELACKER: return vec3(0.90f, 0.55f, 0.15f); /* world music: orange */
    case PROJECT_HIDDEN:         return vec3(0.16f, 0.22f, 0.50f); /* techno: deep blue */
    default:                     return vec3(0.5f, 0.5f, 0.5f);
    }
}

Object *Stage_create(Project project, Vec3 at, float yaw) {
    Object *s = Object_create(OBJ_STAGE, PALETTE_WARM, at, vec3(STAGE_W, 3.6f, STAGE_D), (float)project);
    if (!s) return NULL;
    s->rotation = vec3(0.0f, yaw, 0.0f);
    Object_syncColliders(s);
    Stage_show(s);
    return s;
}

Object *Stage_of(Object *const *objects, int count, Project project) {
    for (int i = 0; i < count; i++)
        if (objects[i]->type == OBJ_STAGE && objects[i]->variant == (int)project) return objects[i];
    return NULL;
}

const char *Stage_stepName(int stage) {
    static const char *const NAME[] = { "clearing the ground", "the frame", "the deck", "the back wall and the roof", "paint and banners", "done" };
    return NAME[stage < 0 ? 0 : stage > BUILD_DONE ? BUILD_DONE : stage];
}

float Stage_part(const Object *s) {
    if (s->build_stage >= BUILD_DONE) return 1.0f;
    return fminf(1.0f, s->build_work / NEED[s->build_stage]);
}

/* What each step takes, lying by it: logs, then the bought material (MaterialKind). */
static const int NEED_LOGS[BUILD_DONE] = { [BUILD_FRAME] = STAGE_LOGS };
static const int NEED_MAT[BUILD_DONE][MATERIAL_KINDS] = {
    [BUILD_FLOOR] = { [MATERIAL_BOARDS] = 2, [MATERIAL_NAILS] = 1 },
    [BUILD_ROOF]  = { [MATERIAL_BOARDS] = 2, [MATERIAL_SCREWS] = 1 },
    [BUILD_DECOR] = { [MATERIAL_PAINT] = 1 },
};

/* Things of a kind lying by it (not held, not used up): logs (kind -1) or material. */
static int lyingBy(const World *world, const Object *s, int kind, Object **found, int max) {
    int n = 0;
    for (int i = 0; i < world->count && n < max; i++) {
        Object *o = world->objects[i];
        if (o->is_held || o->broken) continue;
        if (kind < 0 ? o->type != OBJ_LOG : o->type != OBJ_MATERIAL || o->variant != kind) continue;
        float dx = o->position.x - s->position.x, dz = o->position.z - s->position.z;
        if (dx * dx + dz * dz < STAGE_LOG_REACH * STAGE_LOG_REACH) { if (found) found[n] = o; n++; }
    }
    return n;
}

static int needOf(int stage, int kind) {
    if (stage < 0 || stage >= BUILD_DONE) return 0;
    return kind < 0 ? NEED_LOGS[stage] : NEED_MAT[stage][kind];
}

int Stage_missing(const World *world, const Object *s, char *what, size_t size) {
    static const char *const ONE[] = { "log", "bundle of boards", "box of nails", "box of screws", "bucket of paint" };
    static const char *const MORE[] = { "logs", "bundles of boards", "boxes of nails", "boxes of screws", "buckets of paint" };
    int total = 0;
    size_t len = 0;
    if (what && size) what[0] = 0;
    for (int kind = -1; kind < MATERIAL_KINDS; kind++) {
        int need = needOf(s->build_stage, kind);
        if (!need) continue;
        int have = lyingBy(world, s, kind, NULL, need), gap = need - have;
        if (gap <= 0) continue;
        total += gap;
        if (what && len < size) {
            if (gap == 1) snprintf(what + len, size - len, "%sa %s", len ? " and " : "", ONE[kind + 1]);
            else snprintf(what + len, size - len, "%s%d %s", len ? " and " : "", gap, MORE[kind + 1]);
            while (len < size && what[len]) len++;
        }
    }
    return total;
}

int Stage_missingMaterial(const World *world, const Object *s) {
    for (int kind = 0; kind < MATERIAL_KINDS; kind++) {
        int need = needOf(s->build_stage, kind);
        if (need && lyingBy(world, s, kind, NULL, need) < need) return kind;
    }
    return -1;
}

int Stage_work(World *world, Object *s, float person_hours) {
    if (s->build_stage >= BUILD_DONE) return -1;
    if (Stage_missing(world, s, NULL, 0) > 0) return -2;
    s->build_work += person_hours;
    int done = -1;
    if (s->build_work >= NEED[s->build_stage]) {
        for (int kind = -1; kind < MATERIAL_KINDS; kind++) { /* (what lay by it went into it) */
            Object *used[STAGE_LOGS > 4 ? STAGE_LOGS : 4];
            int n = lyingBy(world, s, kind, used, needOf(s->build_stage, kind));
            for (int k = 0; k < n; k++) Object_useUp(used[k]);
        }
        s->build_stage++;
        s->build_work = 0.0f;
        done = s->build_stage;
    }
    Stage_show(s);
    return done;
}

Vec3 Stage_workSpot(const Object *s, float side) {
    Vec3 f = yaw_front(s->rotation.y), r = vec3(-f.z, 0.0f, f.x); /* (its front: +Z of the mesh) */
    float out = s->build_stage >= BUILD_ROOF ? STAGE_D * 0.5f - 0.6f : STAGE_D * 0.5f + 0.5f; /* (on the deck once there's a roof to it) */
    Vec3 p = vec3_add(s->position, vec3_add(vec3_scale(f, out), vec3_scale(r, side * STAGE_W * 0.4f)));
    p.y = s->position.y;
    return p;
}

Vec3 Stage_handsAt(const Object *s, Vec3 spot, float t) {
    Vec3 to = vec3_sub(s->position, spot);
    to.y = 0.0f;
    float len = vec3_length(to);
    to = len > 1e-3f ? vec3_scale(to, 1.0f / len) : yaw_front(s->rotation.y);
    float h = s->build_stage == BUILD_MARKED ? 0.3f : s->build_stage == BUILD_FRAME ? 1.2f + 0.4f * sinf(t * 0.7f) : s->build_stage == BUILD_FLOOR ? STAGE_DECK : 1.4f;
    float knock = 0.06f * fabsf(sinf(t * 6.0f)); /* (hammering) */
    return vec3_add(spot, vec3_add(vec3_scale(to, 0.55f), vec3(0.0f, h + knock, 0.0f)));
}

void Stage_show(Object *s) {
    int look = s->build_stage * LOOK_STEPS + (int)(Stage_part(s) * (float)LOOK_STEPS);
    if (s->shown == look + 1) return;
    Mesh_destroy(&s->mesh);
    s->mesh = Shapes_stage(s->build_stage, Stage_part(s), STAGE_W, STAGE_D, accent(s->variant));
    s->shown = look + 1;
    int solid = s->build_stage > BUILD_FLOOR ? 1 : 0; /* (the deck can be stood on once it's in) */
    if (s->collider_count != solid) { s->collider_count = solid; Object_syncColliders(s); }
}