Nimbin[12]?Games / Entropie / entropie/src/game/game_bigtree.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/game/game_bigtree.c 17.1 KB · 344 lines raw
/*
 * The big old tree (world/bigtree.h): what the game does around Enrico's work
 * (ai_jobs.c bigTree) and the pullers (ai_onRope).
 *
 * - The throw line: lying at the tree's foot. Thrown (T, wound up, looking up)
 *   so it touches the crown, it's caught over a branch where it touched: the
 *   higher, the better it pulls. Left click at the rope's end: pulled down
 *   again to try once more (before the pulling starts).
 * - The pulling: when the notch is in, the crew nearest are called to the
 *   rope's end (four at most); you can take hold of it too (left click near
 *   the end). Enrico cuts once three or more are on it.
 * - Which way it goes: the pull (how many, how strong, how high the rope) and
 *   the notch against the lean (towards the road) and a gust. Way off, it
 *   comes down across the road onto the phone line: torn, a bill from the
 *   phone company, Kurti cross. Anybody it comes down on: injured (Forestry).
 */

#include <math.h>

#include "game/game_internal.h"
#include "world/bigtree.h"
#include "world/cast.h"
#include "world/forestry.h"
#include "world/injury.h"
#include "world/weather.h"

#define LINE_BILL     1500.0f /* EUR: the phone company puts the line back up */
#define PULL_EACH     0.35f   /* each on the rope (x how strong: fitness) ... */
#define NOTCH_PULL    0.45f   /* ... the notch steering it too ... */
#define LEAN          1.15f   /* ... against the lean, and a gust on top */
#define GUST_LEAN     0.8f
#define ROPE_TOP      16.0f   /* m: caught higher than this is no better */
#define CALL_NEAR     160.0f  /* m: the crew this near get called to the rope */
#define ON_ROPE       3.0f    /* m: you near the rope's end: on it (left click) */
#define PLACE_NEAR    1.2f    /* m: a puller at their place counts */
#define STORM_WIND    11.0f   /* m/s: wind like this can bring the old tree down ... */
#define STORM_FALLS   0.3f    /* ... this likely a game hour */

static Object *named(const Game *game, const char *name) {
    for (int i = 0; i < game->world.count; i++) {
        Object *o = game->world.objects[i];
        if (o->is_character && !strcmp(o->name, name)) return o;
    }
    return NULL;
}

/* ---------- The throw line ---------- */

static void throwLine(Game *game) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    Object *tree = BigTree_tree(w);
    if (!tree || tree->toppled || bt->step < BT_ROPE || bt->rope_h > 0.0f || tree->collider_count < 2) return;
    for (int i = 0; i < w->count; i++) {
        Object *bag = w->objects[i];
        if (bag->type != OBJ_ROPE || bag->broken || bag->is_held || bag->is_grounded) continue;
        Vec3 c = Object_colliderCenter(tree, 1), half = tree->colliders[1].half_extents;
        Vec3 p = bag->position;
        if (fabsf(p.x - c.x) > half.x + 0.1f || fabsf(p.z - c.z) > half.z + 0.1f || p.y < c.y - half.y || p.y > c.y + half.y) continue;
        bt->rope_h = fminf(p.y - Terrain_height(&w->terrain, tree->position.x, tree->position.z), ROPE_TOP);
        bt->rope_h = fmaxf(bt->rope_h, 4.5f);
        Object_useUp(bag);
        static char line[128];
        snprintf(line, sizeof line, "Caught over a branch - %.1f m up!%s", bt->rope_h,
                 bt->rope_h < 7.0f ? " A bit low (left click the rope's end: pull it down, throw again)" : "");
        say(game, line);
        game->message_time = 5.0f;
        Object *e = named(game, "Enrico");
        if (e && !Game_inBerlin(e)) Ai_say(e, bt->rope_h >= 10.0f ? "Yes! Way up there - that pulls like a dream." : bt->rope_h >= 7.0f ? "Good throw! That'll do nicely."
                                            : "Hm, low. It'll hold - but higher pulls better.");
        return;
    }
}

/* ---------- The rope, as it's seen ---------- */

static void showRope(Game *game) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    int shown = bt->rope_h <= 0.0f || bt->step >= BT_FALLING ? 0 : bt->step >= BT_PULL ? 2 : 1;
    int key = shown * 1000 + (int)(bt->rope_h * 10.0f);
    if (key == game->rope_shown) return;
    game->rope_shown = key;
    if (game->rope_look) { Object_useUp(game->rope_look); game->rope_look = NULL; }
    if (!shown) return;
    Vec3 a = BigTree_attach(w);
    Vec3 end = shown == 2 ? BigTree_ropeEnd(w) : vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f));
    if (shown == 1) end.y = Terrain_height(&w->terrain, end.x, end.z) + 0.02f;
    Mesh m = Shapes_rope(vec3_zero(), vec3_sub(end, a), shown == 2 ? 0.3f : 0.0f, shown == 1);
    if (!m.vao) return;
    Object *look = Object_createStructure(m, a, 0.0f, vec3(BT_ROPE_OUT, 12.0f, BT_ROPE_OUT));
    if (look && World_add(w, look)) game->rope_look = look;
}

bool game_bigTreeClick(Game *game) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    Object *p = game->player;
    if (bt->rope_h <= 0.0f || bt->step >= BT_FALLING || Hands_freeHand(p) < 0) return false;
    if (bt->step < BT_PULL) { /* the rope hanging down by the trunk: pulled down again, to throw it once more */
        Vec3 end = vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f));
        if (hypotf(end.x - p->position.x, end.z - p->position.z) > 2.0f) return false;
        bt->rope_h = 0.0f;
        bt->rope_wait = 0.0f;
        if (bt->step == BT_NOTCH && bt->work <= 0.0f) bt->step = BT_ROPE;
        Vec3 at = vec3_add(p->position, vec3_scale(Object_facing(p), 0.6f));
        at.y = World_groundHeight(w, at.x, at.z, p->position.y + 1.0f) + 0.02f;
        Object *bag = Object_create(OBJ_ROPE, PALETTE_WARM, at, vec3_zero(), 0.0f);
        if (bag && !World_add(w, bag)) bag = NULL;
        say(game, "You pull the line down again - pick it up and throw it once more");
        return true;
    }
    Vec3 end = BigTree_ropeEnd(w); /* the pulling: hold of the rope's end */
    if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f) return false;
    game->on_rope = !game->on_rope;
    say(game, game->on_rope ? "You take hold of the rope - when it goes, you pull (walk off: let go)" : "You let go of the rope");
    return true;
}

/* ---------- The pulling ---------- */

static bool canPull(const Object *o) {
    return o->type == OBJ_HUMAN && o->cast_id > 0 && strcmp(o->name, "Enrico") && strcmp(o->name, "Kurti") && !o->injured && o->hospital_until <= 0.0f
        && Injury_castKind(o) == INJ_NONE && !Body_isDown(&o->body) && !o->in_tent && o->indoors <= 0.0f && !o->riding && !o->ai.meeting
        && !Game_inBerlin(o) && o->drunk < 0.6f;
}

static void pullers(Game *game, const Object *enrico) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    bool on = ((bt->step == BT_PULL || bt->step == BT_CUT) && enrico && enrico->ai.on_project) || bt->step == BT_FALLING; /* (falling: they hold on till it's down) */
    int taken[BT_PULLERS] = { 0 }, n = 0;
    for (int i = 0; i < w->count; i++) {
        Object *o = w->objects[i];
        if (o->puller <= 0.0f) continue;
        if (!on || !canPull(o)) { o->puller = 0.0f; continue; }
        taken[(int)o->puller - 1] = 1;
        if (vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR) n++;
    }
    Object *p = game->player;
    if (game->on_rope) {
        Vec3 end = BigTree_ropeEnd(w);
        if (!on && bt->step != BT_FALLING) game->on_rope = false;
        else if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f || Body_isDown(&p->body)) { game->on_rope = false; say(game, "You let go of the rope"); }
        else {
            n++;
            p->hands.rope = bt->step == BT_CUT && bt->work >= BT_CUT_GOES - 3.0f ? 1.0f : 0.05f;
            p->hands.rope_at = BigTree_ropeAt(w, p->position);
            p->hands.rope_dir = vec3_normalize(vec3_sub(BigTree_ropeEnd(w), BigTree_attach(w)));
        }
    }
    if (bt->step != BT_FALLING) bt->pullers = n;
    if (!on) return;
    for (int k = 0; k < BT_PULLERS; k++) { /* free places: the nearest who can come */
        if (taken[k]) continue;
        Object *best = NULL;
        float best_d = CALL_NEAR;
        for (int i = 0; i < w->count; i++) {
            Object *o = w->objects[i];
            if (o->puller > 0.0f || !canPull(o) || o->ai.mode == AI_FOLLOW || o->ai.talking) continue;
            float d = hypotf(o->position.x - Site_bigTree().x, o->position.z - Site_bigTree().z);
            if (d < best_d) { best_d = d; best = o; }
        }
        if (!best) break;
        best->puller = (float)(k + 1);
        best->ai.on_project = false;
        best->ai.mode = AI_IDLE;
        Ai_say(best, k == 0 ? "Coming - I'll take the end of the rope!" : "I'm on it!");
    }
}

/* ---------- Down it comes ---------- */

static float gauss(World *w) { /* (-1..1, mostly near 0) */
    float s = 0.0f;
    for (int k = 0; k < 3; k++) { w->rng = w->rng * 1664525u + 1013904223u; s += (float)(w->rng >> 8) / 16777216.0f; }
    return (s - 1.5f) / 1.5f;
}

static void fell(Game *game, Object *tree, Object *enrico) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    float pull = 0.0f;
    for (int i = 0; i < w->count; i++) {
        const Object *o = w->objects[i];
        if (o->puller > 0.0f && vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR + 0.5f)
            pull += PULL_EACH * (0.7f + 0.6f * o->fitness);
    }
    if (game->on_rope) pull += PULL_EACH * (0.7f + 0.6f * game->player->fitness);
    float leverage = clampf(bt->rope_h / 10.0f, 0.3f, 1.15f);
    float notch = NOTCH_PULL * (enrico ? 0.6f + 0.4f * enrico->skill[ORDER_TREES] : 0.7f);
    float margin = pull * leverage + notch - LEAN - GUST_LEAN * w->gust;
    float side = gauss(w) >= 0.0f ? 1.0f : -1.0f;
    float off = margin >= 0.3f ? 6.0f * gauss(w)
              : margin >= 0.0f ? (6.0f + (0.3f - margin) * 80.0f) * gauss(w)
              : side * fminf(180.0f, -margin / 0.5f * 180.0f + 25.0f * fabsf(gauss(w))); /* (not enough: round towards the lean) */
    bt->off = off;
    Vec3 safe = Site_bigTreeSafe();
    float a = off * PI_F / 180.0f;
    Vec3 dir = vec3(safe.x * cosf(a) - safe.z * sinf(a), 0.0f, safe.x * sinf(a) + safe.z * cosf(a));
    Forestry_fell(w, tree, dir, 0.6f);
    for (int i = 0; i < w->count; i++) if (w->objects[i]->puller > 0.0f) w->objects[i]->ai.speech_time = 0.0f;
    if (enrico) { /* (out of the way: back and to the side) */
        enrico->ai.errand = true;
        enrico->ai.errand_there = false;
        enrico->ai.errand_to = BigTree_escapeSpot(w, off > 0.0f ? -1.0f : 1.0f); /* (away from the way it's turning) */
        enrico->ai.errand_with = NULL;
        enrico->carving = false;
        Ai_say(enrico, fabsf(off) < 30.0f ? "TIMBEEER!" : "Watch out - it's turning! Get away!");
    }
    say(game, "TIMBEEER! The big tree comes down");
}

static bool crosses(Vec3 a, Vec3 b, Vec3 c, Vec3 d) { /* (two stretches on the ground cross) */
    float d1 = (b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x), d2 = (b.x - a.x) * (d.z - a.z) - (b.z - a.z) * (d.x - a.x);
    float d3 = (d.x - c.x) * (a.z - c.z) - (d.z - c.z) * (a.x - c.x), d4 = (d.x - c.x) * (b.z - c.z) - (d.z - c.z) * (b.x - c.x);
    return d1 * d2 < 0.0f && d3 * d4 < 0.0f;
}

static void down(Game *game, Object *tree) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    Vec3 t = Site_bigTree(), tip = vec3_add(t, vec3_scale(tree->fall_dir, 21.0f)), safe = Site_bigTreeSafe();
    bt->off = acosf(clampf(vec3_dot(tree->fall_dir, safe), -1.0f, 1.0f)) * 180.0f / PI_F; /* (however it was felled) */
    Vec3 pole[4];
    int n = Site_phoneLine(pole, 4);
    for (int k = 0; k + 1 < n; k++) if (crosses(t, tip, pole[k], pole[k + 1])) bt->lines_down = true;
    for (int k = 0; k < n; k++) /* (into a pole) */
        if (vec3_length(vec3_cross(vec3_sub(vec3(pole[k].x, 0, pole[k].z), vec3(t.x, 0, t.z)), tree->fall_dir)) < 1.2f
            && vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) > 0.0f && vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) < 21.0f) bt->lines_down = true;
    bt->step = BT_DOWN;
    bt->told = true;
    for (int i = 0; i < w->count; i++) {
        Object *o = w->objects[i];
        if (o->puller > 0.0f) o->puller = 0.0f;
        if (o->is_character && o->cast_id > 0 && !strcmp(o->name, "Enrico")) o->ai.errand = false;
    }
    game->on_rope = false;
    static char line[192];
    Object *kurti = named(game, "Kurti");
    if (bt->lines_down) {
        Site_phoneWiresDown(w, true);
        w->cash -= LINE_BILL;
        for (int i = 0; i < w->count; i++) {
            Object *o = w->objects[i];
            if (o->is_character && o->cast_id > 0) o->commitment = clampf(o->commitment - (o == kurti ? 0.25f : 0.05f), 0.0f, 1.0f);
        }
        if (kurti) Ai_say(kurti, "My phone line! Forty years that line stood. Who pays for that?!");
        snprintf(line, sizeof line, "It came down across the road - right through the phone line. The phone company puts it back up: %d EUR from the cash box",
                 (int)LINE_BILL);
    } else {
        for (int i = 0; i < w->count; i++) {
            Object *o = w->objects[i];
            if (o->is_character && o->cast_id > 0) o->commitment = fminf(1.0f, o->commitment + (!strcmp(o->name, "Enrico") ? 0.1f : 0.04f));
        }
        if (kurti && fabsf(bt->off) < 40.0f) Ai_say(kurti, "Hah! Right where you said. Well pulled, all of you.");
        snprintf(line, sizeof line, fabsf(bt->off) < 15.0f ? "Down - right where it should be. Well pulled!" : "Down - %.0f degrees off, but clear of the phone line. Phew.",
                 fabsf(bt->off));
    }
    say(game, line);
    game->message_time = 8.0f;
}

/* A tree felled that wasn't to be: the fine from the cash box (forestry.c counted it). */
static void treeFine(Game *game) {
    World *w = &game->world;
    if (w->tree_fine <= 0) return;
    w->cash -= (float)w->tree_fine;
    Object *kurti = named(game, "Kurti");
    static char line[160];
    if (w->tree_fine_kind == 1) {
        if (kurti) { kurti->commitment = fmaxf(0.0f, kurti->commitment - 0.3f); Ai_say(kurti, "My apple tree! My father planted that one. Unbelievable."); }
        snprintf(line, sizeof line, "One of Kurti's apple trees - %d EUR from the cash box for a new one, and Kurti's furious", w->tree_fine);
    } else snprintf(line, sizeof line, "That tree was protected - the town's fine: %d EUR from the cash box", w->tree_fine);
    say(game, line);
    game->message_time = 6.0f;
    w->tree_fine = 0;
}

void game_bigTree(Game *game, float dt) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    (void)dt;
    treeFine(game);
    if (bt->lines_down != game->wires_shown) { Site_phoneWiresDown(w, bt->lines_down); game->wires_shown = bt->lines_down; }
    Object *tree = BigTree_tree(w);
    if (!tree) return;
    Object *enrico = named(game, "Enrico");
    throwLine(game);
    pullers(game, enrico);
    if (tree->toppled && bt->step < BT_FALLING) { /* (felled some other way: by you without the rope, or the storm) */
        bt->step = BT_FALLING;
        for (int i = 0; i < w->count; i++) w->objects[i]->puller = 0.0f;
    }
    if (!tree->toppled && bt->step < BT_CUT && Weather_windSpeed(w) > STORM_WIND) { /* old and half dead: a storm can bring it down */
        w->rng = w->rng * 1664525u + 1013904223u;
        if ((float)(w->rng >> 8) / 16777216.0f < STORM_FALLS * w->game_dt / 3600.0f) {
            Forestry_fell(w, tree, Site_bigTreeLean(), 0.3f);
            say(game, "CRACK! The big old tree gives way in the storm - it's coming down!");
            game->message_time = 6.0f;
        }
    }
    if (bt->step == BT_FALLING && !tree->toppled) fell(game, tree, enrico);
    if (bt->step == BT_FALLING && tree->toppled && tree->topple >= 1.0f) down(game, tree);
    showRope(game);
}

const char *game_bigTreeStatus(const Game *game) {
    const BigTree *bt = &game->world.bigtree;
    static char text[96];
    switch (bt->step) {
    case BT_STANDING: return "standing (Enrico hasn't started)";
    case BT_ROPE:     return "the rope up (throw the line from its foot)";
    case BT_NOTCH:    snprintf(text, sizeof text, "the notch, %d%% (rope %.1f m up)", (int)(100.0f * bt->work / BT_NOTCH_WORK), bt->rope_h); return text;
    case BT_PULL:     snprintf(text, sizeof text, "on the rope: %d of 3 (rope %.1f m up)", bt->pullers, bt->rope_h); return text;
    case BT_CUT: case BT_FALLING: return "coming down!";
    default:          return bt->lines_down ? "down - and the phone line too" : "down, the phone line fine";
    }
}

/* Dev: on to the next step (Enrico here and at it; the rope up; the notch in). */
void game_bigTreeJump(Game *game) {
    World *w = &game->world;
    BigTree *bt = &w->bigtree;
    Object *e = named(game, "Enrico");
    if (!e) {
        for (int id = 1; id < Cast_count() && !e; id++)
            if (!strcmp(Cast_get(id)->name, "Enrico")) {
                Vec3 at = BigTree_notchSpot(w);
                at.x += 2.0f;
                e = Cast_create(id, at);
                if (e && !World_add(w, e)) e = NULL;
            }
    }
    if (e && Game_inBerlin(e)) return;
    if (e) { e->ai.on_project = true; e->ai.project_off = 0.0f; e->ai.greet = false; }
    if (bt->step == BT_STANDING) return; /* (he starts it: the throw line put down) */
    if (bt->step == BT_ROPE && bt->rope_h <= 0.0f) {
        for (int i = 0; i < w->count; i++) if (w->objects[i]->type == OBJ_ROPE && !w->objects[i]->broken) Object_useUp(w->objects[i]);
        bt->rope_h = 10.0f;
        return;
    }
    if (bt->step == BT_NOTCH) bt->work = BT_NOTCH_WORK;
}