Nimbin[12]?Games / Entropie / entropie/src/people/ai_fest.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/people/ai_fest.c 19 KB · 347 lines raw
/*
 * The festival weekend, for the people (the game says what's on: World.fest,
 * game_festival.c):
 *
 * - Guests come in from the chicken farm's yard, over to their place at the
 *   camping, the tent out of its bag and up (ai.c pitchTents, the two of them).
 * - A floor playing: over to it, a place on the dance floor in front of the
 *   stage, dancing to its beat for a good while, then a break (a drink, a pee,
 *   a sit down) and back. The better built the floor, the more fun. Sleepy:
 *   off to the tent. Evenings the party people drink; some too much.
 * - The awareness team (said yes to at the Herberge): the crew see to anybody
 *   lying about drunk - water, sat up, okay again.
 * - The storm: those who know it's coming (told by you, the crew, from a
 *   stage) go in - the main building, the barn, your garages' kitchen - and
 *   stay in till it's over. The crew tell the guests on their way in; with an
 *   awareness team they go round the whole place, the tents too.
 * - Sunday: the guests' tents down (packed away), off out at the gate.
 */

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

#include "people/ai_internal.h"
#include "world/car.h"
#include "world/guests.h"
#include "world/site.h"
#include "world/tent.h"
#include "world/why.h"

#define DANCE_SPREAD   4.5f   /* m: places on the dance floor round its middle */
#define DANCE_MIN      150.0f /* real s dancing at a go ... */
#define DANCE_MORE     300.0f /* ... up to this much longer */
#define DANCE_BREAK    60.0f  /* real s off between goes (up to twice that) */
#define FUN_DANCING    0.0009f /* fun a real second dancing (x how good the floor is) */
#define FUN_BORED      0.00015f /* ... lost a real second awake with nothing playing (day and evening) */
#define TELL_REACH     4.0f   /* m: near enough to tell somebody about the storm */
#define TELL_LOOK      25.0f  /* m: the crew warn guests this near on their way in ... */
#define TELL_LOOK_TEAM 140.0f /* ... the awareness team goes round everywhere */
#define HELP_LOOK      45.0f  /* m: the awareness team sees somebody lying drunk this near */
#define GATE_REACH     1.5f
#define HARASS_EVERY   900.0f /* real s: a drunk party guest dancing gets too close to somebody about this often */
#define LITTER_RATE    0.45f  /* pieces a guest drops a game hour awake (x 1.25 - the place's awareness, more drunk) ... */
#define LITTER_MOST    160    /* ... no more while this much lies about */

static bool crewMember(const Object *o) { return o->type == OBJ_HUMAN && o->cast_id > 0; }

static Object *guestNumber(Object *const *objects, int count, int k) {
    for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_HUMAN && objects[i]->guest == k) return objects[i];
    return NULL;
}

static void stopDancing(Object *self) {
    if (self->ai.fest_floor) self->ai.fest_time = -(DANCE_BREAK + DANCE_BREAK * random01(&self->ai));
    self->ai.fest_floor = 0;
    self->body.dance_goal = 0.0f;
}

static bool safeInside(const Object *o) { return o->indoors > 0.0f; }

/* ---------- The storm ---------- */

/* The nearest who doesn't know yet (awake, or - the awareness team - asleep in a tent too), nobody else after them. */
static Object *untold(const Object *self, Object *const *objects, int count, float look, bool tents) {
    Object *best = NULL;
    float best_d = look;
    for (int i = 0; i < count; i++) {
        Object *o = objects[i];
        if (o == self || o->type != OBJ_HUMAN || o->storm_told || o->gone_home || safeInside(o) || o->ai.meeting) continue;
        if ((o->in_tent || o->body.asleep) && !tents) continue;
        float d = flatDistance(o->position, self->position);
        if (d >= best_d) continue;
        bool taken = false;
        for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->type == OBJ_HUMAN && objects[j]->ai.fest_who == o;
        if (!taken) { best_d = d; best = o; }
    }
    return best;
}

static void tellStorm(Object *self, Object *o) {
    o->storm_told = true;
    if (o->in_tent) { o->rain_nap = false; Tent_crawlOut(o); }
    if (o->body.asleep) Body_getUp(&o->body);
    o->ai.talking = false;
    static const char *const SAY[] = { "A storm's coming - get inside, quick! The barn or the house.",
                                       "Storm warning! Not in the tent - into the barn!",
                                       "Come on, inside - a big storm, any minute now!" };
    Ai_say(self, SAY[(int)(random01(&self->ai) * 3.0f) % 3]);
    if (o->ai.speech_time <= 0.0f) Ai_say(o, random01(&o->ai) < 0.5f ? "What? Okay, okay - I'm going." : "Oh no. Right, inside.");
}

static Vec3 nearestDoor(const Object *self) {
    const Fest *f = &g_world->fest;
    int best = 0;
    for (int k = 1; k < 3; k++)
        if (flatDistance(f->shelter[k], self->position) < flatDistance(f->shelter[best], self->position)) best = k;
    return f->shelter[best];
}

static bool storm(Object *self, Object *const *objects, int count, float dt) {
    Ai *ai = &self->ai;
    const Fest *f = &g_world->fest;
    if (f->stage != FEST_WARNED && f->stage != FEST_STORM) { ai->fest_who = NULL; return false; }
    if (!self->storm_told || safeInside(self) || self->body.asleep || self->in_tent) return false;
    stopDancing(self);
    self->sitting = false;
    /* the crew: telling the guests on the way (the awareness team: everybody, everywhere, the tents too) */
    if (crewMember(self) && f->stage == FEST_WARNED && !self->lives_here) {
        Object *who = ai->fest_who;
        if (who && (who->storm_told || safeInside(who) || (ai->fest_walk += dt) > 90.0f)) who = ai->fest_who = NULL;
        if (!who) {
            who = ai->fest_who = untold(self, objects, count, f->awareness ? TELL_LOOK_TEAM : TELL_LOOK, f->awareness);
            ai->fest_walk = 0.0f;
        }
        if (who) {
            Vec3 at = who->in_tent ? Tent_door(who->in_tent) : who->position;
            if (ai_goToPoint(self, at, TELL_REACH, objects, count, dt)) { tellStorm(self, who); ai->fest_who = NULL; }
            return true;
        }
    }
    /* in: the nearest door */
    for (int h = 0; h < 2; h++) if (self->hands.item[h] && self->hands.item[h]->type != OBJ_UMBRELLA) Hands_drop(self, h);
    Vec3 door = nearestDoor(self);
    if (!ai_goToPoint(self, door, 1.2f, objects, count, dt)) {
        self->movement = vec3_scale(self->movement, 1.3f); /* (hurrying) */
        return true;
    }
    self->inside_door = door;
    self->indoors = 30.0f;
    ai->sheltering = false;
    return true;
}

/* ---------- The awareness team ---------- */

static bool seeTo(Object *self, Object *const *objects, int count, float dt) {
    Ai *ai = &self->ai;
    if (!g_world->fest.awareness || !crewMember(self) || self->lives_here || self->drunk > 0.4f) return false;
    if (Why_knows(self, WHY_AWARENESS) && (g_world->hour >= 18.0f || g_world->hour < 4.0f)) /* (on shift: awake till four) */
        self->needs.sleepiness = fminf(self->needs.sleepiness, 0.72f);
    Object *who = ai->fest_who;
    if (who && (!who->passed_out || !Body_isDown(&who->body) || (ai->fest_walk += dt) > 120.0f)) who = ai->fest_who = NULL;
    if (!who) {
        float best_d = HELP_LOOK;
        for (int i = 0; i < count; i++) {
            Object *o = objects[i];
            if (o == self || o->type != OBJ_HUMAN || !o->passed_out || !Body_isDown(&o->body) || o->in_tent) continue;
            float d = flatDistance(o->position, self->position);
            bool taken = false;
            for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->type == OBJ_HUMAN && objects[j]->ai.fest_who == o;
            if (d < best_d && !taken) { best_d = d; who = o; }
        }
        if (!who) return false;
        ai->fest_who = who;
        ai->fest_walk = 0.0f;
        Ai_say(self, "Somebody's down over there - I'll have a look.");
    }
    stopDancing(self);
    if (!ai_goToPoint(self, who->position, 1.7f, objects, count, dt)) return true;
    Hands_touch(self, vec3_add(who->position, vec3(0.0f, 0.3f, 0.0f)));
    Ai_say(self, random01(ai) < 0.5f ? "Hey, are you okay? Here - drink some water." : "Easy now. Sit up - water, then you'll be fine.");
    who->drunk = fminf(who->drunk, 0.45f); /* (water, sat up: back on their feet soon) */
    who->needs.sleepiness = fminf(who->needs.sleepiness, 0.5f);
    who->fun = fminf(1.0f, who->fun + 0.08f);
    g_world->fest.helped++;
    ai->fest_who = NULL;
    return true;
}

/* ---------- Guests ---------- */

/* The evening: the party people drink (their own), some too much. */
static void partyDrinks(Object *self) {
    float h = g_world->hour, game_h = g_world->game_dt / 3600.0f;
    bool evening = h >= 20.0f || h < 3.0f;
    if (!evening || self->body.asleep) return;
    float party = (float)((self->guest * 2654435761u) >> 24 & 255u) / 255.0f; /* (how much of a party person: their own) */
    float most = party < 0.6f ? 0.35f : 0.6f + 0.6f * (party - 0.6f) / 0.4f;  /* (a few end up over 1: down) */
    if (self->drunk < most) self->drunk = fminf(most, self->drunk + 0.22f * game_h);
}

/* Their place at the camping: over there, the tent out (the first of the two to get there). */
static bool camp(Object *self, Object *const *objects, int count, float dt) {
    if (self->camp_spot) return false;
    Vec3 spot = Guest_campSpot(self->guest);
    spot.y = self->position.y;
    Vec3 side = vec3(self->guest % 2 ? -1.2f : 1.2f, 0.0f, 1.6f); /* (beside where the tent goes) */
    if (!ai_goToPoint(self, vec3_add(spot, side), 0.8f, objects, count, dt)) return true;
    self->camp_spot = 1;
    Object *mate = guestNumber(objects, count, Guest_mate(self->guest));
    bool there = Tent_of(objects, count, self) != NULL;
    for (int i = 0; i < count && !there; i++) {
        const Object *o = objects[i];
        there = (o->type == OBJ_TENT && !o->broken && (o->owner == self || (mate && o->owner == mate)))
             || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken && (o->owner == self || (mate && o->owner == mate)));
    }
    if (!there) {
        spot.y = World_groundHeight(g_world, spot.x, spot.z, self->position.y + 2.0f) + 0.02f;
        Object *bag = Gear_create(GEAR_TENT_BIG, self, spot);
        if (bag && !World_add(g_world, bag)) bag = NULL;
        if (bag) {
            Object *tent = Tent_unpack(g_world, bag, Guest_campDoor());
            if (tent) tent->owner2 = mate;
            Ai_say(self, random01(&self->ai) < 0.5f ? "Here we are - tent up, then the music!" : "This is our spot. Right, the tent.");
        }
    }
    return true;
}

/* A cup, a can, a bit of paper: dropped where they stand (less where people look out for the place). */
static void litter(Object *self) {
    const Fest *f = &g_world->fest;
    if (f->stage < FEST_OPEN || f->stage > FEST_LEAVING || f->litter >= LITTER_MOST || self->heading_home) return;
    float rate = LITTER_RATE * (1.25f - g_world->aware) * (1.0f + 0.6f * fminf(self->drunk, 1.0f)) * (self->supporter ? 0.3f : 1.0f);
    if (random01(&self->ai) >= rate * g_world->game_dt / 3600.0f) return;
    Vec3 f2 = Object_facing(self);
    Vec3 at = vec3_add(self->position, vec3(-f2.z * 0.3f, 0.0f, f2.x * 0.3f));
    at.y = World_groundHeight(g_world, at.x, at.z, self->position.y + 1.0f);
    Site_litter(g_world, at, self->guest + (int)(random01(&self->ai) * 7.0f), self);
}

static float leaveHour(const Object *self) { return 10.0f + 4.5f * (float)((self->guest - 1) / 2) / (float)GUEST_SPOTS; } /* (two by two, from ten) */

/* Sunday: off home - the tent down and gone, out at the gate. */
static bool leave(Object *self, Object *const *objects, int count, float dt) {
    bool sunday = self->guest && !self->supporter && g_world->fest.stage == FEST_LEAVING && g_world->hour >= leaveHour(self);
    if (!sunday && !self->heading_home) return false;
    if (self->ai.mode == AI_WORK) Ai_stopWork(self);
    stopDancing(self);
    self->sitting = false;
    if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); }
    if (self->body.asleep) { Body_getUp(&self->body); return true; }
    Object *tent = Tent_of(objects, count, self);
    if (tent) { /* (packed up and away with them) */
        if (!ai_goToPoint(self, Tent_door(tent), 1.2f, objects, count, dt)) return true;
        Tent_takeDown(objects, count, tent);
        float messy = (1.0f - self->fun) * (1.2f - g_world->aware); /* (left behind: the ones who didn't care) */
        for (int k = 0; k < 3 && g_world->fest.litter < LITTER_MOST + 30; k++)
            if (self->guest && random01(&self->ai) < messy) Site_litter(g_world, vec3_add(Tent_door(tent), vec3(0.4f * (float)k - 0.2f, 0.0f, 0.5f)), self->guest + k * 3, self);
        Object_useUp(tent);
        Ai_say(self, "That's the tent packed. What a weekend!");
        return true;
    }
    Vec3 gate = Guest_gate();
    gate.y = self->position.y;
    self->needs.sleepiness = fminf(self->needs.sleepiness, 0.6f); /* (they'll sleep in the car) */
    if (!ai_goToPoint(self, gate, GATE_REACH, objects, count, dt) && (self->ai.fest_walk += dt) < 400.0f) return true; /* (lost on the way: gone anyway) */
    freeHands(self, NULL);
    self->gone_home = true;
    self->inside_door = Guest_gate();
    self->indoors = 1e7f;
    return true;
}

/* A floor playing: dancing there a while. */
static bool dance(Object *self, Object *const *objects, int count, float dt) {
    Ai *ai = &self->ai;
    const Fest *f = &g_world->fest;
    if (ai->fest_floor && !(f->on & (1u << (ai->fest_floor - 1)))) stopDancing(self); /* (the music stopped) */
    if (ai->fest_time < 0.0f && !ai->fest_floor) { ai->fest_time += dt; return false; }
    bool guest = self->guest > 0;
    if (!guest && !ai->fest_floor && (g_world->hour > 3.0f && g_world->hour < 19.0f)) return false; /* (the crew: evenings and nights) */
    if (self->needs.sleepiness > 0.8f || self->needs.bladder > 0.8f || self->drunk > 0.9f || Body_isSmoking(&self->body)) { stopDancing(self); return false; }
    if (!ai->fest_floor) {
        if (!f->on) return false;
        float pick = random01(ai) * 1.2f, sum = 0.0f; /* (the better floors pull more; each guest a favourite) */
        int fav = guest ? (self->guest * 7) % FEST_FLOORS : self->cast_id % FEST_FLOORS;
        float w[FEST_FLOORS], all = 0.0f;
        for (int k = 0; k < FEST_FLOORS; k++) { w[k] = (f->on & (1u << k)) ? f->good[k] * (k == fav ? 2.0f : 1.0f) : 0.0f; all += w[k]; }
        if (all <= 0.0f) return false;
        for (int k = 0; k < FEST_FLOORS && !ai->fest_floor; k++) { sum += w[k] / all * 1.2f; if (pick <= sum) ai->fest_floor = k + 1; }
        if (!ai->fest_floor) { ai->fest_time = -DANCE_BREAK; return false; } /* (not now) */
        float a = 6.2832f * random01(ai), r = DANCE_SPREAD * sqrtf(random01(ai));
        ai->fest_spot = vec3_add(f->dance[ai->fest_floor - 1], vec3(cosf(a) * r, 0.0f, sinf(a) * r));
        ai->fest_time = 0.0f;
        if (random01(ai) < 0.3f) Ai_say(self, ai->fest_floor == 2 ? "The Obstwiese's going off - come on!" : "I love this one - let's dance!");
    }
    int k = ai->fest_floor - 1;
    self->sitting = false;
    if (flatDistance(self->position, ai->fest_spot) > 0.9f) {
        self->body.dance_goal = 0.0f;
        if (!ai_goToPoint(self, ai->fest_spot, 0.6f, objects, count, dt)) {
            if ((ai->fest_walk += dt) > 150.0f) { stopDancing(self); ai->fest_walk = 0.0f; } /* (can't get there) */
            return true;
        }
    }
    ai->fest_walk = 0.0f;
    stand(self);
    turnTowards(self, vec3_scale(f->front[k], -1.0f), dt);
    self->body.dance_goal = 1.0f;
    if (guest && !self->female && self->drunk > 0.6f && self->harass_t <= 0.0f && !self->ai.harass) { /* (a drunk one, now and then: too close to somebody) */
        float party = (float)((self->guest * 2654435761u) >> 24 & 255u) / 255.0f;
        if (party > 0.75f && random01(ai) < dt / HARASS_EVERY) {
            Object *best = NULL;
            float best_d = 6.0f;
            for (int i = 0; i < count; i++) {
                Object *o = objects[i];
                if (o == self || o->type != OBJ_HUMAN || !o->female || o->guest == Guest_mate(self->guest) || Body_isDown(&o->body)) continue;
                float d = flatDistance(o->position, self->position);
                if (d < best_d) { best_d = d; best = o; }
            }
            if (best) { ai->harass = best; ai->harass_walk = 0.0f; stopDancing(self); return false; }
        }
    }
    self->body.dance_beat = f->clock * f->bpm[k] / 60.0f + 0.03f * (float)(self->guest % 3);
    self->body.dance_style = guest ? self->guest % 4 : self->cast_id % 4;
    if (guest) self->fun = fminf(1.0f, self->fun + FUN_DANCING * f->good[k] * dt);
    if ((ai->fest_time += dt) > DANCE_MIN + DANCE_MORE * (float)((self->guest * 13 + self->cast_id * 7) % 10) / 10.0f) stopDancing(self);
    return true;
}

bool ai_festival(Object *self, Object *const *objects, int count, float dt) {
    if (!g_world) return false;
    const Fest *f = &g_world->fest;
    if (f->stage == FEST_NONE) { self->body.dance_goal = 0.0f; self->ai.fest_floor = 0; return false; }
    if (self->guest) { /* (they brought their own food and drink: the crew's is the crew's) */
        self->needs.hunger = fminf(self->needs.hunger, 0.3f);
        self->needs.thirst = fminf(self->needs.thirst, 0.3f);
        partyDrinks(self);
        if (!self->body.asleep && !self->in_tent && !Body_isDown(&self->body)) litter(self);
        if (!f->on && !self->body.asleep && g_world->hour > 9.0f && f->stage == FEST_OPEN)
            self->fun = fmaxf(0.0f, self->fun - FUN_BORED * dt);
    }
    if (((self->guest && !self->supporter && f->stage == FEST_LEAVING && g_world->hour >= leaveHour(self)) || self->heading_home) && (self->in_tent || self->body.asleep)) {
        if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); } /* (time to go: up, even after a long night) */
        if (self->body.asleep) { self->drunk = fminf(self->drunk, 0.5f); self->needs.sleepiness = fminf(self->needs.sleepiness, 0.6f); Body_getUp(&self->body); }
        return true;
    }
    if (Body_isDown(&self->body) || self->in_tent || self->body.asleep) return false;
    if (storm(self, objects, count, dt)) return true;
    if (f->stage == FEST_WARNED || f->stage == FEST_STORM) { stopDancing(self); return false; } /* (not told: they carry on - the rain, a tent...) */
    AiMode m = self->ai.mode;
    if (m != AI_IDLE && m != AI_WANDER && m != AI_TABLE) { stopDancing(self); return false; } /* (busy with something) */
    if (seeTo(self, objects, count, dt)) return true;
    if ((self->guest || self->heading_home) && leave(self, objects, count, dt)) return true;
    if (self->guest) {
        if (camp(self, objects, count, dt)) return true;
        Object *tent = Tent_of(objects, count, self);
        if (tent && tent->tent_stage < TENT_UP) { stopDancing(self); return false; } /* (the tent first: ai.c pitchTents) */
        if (self->needs.sleepiness > 0.8f && tent && tent->tent_stage >= TENT_UP && flatDistance(tent->position, self->position) > 50.0f) {
            stopDancing(self); /* (sleepy: back to the tent - ai.c goToBed takes it from there) */
            ai_goToPoint(self, Tent_door(tent), 2.0f, objects, count, dt);
            return true;
        }
    }
    if (f->stage != FEST_OPEN) { stopDancing(self); return false; }
    return dance(self, objects, count, dt);
}