Nimbin[12]?Games / Entropie / entropie/src/world/tent.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/tent.c 16.3 KB · 351 lines raw
#include "world/tent.h"
#include "world/car.h"
#include "people/hands.h"
#include "render/shapes.h"
#include "world/weather.h"

#include <stdio.h>
#include <string.h>

#define LAY_TIME    4.0f   /* s (one person, average): spreading it out, the poles through their sleeves */
#define POLES_SMALL 16.0f  /* ... the poles in and up */
#define POLES_BIG   24.0f
#define COVER_SMALL 10.0f  /* ... the cover over it */
#define COVER_BIG   14.0f
#define PEG_TIME    2.5f   /* ... each peg (with a stone to hit it: twice as quick) */
#define DOOR_OUT    0.45f  /* in front of the door: this far out from the cover */
#define BED_REACH   0.8f   /* a sleeping bag or mat put down this near the door goes in */
#define DOWN_QUICKER 0.6f  /* taking it down: each bit this much of the time putting it up took ... */
#define FOLD_TIME   7.0f   /* ... and folding it up into its bag at the end */

/* How far the stage shows: up, its work; coming down, the other way round. */
static float shownWork(const Object *tent) {
    return tent->tent_down ? 1.0f - tent->tent_work : tent->tent_work;
}

static Vec3 local(const Object *tent, Vec3 p) {
    return vec3_add(tent->position, mat4_mul_dir(mat4_rotate_y(tent->rotation.y), p));
}

static Vec3 toLocal(const Object *tent, Vec3 w) {
    return mat4_mul_dir_transposed(mat4_rotate_y(tent->rotation.y), vec3_sub(w, tent->position));
}

/* Everybody's own cover colour (by name, like their other things). */
static Vec3 flyColor(const Object *tent) {
    const Object *o = tent->owner;
    if (o && o->guest) { /* the guests': all sorts */
        static const Vec3 FLY[] = { { 0.78f, 0.22f, 0.20f }, { 0.20f, 0.42f, 0.70f }, { 0.85f, 0.62f, 0.15f }, { 0.25f, 0.55f, 0.35f },
                                    { 0.55f, 0.30f, 0.62f }, { 0.15f, 0.60f, 0.62f }, { 0.60f, 0.60f, 0.58f }, { 0.88f, 0.42f, 0.18f } };
        return FLY[((o->guest - 1) / 2) % 8];
    }
    if (tent->variant) return vec3(0.40f, 0.44f, 0.24f);                         /* the big one: olive */
    if (!o || o->type == OBJ_PLAYER) return vec3(0.22f, 0.36f, 0.56f);          /* yours: blue */
    if (!strcmp(o->name, "Toni")) return vec3(0.78f, 0.42f, 0.16f);             /* Toni's: orange */
    return vec3(0.30f, 0.50f, 0.30f);
}

/* Its look and its box for how far it's up (built again only when that shows). */
static void reshape(Object *tent) {
    int big = tent->variant, steps = tent->tent_stage == TENT_POLES || tent->tent_stage == TENT_COVER ? (int)(shownWork(tent) * 12.0f) : 0;
    int shown = tent->tent_stage * 1000 + steps * 10 + tent->stored;
    if (shown == tent->shown) return;
    tent->shown = shown;
    Mesh_destroy(&tent->mesh);
    tent->mesh = Shapes_tent(big, tent->tent_stage, (float)steps / 12.0f, tent->stored, vec3(0.90f, 0.86f, 0.62f), flyColor(tent));
    float h = tent->tent_stage == TENT_LAID ? 0.04f : tent->tent_stage == TENT_POLES ? fmaxf(0.04f, TENT_H(big) * shownWork(tent)) : TENT_H(big);
    tent->colliders[0].center = vec3(0.0f, h * 0.5f, 0.0f);
    tent->colliders[0].half_extents = vec3(TENT_W(big) * 0.5f, h * 0.5f, TENT_L(big) * 0.5f);
    Object_syncColliders(tent);
}

Object *Tent_unpack(World *world, Object *bag, Vec3 door_dir) {
    if (!bag || bag->type != OBJ_GEAR || bag->variant < GEAR_TENT_SMALL || bag->is_held || bag->broken) return NULL;
    int big = bag->variant == GEAR_TENT_BIG;
    Vec3 at = bag->position;
    at.y = Terrain_height(&world->terrain, at.x, at.z); /* (on the ground: not on the sleeping bags lying there - it hovered) */
    Object *tent = Object_create(OBJ_TENT, PALETTE_STANDARD, at, vec3_zero(), big ? 1.0f : 0.0f);
    if (!tent) return NULL;
    tent->owner = bag->owner;
    if (big && bag->owner && !strcmp(bag->owner->name, "Volkram")) /* (the big one is Volkram's and Jana's) */
        for (int i = 0; i < world->count; i++)
            if (world->objects[i]->type == OBJ_HUMAN && !strcmp(world->objects[i]->name, "Jana")) tent->owner2 = world->objects[i];
    snprintf(tent->name, sizeof(tent->name), "Tent");
    door_dir = vec3_normalize(vec3(door_dir.x, 0.0f, door_dir.z));
    Object_setRotation(tent, vec3(0.0f, yaw_facing(door_dir), 0.0f));
    reshape(tent);
    if (!World_add(world, tent)) return NULL;
    Object_useUp(bag);
    return tent;
}

static bool hasStone(const Object *who) {
    for (int h = 0; h < 2; h++)
        if (who->hands.item[h] && who->hands.state[h] == HAND_HOLDING && who->hands.item[h]->type == OBJ_STONE) return true;
    return false;
}

void Tent_takeDown(Object *const *objects, int count, Object *tent) {
    if (tent->type != OBJ_TENT || tent->tent_down || tent->broken) return;
    for (int i = 0; i < count; i++) { /* (anybody in it: out) */
        Object *o = objects[i];
        if (o->in_tent != tent) continue;
        o->rain_nap = false;
        if (o->body.asleep) Body_getUp(&o->body);
        Tent_crawlOut(o);
    }
    tent->tent_down = true;
    tent->tent_work = 0.0f;
    if (tent->tent_stage >= TENT_UP) { tent->tent_stage = TENT_PEGS; tent->stored = TENT_PEGS(tent->variant); }
    else if (tent->tent_stage == TENT_PEGS && tent->stored == 0) tent->tent_stage = TENT_COVER;
    reshape(tent);
}

void Tent_refresh(Object *tent) {
    tent->shown = -1;
    reshape(tent);
}

bool Tent_movable(const Object *tent) {
    return tent && tent->type == OBJ_TENT && !tent->broken &&
           (tent->tent_stage < TENT_PEGS || (tent->tent_stage == TENT_PEGS && tent->stored == 0) || (tent->tent_down && tent->tent_stage <= TENT_COVER));
}

void Tent_startMove(Object *tent) {
    if (tent->tent_down) { /* (the pegs out on the way down: now it's going up again where it's put) */
        tent->tent_down = false;
        if (tent->tent_stage == TENT_COVER) { tent->tent_stage = TENT_PEGS; tent->stored = 0; }
        tent->tent_work = 0.0f;
        reshape(tent);
    }
    tent->tent_dragged = true;
}

/* Coming down: the pegs out, the cover off, the poles down, folded up (packed in Tent_update). */
static int workDown(Object *tent, float rate, float dt) {
    int big = tent->variant;
    float time = tent->tent_stage == TENT_LAID ? FOLD_TIME
               : DOWN_QUICKER * (tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL)
                               : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME);
    tent->tent_work += dt * rate / time;
    tent->tent_idle = 0.0f;
    int done = -1;
    if (tent->tent_work >= 1.0f) {
        if (tent->tent_stage == TENT_LAID) { tent->tent_work = 1.0f; return TENT_DOWN_PACKED; } /* (Tent_update packs it) */
        tent->tent_work = 0.0f;
        if (tent->tent_stage == TENT_PEGS) {
            if (--tent->stored <= 0) { tent->stored = 0; tent->tent_stage = TENT_COVER; done = TENT_DOWN + TENT_COVER; }
        } else {
            tent->tent_stage--;
            done = TENT_DOWN + tent->tent_stage;
        }
    }
    reshape(tent);
    return done;
}

int Tent_work(Object *tent, Object *who, float dt) {
    if (tent->type != OBJ_TENT || tent->broken || tent->tent_dragged || (tent->tent_stage >= TENT_UP && !tent->tent_down)) return -1;
    int big = tent->variant;
    float rate = (0.75f + 0.5f * who->wits) * (1.0f - 0.5f * fminf(who->drunk, 1.0f));
    if (tent->tent_down) return workDown(tent, rate, dt);
    float time = tent->tent_stage == TENT_LAID ? LAY_TIME
               : tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL)
               : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME / (hasStone(who) ? 2.0f : 1.0f);
    tent->tent_work += dt * rate / time;
    tent->tent_idle = 0.0f;
    int done = -1;
    if (tent->tent_work >= 1.0f) {
        tent->tent_work = 0.0f;
        if (tent->tent_stage == TENT_PEGS) {
            if (++tent->stored >= TENT_PEGS(big)) { tent->tent_stage = TENT_UP; done = TENT_UP; }
        } else {
            tent->tent_stage++;
            done = tent->tent_stage;
        }
    }
    reshape(tent);
    return done;
}

const char *Tent_job(const Object *tent) {
    if (tent->tent_down) switch (tent->tent_stage) {
    case TENT_LAID:  return "folding it up into its bag";
    case TENT_POLES: return "taking the poles down";
    case TENT_COVER: return "pulling the cover off";
    default:         return "pulling the pegs out";
    }
    switch (tent->tent_stage) {
    case TENT_LAID:  return "spreading it out";
    case TENT_POLES: return "putting the poles in";
    case TENT_COVER: return "pulling the cover over";
    case TENT_PEGS:  return "hammering the pegs in";
    default:         return "done";
    }
}

static Vec3 nextPeg(const Object *tent) {
    float x, z;
    Shapes_tentPeg(tent->variant, tent->tent_down ? (tent->stored > 0 ? tent->stored - 1 : 0) : tent->stored, &x, &z);
    return vec3(x, 0.0f, z);
}

Vec3 Tent_workSpot(const Object *tent, float side) {
    const float hw = TENT_W(tent->variant) * 0.5f, hl = TENT_L(tent->variant) * 0.5f;
    float s = side < 0.0f ? -1.0f : 1.0f;
    switch (tent->tent_stage) {
    case TENT_COVER: return local(tent, vec3(s * 0.5f * fabsf(side), 0.0f, -hl * 1.14f - 0.55f)); /* at the back, pulling it over */
    case TENT_PEGS: {
        Vec3 p = nextPeg(tent), out = vec3_normalize(vec3(p.x, 0.0f, p.z));
        return local(tent, vec3_add(p, vec3_scale(out, 0.45f)));
    }
    default: return local(tent, vec3(s * (hw + 0.55f), 0.0f, 0.0f)); /* beside it */
    }
}

Vec3 Tent_handsAt(const Object *tent, const Object *who, float clock) {
    const int big = tent->variant;
    const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f, h = TENT_H(big);
    Vec3 me = toLocal(tent, who->position);
    float s = me.x < 0.0f ? -1.0f : 1.0f;
    switch (tent->tent_stage) {
    case TENT_LAID:  return local(tent, vec3(s * hw, 0.08f, clampf(me.z, -hl, hl)));
    case TENT_POLES: return local(tent, vec3(s * hw * 0.7f, fmaxf(0.1f, 0.55f * h * shownWork(tent)), clampf(me.z, -hl * 0.5f, hl * 0.5f)));
    case TENT_COVER: return local(tent, vec3(s * 0.3f, h * 0.8f, -hl + 2.0f * hl * shownWork(tent)));
    case TENT_PEGS: {
        Vec3 p = nextPeg(tent);
        p.y = 0.06f + 0.14f * fabsf(sinf(clock * 7.0f)); /* (hitting it in) */
        return local(tent, p);
    }
    default: return Tent_door(tent);
    }
}

Vec3 Tent_door(const Object *tent) {
    return local(tent, vec3(0.0f, 0.0f, TENT_L(tent->variant) * 0.5f * 1.14f + DOOR_OUT));
}

bool Tent_isTheirs(const Object *tent, const Object *who) {
    return tent && tent->type == OBJ_TENT && who && (tent->owner == who || tent->owner2 == who);
}

Object *Tent_of(Object *const *objects, int count, const Object *who) {
    for (int i = 0; i < count; i++)
        if (objects[i]->type == OBJ_TENT && !objects[i]->broken && Tent_isTheirs(objects[i], who)) return objects[i];
    return NULL;
}

static int slotOf(const Object *tent, const Object *who) { return tent->owner2 == who ? 1 : 0; }

bool Tent_hasBed(const Object *tent, const Object *who) {
    return tent && (tent->tent_beds & (1 << (2 * slotOf(tent, who)))) != 0;
}

void Tent_crawlIn(Object *tent, Object *who) {
    const int big = tent->variant;
    float x = big ? (slotOf(tent, who) ? 0.45f : -0.45f) : 0.0f;
    Vec3 at = local(tent, vec3(x, 0.0f, TENT_L(big) * 0.5f - 0.95f)); /* (lying back into it, the feet towards the door) */
    who->in_tent = tent;
    who->sitting = false;
    who->position = vec3(at.x, tent->position.y, at.z);
    who->rotation.y = tent->rotation.y; /* (facing the door) */
    who->velocity = vec3_zero();
    who->movement = vec3_zero();
    Object_syncColliders(who);
    who->body.last_facing = Object_facing(who); /* (lies back from where they face now: the head to the back of the tent) */
    Body_lieDown(&who->body);
}

void Tent_crawlOut(Object *who) {
    Object *tent = who->in_tent;
    if (!tent) return;
    Vec3 door = Tent_door(tent);
    who->in_tent = NULL;
    who->position = vec3(door.x, tent->position.y, door.z);
    who->rotation.y = tent->rotation.y;
    who->body.replant = true;
    Object_syncColliders(who);
}

/* Folded up: its bag where it lay (left packed), the sleeping bags and mats that were in it at the door. */
static void pack(World *world, Object *tent) {
    const int big = tent->variant;
    Object *bag = Gear_create(big ? GEAR_TENT_BIG : GEAR_TENT_SMALL, tent->owner, vec3_add(tent->position, vec3(0.0f, 0.02f, 0.0f)));
    if (bag) {
        bag->keep = true;
        bag->rotation.y = tent->rotation.y;
        Object_syncColliders(bag);
        if (!World_add(world, bag)) Object_destroy(bag);
    }
    Object *sleeper[2] = { tent->owner, tent->owner2 };
    for (int k = 0; k < 4; k++) {
        if (!(tent->tent_beds & (1 << k)) || !sleeper[k / 2]) continue;
        Vec3 at = local(tent, vec3((big ? (k / 2 ? 0.45f : -0.45f) : 0.0f) + (k % 2 ? 0.3f : -0.3f), 0.0f, TENT_L(big) * 0.5f * 1.14f + DOOR_OUT + 0.3f));
        at.y = World_groundHeight(world, at.x, at.z, at.y + 0.5f) + 0.02f;
        Object *thing = Gear_create(k % 2 ? GEAR_MAT : GEAR_SLEEPING_BAG, sleeper[k / 2], at);
        if (!thing) continue;
        thing->rotation.y = tent->rotation.y;
        Object_syncColliders(thing);
        if (!World_add(world, thing)) Object_destroy(thing);
    }
    tent->tent_beds = 0;
    tent->owner = tent->owner2 = NULL;
    Object_useUp(tent);
}

/* The wind and a tent not pegged down (pegs under half in): it slides and
   turns along the ground, more standing on its poles than lying flat, till
   it snags on something or somebody gets hold of it (working on it). */
#define BLOW_STANDING 4.0f  /* m/s: on its poles, it goes from this wind ... */
#define BLOW_FLAT     6.5f  /* ... lying flat from this */
#define BLOW_PUSH     0.13f /* m/s it slides for each m/s over that */
#define BLOW_HELD     1.5f  /* s: worked on this recently, it's held */

static bool notIt(const Object *o, const void *tent) { return o == tent || o->is_character || !o->is_static; }

static void blowAbout(World *world, Object *tent, float dt) {
    if (tent->tent_stage >= TENT_UP || (tent->tent_stage == TENT_PEGS && tent->stored * 2 >= TENT_PEGS(tent->variant)) ||
        tent->tent_idle < BLOW_HELD) return;
    float speed = Weather_windSpeed(world), from = tent->tent_stage == TENT_LAID ? BLOW_FLAT : BLOW_STANDING;
    if (speed <= from) return;
    Vec3 dir = vec3_scale(world->wind, 1.0f / speed);
    float push = (speed - from) * BLOW_PUSH * (tent->tent_stage == TENT_LAID ? 0.5f : 1.0f);
    float reach = 0.5f * fmaxf(TENT_W(tent->variant), TENT_L(tent->variant)) + 0.15f;
    Vec3 from_p = vec3_add(tent->position, vec3(0.0f, 0.3f, 0.0f));
    if (World_rayCast(world, from_p, dir, reach, notIt, tent, NULL, NULL) >= 0.0f) return; /* (snagged on something) */
    tent->position = vec3_add(tent->position, vec3_scale(dir, push * dt));
    tent->position.y = Terrain_height(&world->terrain, tent->position.x, tent->position.z);
    Object_setRotation(tent, vec3(0.0f, tent->rotation.y + 0.06f * push * sinf(tent->position.x * 1.7f + tent->position.z) * dt, 0.0f));
    Object_syncColliders(tent);
    world->tent_blowing = tent;
    tent->tent_blown = 6.0f; /* (its owner comes running) */
}

#define RAIN_NAP_END 0.15f /* rain: under this, out of the tent again */

void Tent_update(World *world, float dt) {
    world->tent_blowing = NULL;
    for (int i = 0; i < world->count; i++) {
        Object *o = world->objects[i];
        if (o->type == OBJ_TENT) {
            o->tent_idle += dt;
            o->tent_blown = fmaxf(0.0f, o->tent_blown - dt);
            if (o->tent_down && !o->broken && o->tent_stage == TENT_LAID && o->tent_work >= 1.0f) pack(world, o);
            else if (!o->broken) blowAbout(world, o, dt);
            continue;
        }
        if (o->rain_nap && (!o->body.asleep || world->rain < RAIN_NAP_END)) { /* in out of the rain: up when it stops */
            o->rain_nap = false;
            if (o->body.asleep && o->needs.sleepiness < 0.6f) Body_getUp(&o->body);
        }
        if (o->is_character && o->in_tent && !o->body.asleep && o->body.pose == POSE_UP) { Tent_crawlOut(o); continue; }
        /* a sleeping bag or a mat put down at the door of its owner's tent: in it goes */
        if (o->type != OBJ_GEAR || o->variant > GEAR_MAT || o->is_held || o->broken || !o->owner) continue;
        Object *tent = Tent_of(world->objects, world->count, o->owner);
        if (!tent || tent->tent_stage < TENT_COVER) continue;
        Vec3 door = Tent_door(tent);
        if (hypotf(o->position.x - door.x, o->position.z - door.z) > BED_REACH) continue;
        tent->tent_beds |= 1 << (2 * slotOf(tent, o->owner) + (o->variant == GEAR_MAT ? 1 : 0));
        Object_useUp(o);
    }
}