Entropie git · main
Entropie festival game; first steps
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.gzentropie/src/people/ai_fest.c 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);
}