/* * 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 #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."; } }