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_project.c raw
/*
* Their part of the festival (world/cast.h: leads, helps, commitment).
*
* The lead and the helpers of a floor go to its stage in working hours and
* build it, in goes of a couple of hours (then a break, eating, the loo...).
* Held up for bought material that lies somewhere else (by the barn, where the
* shopping gets unloaded), one of them goes and carries it over; logs are
* yours to bring. Off on a shopping run (game_shop.c): over to the car.
* The side jobs (mowing, the snails, Kerry's art) go the same way, their
* work itself in ai_jobs.c.
* How into it they are goes with what happens: a step finished lifts them;
* being held up (no logs for the frame), working on worn out, nobody leading,
* being annoyed wear it down. Too low, a lead gives up leading ("somebody
* else can do it") and stays on as a helper; lower still, they stop helping
* at all. Evenings at the fire bring a bit of it back.
*/
#include <stdio.h>
#include "people/ai_internal.h"
#include "world/stage.h"
#include "world/camp.h"
#define WORK_FROM 8.5f /* o'clock: the working day ... */
#define WORK_TO 19.5f /* ... till */
#define SHIFT 7200.0f /* game s at it, then a break ... */
#define BREAK_TIME 2400.0f /* ... this long */
#define START_CHANCE 0.03f /* a second: idle in working hours, off to it (helpers: less without the lead there) */
#define TIRED 0.3f /* stamina below this: they stop */
#define NEED_STOP 0.65f /* hunger, thirst or bladder over this: they stop */
#define LEAD_NEAR 40.0f /* m: the lead about (helpers keep at it better) */
#define HELD_UP_LEAVE 1500.0f /* game s held up: they give it up for now */
#define REMARK_EVERY 90.0f /* real s between remarks about it */
#define QUIT_LEAD 0.3f /* commitment below this: the lead gives it up ... */
#define QUIT_HELP 0.15f /* ... below this they stop helping at all */
/* commitment, per game hour */
#define LIFT_DONE 0.08f /* (once) a step of it finished, for everybody at it */
#define WEAR_HELD_UP 0.06f
#define WEAR_WORN 0.04f /* working on worn out */
#define WEAR_NO_LEAD 0.03f /* a helper with nobody leading it */
#define WEAR_ANNOYED 0.04f
#define LIFT_EVENING 0.02f /* sitting at the fire of an evening */
#define FETCH_SPAN 150.0f /* m: material lying this far off is fetched for the stage */
#define FETCH_GIVE_UP 240.0f /* real s at fetching one thing: given up */
static bool floorProject(int p) {
return p == PROJECT_HOFBUEHNE || p == PROJECT_OBSTWIESE || p == PROJECT_KARTOFFELACKER || p == PROJECT_HIDDEN;
}
/* Something to go and do on the ground: a floor, or a side job (ai_jobs.c). */
static bool workable(int p) {
return floorProject(p) || p == PROJECT_MOWING || p == PROJECT_SNAILS || p == PROJECT_WOOD_ART || p == PROJECT_BIG_TREE;
}
static int theirProject(const Object *self) {
if (workable(self->leads)) return self->leads;
if (workable(self->helps)) return self->helps;
return PROJECT_NONE;
}
static Object *leadOf(Object *const *objects, int count, int project) {
for (int i = 0; i < count; i++)
if (objects[i]->is_character && objects[i]->leads == project) return objects[i];
return NULL;
}
/* Who speaks for those at it: the lead if there, else the first of them (one voice, not a chorus). */
static bool firstAt(const Object *self, Object *const *objects, int count, int project) {
if (self->leads == project) return true;
for (int i = 0; i < count; i++) {
const Object *o = objects[i];
if (o == self || !o->is_character || !o->ai.on_project || theirProject(o) != project) continue;
if (o->leads == project || o->cast_id < self->cast_id) return false;
}
return true;
}
static void lose(Object *self, float per_hour, float game_dt) {
self->commitment = clampf(self->commitment - per_hour * game_dt / 3600.0f, 0.0f, 1.0f);
}
void ai_commitment(Object *self, Object *const *objects, int count, float dt) {
if (self->cast_id < 0 || !g_world) return;
Ai *ai = &self->ai;
const float gdt = g_world->game_dt;
ai->praised = fmaxf(0.0f, ai->praised - gdt);
if (ai->annoyed > 0.5f) lose(self, WEAR_ANNOYED, gdt);
if (ai->on_project && self->needs.stamina < TIRED + 0.1f) lose(self, WEAR_WORN, gdt);
if (ai->on_project && ai->held_up > 0.0f) lose(self, WEAR_HELD_UP, gdt);
if (ai->on_project && self->leads != theirProject(self) && !leadOf(objects, count, theirProject(self))) lose(self, WEAR_NO_LEAD, gdt);
if (self->sitting && (g_world->hour > 19.0f || g_world->hour < 1.0f) && Camp_fireNear(objects, count, self->position, 5.0f, true))
self->commitment = fminf(1.0f, self->commitment + LIFT_EVENING * gdt / 3600.0f);
(void)dt;
/* gone too low: the lead given up, or helping at all */
if (self->leads != PROJECT_NONE && self->commitment < QUIT_LEAD) {
char line[AI_SPEECH_MAX];
const char *why = ai->held_up > 0.0f ? "Nobody brings what we need." : self->needs.stamina < TIRED + 0.1f ? "I'm wrecked." :
ai->annoyed > 0.5f ? "I've had enough of all this." : "It's not working for me.";
snprintf(line, sizeof line, "%s I'm out - somebody else can lead the %s.", why, Cast_projectName((Project)self->leads));
Ai_say(self, line);
self->helps = self->commitment > QUIT_HELP ? self->leads : PROJECT_NONE;
self->leads = PROJECT_NONE;
} else if (self->helps != PROJECT_NONE && self->leads == PROJECT_NONE && self->commitment < QUIT_HELP) {
Ai_say(self, "Count me out of the work. I'm just here for the party now.");
self->helps = PROJECT_NONE;
ai->on_project = false;
}
}
/* Stop for now (a break, the day's over, held up too long...). */
static bool stop(Object *self, const char *say) {
Ai *ai = &self->ai;
if (ai->on_project && say && self->ai.speech_time <= 0.0f) Ai_say(self, say);
if (ai->on_project) ai->project_off = BREAK_TIME; /* (a break before the next go: only after one) */
if (ai->fetch) { /* (whatever they were bringing: down where they are) */
for (int h = 0; h < 2; h++) if (self->hands.item[h] == ai->fetch) Hands_drop(self, h);
ai->fetch = NULL;
}
ai->on_project = false;
ai->shift = 0.0f;
ai->held_up = 0.0f;
return false;
}
/* Others at work on it with them. */
static bool othersAt(const Object *self, Object *const *objects, int count, int project) {
for (int i = 0; i < count; i++)
if (objects[i] != self && objects[i]->is_character && objects[i]->ai.on_project && theirProject(objects[i]) == project) return true;
return false;
}
/* Lying by a stage (it's for that one). */
static bool atAStage(const Object *o, Object *const *objects, int count) {
for (int i = 0; i < count; i++)
if (objects[i]->type == OBJ_STAGE && flatDistance(objects[i]->position, o->position) < STAGE_LOG_REACH) return true;
return false;
}
static bool fetchedByOther(const Object *self, const Object *it, Object *const *objects, int count) {
for (int i = 0; i < count; i++)
if (objects[i] != self && objects[i]->is_character && objects[i]->ai.fetch == it) return true;
return false;
}
/* The nearest of that material lying about (not at a stage, nobody bringing it), or NULL. */
static Object *materialFor(const Object *self, Object *const *objects, int count, int kind) {
Object *best = NULL;
float best_d = FETCH_SPAN;
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o->type != OBJ_MATERIAL || o->variant != kind || o->is_held || o->broken) continue;
float d = flatDistance(o->position, self->position);
if (d < best_d && !atAStage(o, objects, count) && !fetchedByOther(self, o, objects, count)) { best_d = d; best = o; }
}
return best;
}
static bool holds(const Object *self, const Object *it) {
for (int h = 0; h < 2; h++)
if (self->hands.item[h] == it && (self->hands.state[h] == HAND_HOLDING || self->hands.state[h] == HAND_REACHING)) return true;
return false;
}
/* Bringing material over to the stage: true while at it. */
static bool fetchMaterial(Object *self, Object *stage, Object *const *objects, int count, float dt) {
Ai *ai = &self->ai;
Object *it = ai->fetch;
if (!it) return false;
bool mine = holds(self, it);
if (it->broken || (it->is_held && !mine) || (ai->fetch_time += dt) > FETCH_GIVE_UP) {
for (int h = 0; h < 2; h++) if (mine && self->hands.item[h] == it) Hands_drop(self, h);
ai->fetch = NULL;
return false;
}
if (mine) {
for (int h = 0; h < 2; h++) if (self->hands.item[h] == it && self->hands.state[h] == HAND_REACHING) { stand(self); return true; }
Vec3 front = yaw_front(stage->rotation.y);
Vec3 drop = vec3_add(Stage_workSpot(stage, ai->side < 0.0f ? -0.3f : 0.3f), vec3_scale(front, 1.3f)); /* (out in front, behind those at work) */
if (!ai_goToPoint(self, drop, 0.5f, objects, count, dt)) return true;
for (int h = 0; h < 2; h++) if (self->hands.item[h] == it) Hands_drop(self, h);
ai->fetch = NULL;
if (ai->speech_time <= 0.0f) Ai_say(self, it->variant == MATERIAL_BOARDS ? "Boards are here!" : it->variant == MATERIAL_PAINT ? "Paint's here!" : "Got it - here.");
return true;
}
if (Hands_gapFrom(self->position, self->look.height, it) < PICKUP_REACH) {
stand(self);
turnTowards(self, vec3_sub(it->position, self->position), dt);
freeHands(self, NULL);
if (!Hands_pickUpObject(self, self->main_hand, it) && !Hands_pickUpObject(self, 1 - self->main_hand, it)) ai->fetch = NULL;
return true;
}
ai_goToPoint(self, it->position, 0.3f, objects, count, dt);
return true;
}
/* Off somewhere on purpose: a shopping run (over to the car, the driver's door: game_shop.c gets
them in), showing somebody the way (waiting for them when they fall behind: game_arrival.c). */
#define GUIDE_WAIT 6.0f /* m: the one shown the way further behind than this: wait for them */
bool ai_errand(Object *self, Object *const *objects, int count, float dt) {
Ai *ai = &self->ai;
if (!ai->errand) return false;
if (self->sitting) self->sitting = false; /* (up first) */
self->indoors = 0.0f;
const Object *w = ai->errand_with;
if (w && !ai->errand_there && flatDistance(w->position, self->position) > GUIDE_WAIT) { /* (they're coming? waiting, looking back) */
stand(self);
turnTowards(self, vec3_sub(w->position, self->position), dt);
return true;
}
ai->errand_there = ai_goToPoint(self, ai->errand_to, w ? 1.0f : 0.6f, objects, count, dt);
if (ai->errand_there) {
stand(self);
if (w) turnTowards(self, vec3_sub(w->position, self->position), dt);
}
return true;
}
static const char *doneLine(int project, int step) {
static const char *const ANY[] = { "", "Ground's clear - now the frame.", "The frame stands!", "Deck's in - try it, it holds.",
"Roof's on. Now make it pretty.", "Done! Look at it!" };
if (project == PROJECT_OBSTWIESE && step == BUILD_DONE) return "Done - the trees will glow on this one. Psy heaven!";
if (project == PROJECT_HIDDEN && step == BUILD_ROOF) return "Back wall's up - nobody will see it from the yard. Perfect.";
return ANY[step < 0 ? 0 : step > BUILD_DONE ? BUILD_DONE : step];
}
bool ai_projectWork(Object *self, Object *const *objects, int count, float dt) {
Ai *ai = &self->ai;
if (!g_world || self->cast_id < 0) return false;
const float gdt = g_world->game_dt;
int project = theirProject(self);
bool floor = floorProject(project);
Object *stage = floor ? Stage_of(objects, count, (Project)project) : NULL;
self->carving = false;
self->mowing = 0.0f;
ai->project_off = fmaxf(0.0f, ai->project_off - gdt);
if (!project || (floor && (!stage || stage->build_stage >= BUILD_DONE)) || (!floor && ai_jobDone(project) && self->snails == 0)) return stop(self, NULL); /* (snails still in the bucket: let go first) */
ai->remark = fmaxf(0.0f, ai->remark - dt);
bool lead = self->leads == project;
Object *boss = leadOf(objects, count, project);
bool boss_near = boss && flatDistance(boss->position, stage ? stage->position : self->position) < LEAD_NEAR;
float hour = g_world->hour;
/* not now */
if (hour < WORK_FROM || hour > WORK_TO) return stop(self, lead ? "That's it for today." : NULL);
if (g_world->fest.stage >= FEST_OPEN) return stop(self, NULL); /* (the festival's on: what's built is built) */
if (self->needs.stamina < TIRED) return stop(self, "I need a break.");
if (self->needs.hunger > NEED_STOP || self->needs.thirst > NEED_STOP || self->needs.bladder > NEED_STOP) return stop(self, NULL);
if (self->drunk > 0.5f || self->body.asleep || self->in_tent || self->riding || self->indoors > 0.0f || ai->sheltering || ai->errand) return stop(self, NULL);
if (!ai->on_project) {
if (ai->project_off > 0.0f || ai->greet || ai->unload) return false;
float chance = START_CHANCE * (lead || boss_near ? 1.0f : 0.25f) * (0.5f + self->commitment);
int focus = g_world->focus; /* (what the Herberge said this weekend is for) */
if ((focus == FOCUS_FLOORS && floor) || (focus == FOCUS_SIDE && !floor)) chance *= 1.6f;
else if (focus == FOCUS_PARTY) chance *= 0.5f;
if (random01(ai) >= chance * dt) return false;
ai->on_project = true;
ai->shift = 0.0f;
if (lead && ai->speech_time <= 0.0f) {
char line[AI_SPEECH_MAX];
if (floor) snprintf(line, sizeof line, "Off to the %s - %s. Who's coming?", Cast_projectName((Project)project), Stage_stepName(stage->build_stage));
else snprintf(line, sizeof line, "%s", ai_jobStart(project));
Ai_say(self, line);
}
}
if ((ai->shift += gdt) > SHIFT) return stop(self, lead ? "Break! Back in a bit." : NULL);
if (self->sitting) self->sitting = false; /* (up first) */
if (!floor) { /* a side job */
char why[AI_SPEECH_MAX] = "";
int got = ai_job(self, project, objects, count, dt, why, sizeof why);
if (got == SIDE_HELD) {
ai->held_up += gdt;
if (ai->remark <= 0.0f && firstAt(self, objects, count, project) && why[0]) { Ai_say(self, why); ai->remark = REMARK_EVERY; }
if (ai->held_up > HELD_UP_LEAVE) return stop(self, "Can't get on with it like this. Later.");
return true;
}
ai->held_up = 0.0f;
if (got == SIDE_STEP) {
self->commitment = fminf(1.0f, self->commitment + LIFT_DONE * 0.5f);
if (why[0]) Ai_say(self, why);
}
if (got == SIDE_FINISHED) {
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o->is_character && theirProject(o) == project) o->commitment = fminf(1.0f, o->commitment + LIFT_DONE);
}
return stop(self, why[0] ? why : NULL);
}
return true;
}
if (fetchMaterial(self, stage, objects, count, dt)) return true;
freeHands(self, NULL);
float side = ai->side != 0.0f ? ai->side * 0.8f : (lead ? 0.0f : 0.6f);
Vec3 spot = Stage_workSpot(stage, side);
if (!ai_goToPoint(self, spot, 0.4f, objects, count, dt)) return true;
Vec3 hands = Stage_handsAt(stage, spot, self->body.clock);
turnTowards(self, vec3_sub(stage->position, self->position), dt);
Hands_touch(self, hands);
/* the work: quicker the handier they are, and with the lead about */
float handy = 0.5f * (self->skill[ORDER_FIREWOOD] + self->skill[ORDER_TREES]);
float rate = (0.6f + 0.8f * handy) * self->tempo * (lead ? 1.1f : boss_near ? 1.0f : 0.6f) * (1.0f - 0.5f * fminf(self->drunk, 1.0f));
int got = Stage_work(g_world, stage, rate * gdt / 3600.0f);
if (got == -2) { /* held up: material missing */
int kind = Stage_missingMaterial(g_world, stage);
Object *there = kind >= 0 ? materialFor(self, objects, count, kind) : NULL;
if (there && (!lead || !othersAt(self, objects, count, project))) { /* (lying somewhere else: fetched) */
ai->fetch = there;
ai->fetch_time = 0.0f;
if (ai->speech_time <= 0.0f) Ai_say(self, kind == MATERIAL_BOARDS ? "I'll get the boards." : "I'll fetch it.");
return true;
}
if (kind >= 0) { /* (somebody's on the way with it: wait for it) */
for (int i = 0; i < count; i++)
if (objects[i]->is_character && objects[i]->ai.fetch && objects[i]->ai.fetch->variant == kind && theirProject(objects[i]) == project) return true;
}
ai->held_up += gdt;
if (ai->remark <= 0.0f && firstAt(self, objects, count, project)) {
char line[AI_SPEECH_MAX], what[40];
Stage_missing(g_world, stage, what, sizeof what);
if (stage->build_stage == BUILD_FRAME) snprintf(line, sizeof line, "We need %s here for the frame!", what);
else snprintf(line, sizeof line, "We need %s - somebody go shopping!", what);
Ai_say(self, line);
ai->remark = REMARK_EVERY;
}
if (ai->held_up > HELD_UP_LEAVE)
return stop(self, stage->build_stage == BUILD_FRAME ? "No logs, no frame. I'm off till there are some." : "Nothing to build with. I'm off till the shopping's here.");
return true;
}
ai->held_up = 0.0f;
if (got > 0) { /* a step done: everybody at it is lifted */
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o->is_character && o->ai.on_project && theirProject(o) == project) o->commitment = fminf(1.0f, o->commitment + LIFT_DONE);
}
if (firstAt(self, objects, count, project)) Ai_say(self, doneLine(project, got));
}
return true;
}