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_jobs.c raw
/*
* The side jobs, one frame of the work at a time (ai_project.c decides when
* they're at it: working hours, breaks, how into it they are):
*
* - Mowing the Kartoffelacker (Toni): the scythe fetched, then cell by cell
* along the field, a swing at a time (world/field.c). A snail in the grass
* there is dead.
* - The snail rescue (Doro, Stella): from snail to snail, bending down for
* each, into the bucket; full (or none left near), over to the refuge by
* the wood and let go.
* - Kerry's art: a log lying at the art spot and the chainsaw - he carves it
* into a figure; without either he says what he needs.
*/
#include <stdio.h>
#include "people/ai_internal.h"
#include "world/field.h"
#include "world/site.h"
#include "world/stage.h"
#include "world/bigtree.h"
#define MOW_REACH 1.2f /* m from the cell: mowing it from here */
#define MOW_SWING 1.5f /* real s a swing of the scythe (quicker for the quick) */
#define MOW_RADIUS 0.8f /* m: what a swing takes */
#define MOW_CAREFUL 0.45f /* how careful (ai_stackCare): the snails out of the field before they mow */
#define SNAIL_LOOK 30.0f /* m: snails this near are gone for (none: to the refuge with what's in the bucket) */
#define SNAIL_BEND 1.2f /* s bending down for one */
#define REFUGE_NEAR 1.8f /* m: at the refuge */
#define CARVE_SIDE 0.8f /* m out from the log's middle: where Kerry stands */
/* Something of that kind in their hands. */
static Object *heldOf(const Object *self, ObjectType type) {
for (int h = 0; h < 2; h++) {
Object *it = self->hands.item[h];
if (it && it->type == type && self->hands.state[h] == HAND_HOLDING) return it;
}
return NULL;
}
static bool takenBy(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 a kind lying about (not held, nobody else after it). */
static Object *nearestFree(const Object *self, Object *const *objects, int count, ObjectType type, int variant, float within) {
Object *best = NULL;
float best_d = within;
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o->type != type || o->is_held || o->broken || (variant >= 0 && o->variant != variant)) continue;
float d = flatDistance(o->position, self->position);
if (d < best_d && !takenBy(self, o, objects, count)) { best_d = d; best = o; }
}
return best;
}
/* Over to it and up it comes (true: in the hands). */
static bool take(Object *self, Object *it, Object *const *objects, int count, float dt) {
for (int h = 0; h < 2; h++) if (self->hands.item[h] == it) return self->hands.state[h] == HAND_HOLDING;
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)) self->ai.fetch = NULL;
return false;
}
ai_goToPoint(self, it->position, 0.3f, objects, count, dt);
return false;
}
static float handy(const Object *self) {
return (0.6f + 0.8f * 0.5f * (self->skill[ORDER_FIREWOOD] + self->skill[ORDER_TREES])) * self->tempo * (1.0f - 0.5f * fminf(self->drunk, 1.0f));
}
/* ---------- The scythe ---------- */
static int mow(Object *self, Object *const *objects, int count, float dt, char *why, size_t size) {
Ai *ai = &self->ai;
if (Field_done(g_world) >= 1.0f) { snprintf(why, size, "The Kartoffelacker's mown! Looks like a festival ground now."); return SIDE_FINISHED; }
if (ai_stackCare(self) >= MOW_CAREFUL && Field_snailsInField(g_world) > 0 && !g_world->inspected) { /* (the careful: the snails out first) */
snprintf(why, size, "I'll wait with the mowing till the snails are out of the field - %d still in the grass.", Field_snailsInField(g_world));
return SIDE_HELD;
}
Object *scythe = heldOf(self, OBJ_SCYTHE);
if (!scythe) { /* the scythe first */
Object *s = ai->fetch && ai->fetch->type == OBJ_SCYTHE && !ai->fetch->is_held ? ai->fetch : nearestFree(self, objects, count, OBJ_SCYTHE, -1, 400.0f);
if (!s) { snprintf(why, size, "Who's got the scythe? I can't mow without it."); return SIDE_HELD; }
if (ai->fetch != s && ai->speech_time <= 0.0f) Ai_say(self, "Scythe first - it's in the tool room.");
ai->fetch = s;
take(self, s, objects, count, dt);
return SIDE_WORKING;
}
if (ai->fetch == scythe) ai->fetch = NULL;
Vec3 cell;
if (!Field_nextUnmown(g_world, self->position, &cell)) return SIDE_FINISHED;
if (flatDistance(cell, self->position) > MOW_REACH) {
ai_goToPoint(self, cell, MOW_REACH * 0.8f, objects, count, dt);
return SIDE_WORKING;
}
stand(self);
turnTowards(self, vec3_sub(cell, self->position), dt);
float swing = MOW_SWING / fmaxf(self->tempo, 0.5f);
ai->fetch_time += dt;
self->mowing = 0.001f + 3.1416f * ai->fetch_time / swing; /* (the blade across, right to left) */
if (ai->fetch_time < swing) return SIDE_WORKING;
ai->fetch_time = 0.0f;
float before = Field_done(g_world);
int killed = 0;
Field_mow(g_world, vec3_add(self->position, vec3_scale(Object_facing(self), 0.9f)), MOW_RADIUS, &killed);
World_noise(g_world, NOISE_RUSTLE, self->position, 0.4f);
float after = Field_done(g_world);
if (killed) { snprintf(why, size, "Oh no - I got a snail with the scythe! They should be out of here first."); return SIDE_STEP; }
if ((int)(after * 4.0f) > (int)(before * 4.0f) && after < 1.0f) {
snprintf(why, size, "%d%% of the Kartoffelacker mown.", (int)(after * 4.0f) * 25);
return SIDE_STEP;
}
return SIDE_WORKING;
}
/* ---------- The snails ---------- */
static int snails(Object *self, Object *const *objects, int count, float dt, char *why, size_t size) {
Ai *ai = &self->ai;
Object *sn = ai->fetch && ai->fetch->type == OBJ_SNAIL && !ai->fetch->broken ? ai->fetch : nearestFree(self, objects, count, OBJ_SNAIL, 0, SNAIL_LOOK);
if (self->snails >= SNAIL_BUCKET || (self->snails > 0 && !sn)) { /* the bucket over to the refuge */
ai->fetch = NULL;
Vec3 refuge = Site_snailRefuge();
if (!ai_goToPoint(self, refuge, REFUGE_NEAR, objects, count, dt)) return SIDE_WORKING;
int n = Field_release(g_world, self);
snprintf(why, size, "There you go, %d of you - safe here.", n);
return SIDE_STEP;
}
if (!sn) { /* none near: further off, or all done */
sn = nearestFree(self, objects, count, OBJ_SNAIL, 0, 1000.0f);
if (!sn) { snprintf(why, size, "That's all the snails out. The office can come!"); return SIDE_FINISHED; }
}
if (ai->fetch != sn) ai->fetch_time = 0.0f;
ai->fetch = sn;
if (flatDistance(sn->position, self->position) > 0.55f) {
ai_goToPoint(self, sn->position, 0.45f, objects, count, dt);
return SIDE_WORKING;
}
stand(self);
turnTowards(self, vec3_sub(sn->position, self->position), dt);
self->body.crouch_goal = fmaxf(self->body.crouch_goal, 0.85f); /* (down to it) */
Hands_touch(self, vec3_add(sn->position, vec3(0.0f, 0.04f, 0.0f)));
if ((ai->fetch_time += dt) < SNAIL_BEND) return SIDE_WORKING;
Field_pickSnail(g_world, self, sn);
ai->fetch = NULL;
ai->fetch_time = 0.0f;
return SIDE_WORKING;
}
/* ---------- Kerry's art ---------- */
static int carve(Object *self, Object *const *objects, int count, float dt, char *why, size_t size) {
Ai *ai = &self->ai;
Object *log = Field_artLog(g_world);
if (!log) { snprintf(why, size, "Bring me a log to the yard by the pavilions - I'll make something of it!"); return SIDE_HELD; }
Object *saw = heldOf(self, OBJ_CHAINSAW);
if (!saw) {
Object *c = ai->fetch && ai->fetch->type == OBJ_CHAINSAW && !ai->fetch->is_held ? ai->fetch : nearestFree(self, objects, count, OBJ_CHAINSAW, -1, 400.0f);
if (!c) { snprintf(why, size, "Somebody's got the chainsaw - I need it for the art."); return SIDE_HELD; }
ai->fetch = c;
take(self, c, objects, count, dt);
return SIDE_WORKING;
}
if (ai->fetch == saw) ai->fetch = NULL;
Vec3 c = Object_colliderCenter(log, 0), along = log->colliders[0].axes[1];
Vec3 across = vec3_normalize(vec3(-along.z, 0.0f, along.x));
if (vec3_dot(vec3_sub(self->position, c), across) < 0.0f) across = vec3_scale(across, -1.0f);
Vec3 spot = vec3_add(c, vec3_scale(across, CARVE_SIDE));
if (!ai_goToPoint(self, spot, 0.35f, objects, count, dt)) return SIDE_WORKING;
stand(self);
turnTowards(self, vec3_sub(c, self->position), dt);
float t = self->body.clock;
Hands_touch(self, vec3_add(c, vec3(0.15f * sinf(t * 1.3f), 0.25f + 0.1f * sinf(t * 0.7f), 0.1f * cosf(t * 1.1f))));
self->carving = true;
log->build_work += handy(self) * g_world->game_dt / 3600.0f;
if (log->build_work < CARVE_NEED) return SIDE_WORKING;
self->carving = false;
Field_carve(g_world, log);
snprintf(why, size, "Done! What do you think? Another log and I'll do another one.");
return SIDE_STEP;
}
/* ---------- The big old tree (Enrico) ---------- */
#define ROPE_WAIT 5400.0f /* game s waiting for somebody to throw the line up: then he does it himself (not as high) */
#define CLEAR_WAIT 45.0f /* real s shouting at people in the way before he cuts anyway */
#define FALL_ZONE 24.0f /* m round the tree on the side it falls to (and some of the rest): nobody but the pullers */
static void sayEvery(Object *self, const char *line) {
if (self->ai.remark > 0.0f || self->ai.speech_time > 0.0f) return;
Ai_say(self, line);
self->ai.remark = 60.0f;
}
/* Somebody standing where it could come down (not the pullers out at the rope's end). */
static Object *inTheWay(const Object *self, Object *const *objects, int count) {
Vec3 t = Site_bigTree(), safe = Site_bigTreeSafe();
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o == self || !o->is_character || o->type == OBJ_DOG || o->puller > 0.0f || o->indoors > 0.0f || o->riding) continue;
Vec3 d = vec3_sub(o->position, t);
d.y = 0.0f;
float dist = vec3_length(d), ahead = vec3_dot(d, safe);
if (dist < 6.0f || (dist < FALL_ZONE && ahead > -4.0f)) return o;
}
return NULL;
}
static int bigTree(Object *self, Object *const *objects, int count, float dt, char *why, size_t size) {
Ai *ai = &self->ai;
BigTree *bt = &g_world->bigtree;
Object *tree = BigTree_tree(g_world);
if (!tree || bt->step >= BT_FALLING) { snprintf(why, size, "%s", bt->lines_down ? "Down. And the line with it... sorry, Kurti." : "Down it is - and the line's fine!"); return SIDE_FINISHED; }
Vec3 t = Site_bigTree();
self->carving = false;
switch (bt->step) {
case BT_STANDING: { /* first a good look at it, the throw line put down at its foot */
if (!ai_goToPoint(self, BigTree_notchSpot(g_world), 1.0f, objects, count, dt)) return SIDE_WORKING;
Vec3 at = vec3_add(BigTree_notchSpot(g_world), vec3_scale(Site_bigTreeSafe(), 1.5f));
at.y = World_groundHeight(g_world, at.x, at.z, at.y + 2.0f) + 0.02f;
Object *bag = Object_create(OBJ_ROPE, PALETTE_WARM, at, vec3_zero(), 0.0f);
if (bag && !World_add(g_world, bag)) bag = NULL;
bt->step = BT_ROPE;
snprintf(why, size, "It leans to the road - the phone line! A rope up high, then we pull it the other way.");
return SIDE_STEP;
}
case BT_ROPE: /* the rope up over a branch: you (the throw line lies at its foot), or after a while he does it */
if (bt->rope_h > 0.0f) { bt->step = BT_NOTCH; bt->work = 0.0f; return SIDE_WORKING; }
if (!ai_goToPoint(self, vec3_add(BigTree_notchSpot(g_world), vec3_scale(Site_bigTreeSafe(), 3.0f)), 1.0f, objects, count, dt)) return SIDE_WORKING;
stand(self);
turnTowards(self, vec3_sub(t, self->position), dt);
self->body.look_dir = vec3_normalize(vec3_add(vec3_sub(t, self->position), vec3(0.0f, 12.0f, 0.0f)));
self->body.look_weight = 0.8f;
if ((bt->rope_wait += g_world->game_dt) < ROPE_WAIT) {
sayEvery(self, "Who throws the line up over a branch? As high as you can - the bag's at the foot.");
return SIDE_WORKING;
}
for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_ROPE && !objects[i]->broken) Object_useUp(objects[i]);
bt->rope_h = 6.0f + 1.5f * random01(ai);
snprintf(why, size, "Right, I'll do it myself... %.1f m. Not great - but it'll have to do.", bt->rope_h);
return SIDE_STEP;
case BT_NOTCH: { /* the chainsaw, the notch on the side it should fall to */
Object *saw = heldOf(self, OBJ_CHAINSAW);
if (!saw) {
Object *c = ai->fetch && ai->fetch->type == OBJ_CHAINSAW && !ai->fetch->is_held ? ai->fetch : nearestFree(self, objects, count, OBJ_CHAINSAW, -1, 400.0f);
if (!c) { snprintf(why, size, "Who's got the chainsaw? I need it for the big tree."); return SIDE_HELD; }
ai->fetch = c;
take(self, c, objects, count, dt);
return SIDE_WORKING;
}
if (ai->fetch == saw) ai->fetch = NULL;
if (!ai_goToPoint(self, BigTree_notchSpot(g_world), 0.35f, objects, count, dt)) return SIDE_WORKING;
stand(self);
turnTowards(self, vec3_sub(t, self->position), dt);
Hands_touch(self, vec3_add(vec3_lerp(t, self->position, 0.45f), vec3(0.0f, 0.55f, 0.0f)));
self->carving = true;
self->body.crouch_goal = fmaxf(self->body.crouch_goal, 0.45f);
if ((bt->work += handy(self) * g_world->game_dt / 3600.0f) < BT_NOTCH_WORK) return SIDE_WORKING;
bt->step = BT_PULL;
bt->work = 0.0f;
snprintf(why, size, "Notch is in! Now - everybody on the rope, out at the far end. Three at least!");
return SIDE_STEP;
}
case BT_PULL: case BT_CUT: { /* the back cut, once there are enough on the rope and the way's clear */
if (!heldOf(self, OBJ_CHAINSAW)) { bt->step = BT_NOTCH; return SIDE_WORKING; } /* (somebody took it: back for it) */
if (!ai_goToPoint(self, BigTree_backSpot(g_world), 0.35f, objects, count, dt)) return SIDE_WORKING;
stand(self);
turnTowards(self, vec3_sub(t, self->position), dt);
if (bt->step == BT_PULL) {
if (bt->pullers < 3) { sayEvery(self, "I need three on the rope - at least! Out at the end of it."); return SIDE_WORKING; }
Object *way = inTheWay(self, objects, count);
if (way && (ai->fetch_time += dt) < CLEAR_WAIT) {
char line[AI_SPEECH_MAX];
snprintf(line, sizeof line, "%s! Out of there - that's where it comes down!", way->type == OBJ_PLAYER ? "Hey, you" : way->name);
if (ai->speech_time <= 0.0f) Ai_say(self, line);
return SIDE_WORKING;
}
ai->fetch_time = 0.0f;
bt->step = BT_CUT;
bt->work = 0.0f;
Ai_say(self, "Back cut! On the rope - get ready...");
}
Hands_touch(self, vec3_add(vec3_lerp(t, self->position, 0.45f), vec3(0.0f, 0.6f, 0.0f)));
self->carving = true;
float was = bt->work;
bt->work += dt;
if (was < BT_CUT_GOES && bt->work >= BT_CUT_GOES) Ai_say(self, "It's going - PULL! PULL!");
if (bt->work < BT_CUT_TIME) return SIDE_WORKING;
self->carving = false;
bt->step = BT_FALLING; /* (game_bigtree.c: which way, down it comes) */
return SIDE_WORKING;
}
default: return SIDE_FINISHED;
}
}
/* On the rope (game_bigtree.c picks them: puller = their place + 1): out to it, hands on, and when it goes: pull. */
bool ai_onRope(Object *self, Object *const *objects, int count, float dt) {
if (self->puller <= 0.0f || !g_world) return false;
BigTree *bt = &g_world->bigtree;
if (bt->step < BT_PULL || bt->step > BT_FALLING || Body_isBusy(&self->body)) { self->puller = 0.0f; return false; }
if (self->sitting) self->sitting = false;
for (int h = 0; h < 2; h++) if (self->hands.item[h]) Hands_drop(self, h); /* (both hands on it) */
Vec3 spot = BigTree_pullerSpot(g_world, (int)self->puller - 1);
if (!ai_goToPoint(self, spot, 0.35f, objects, count, dt)) return true;
stand(self);
Vec3 t = Site_bigTree();
turnTowards(self, vec3_sub(t, self->position), dt);
Vec3 at = BigTree_ropeAt(g_world, self->position);
self->hands.rope = bt->step == BT_CUT && bt->work >= BT_CUT_GOES - 3.0f ? 1.0f : 0.05f;
self->hands.rope_at = at;
self->hands.rope_dir = vec3_normalize(vec3_sub(BigTree_ropeEnd(g_world), BigTree_attach(g_world)));
if (bt->step == BT_CUT && bt->work >= BT_CUT_GOES && self->ai.speech_time <= 0.0f && random01(&self->ai) < 0.5f * dt)
Ai_say(self, random01(&self->ai) < 0.5f ? "Hnnngh!" : "Pull!");
(void)objects; (void)count;
return true;
}
int ai_job(Object *self, int project, Object *const *objects, int count, float dt, char *why, size_t size) {
switch (project) {
case PROJECT_MOWING: return mow(self, objects, count, dt, why, size);
case PROJECT_SNAILS: return snails(self, objects, count, dt, why, size);
case PROJECT_WOOD_ART: return carve(self, objects, count, dt, why, size);
case PROJECT_BIG_TREE: return self->leads == PROJECT_BIG_TREE ? bigTree(self, objects, count, dt, why, size) : SIDE_FINISHED; /* (helpers: on the rope when it's time) */
default: return SIDE_FINISHED;
}
}
bool ai_jobDone(int project) {
if (!g_world) return true;
switch (project) {
case PROJECT_MOWING: return Field_done(g_world) >= 1.0f;
case PROJECT_SNAILS: return Field_snailsLeft(g_world) == 0 || g_world->inspected;
case PROJECT_BIG_TREE: return g_world->bigtree.step >= BT_FALLING;
default: return false; /* (Kerry: as long as there are logs) */
}
}
const char *ai_jobStart(int project) {
switch (project) {
case PROJECT_MOWING: return "Right - the Kartoffelacker. Where's my scythe?";
case PROJECT_SNAILS: return "Snail time! Gently, everybody - they're protected.";
case PROJECT_WOOD_ART: return "Time to make some art. Chainsaw, log, go!";
case PROJECT_BIG_TREE: return "Right - the big tree. Let's have a look at it.";
default: return "Off to work.";
}
}