#ifndef PEOPLE_AI_INTERNAL_H #define PEOPLE_AI_INTERNAL_H /* * Inside the people's minds (ai.c, ai_work.c, ai_help.c): speeds, distances * and the helpers all three share. Not for use outside the AI. */ #include "people/ai.h" #include "world/camp.h" #include "world/object.h" #include #include #define STROLL_SPEED 1.3f #define WALK_SPEED 2.2f /* (as quick as you walk: they hurried along at 3) */ #define WALK_TURN 5.0f /* rad/s the way someone walks can turn */ #define RUN_SPEED 5.0f #define TURN_RATE 5.0f /* radians per second */ #define BODY_RADIUS 0.35f /* clearance kept from obstacles */ #define PERSON_RADIUS 0.6f /* clearance kept from other people */ #define LOOK_AHEAD 1.4f #define ARRIVE_DISTANCE 0.25f #define BLOCKED_SETTLE 1.5f /* blocked this close to the spot: count as there */ #define SETTLED_UNTIL 0.7f /* once there, off again only when the spot is this far (no shuffling) */ #define STUCK_TIME 1.5f #define WANDER_RADIUS 7.0f #define PAUSE_MIN 3.0f #define PAUSE_MAX 8.0f #define NOTICE_DISTANCE 6.0f /* idle people turn towards the player */ #define FOLLOW_SPACING 1.0f /* beside the leader while walking */ #define RUN_TO_LEADER 3.5f /* further away than this: run */ #define GRAB_TRY_RADIUS 1.2f /* close enough to the spot: try to grab */ #define SPOT_GAP 0.45f /* a helper spot must be this close to the object */ #define APPROACH_SPEED 0.6f #define DRINK_CHANCE 0.15f /* per pause: go and get a beer */ #define DRINK_RANGE 12.0f #define DRINK_MAX_DRUNK 0.45f #define FETCH_TIMEOUT 20.0f #define PICKUP_REACH 0.5f #define DRINK_PAUSE 1.2f /* between sips, and before putting it down */ #define WAKE_AFTER 12.0f /* someone asleep this long gets woken up */ #define WAKE_RANGE 25.0f #define WAKE_REACH 0.75f /* close enough to the sleeper's chest to shake them */ #define SHAKE_TIME 2.5f #define WOOD_RADIUS 10.0f /* wood this close to the work area belongs to the job */ #define TREE_RADIUS 35.0f /* ORDER_TREES: how far to go for the next tree */ #define STAND_OFF 0.55f /* from the wood's surface to where a worker stands */ #define WORK_ARRIVE 0.12f /* close enough to the spot to start working */ #define JOB_PATIENCE 30.0f /* seconds on one piece before trying another */ #define SKIP_TIME 20.0f #define TALK_DISTANCE 1.4f /* walking up to someone to talk */ #define APPROACH_TIMEOUT 25.0f #define ANNOYED_FADE 180.0f /* seconds until fully over being annoyed */ #define CAREFUL_SPEED 3.8f /* running with a chainsaw */ typedef enum { WANT_BEER, WANT_DRINK, WANT_FOOD } Want; typedef struct { Job job; Object *target; Vec3 spot; /* where to stand */ Vec3 aim; /* what to face */ } WoodTask; #define UP vec3(0.0f, 1.0f, 0.0f) void ai_walk(Object *o, Vec3 dir, float speed, float dt); bool ai_blockedBy(const Object *self, const Object *ignore, const Object *ignore2, Object *const *objects, int count, Vec3 p); Vec3 ai_steerPast(Object *self, const Object *ignore, const Object *ignore2, Object *const *objects, int count, Vec3 goal); Vec3 ai_steer(Object *self, const Object *ignore, Object *const *objects, int count, Vec3 goal); Object *ai_findTool(const Object *self, Object *const *objects, int count, Job job); WoodTask ai_taskFor(const Object *self, Object *o, Job job); bool ai_nextWoodTask(Object *self, Object *const *objects, int count, Vec3 area, bool trees, WoodTask *out); void ai_workOn(Object *self, Object *const *objects, int count, const WoodTask *t, float dt); Object *ai_nearestOf(const Object *self, Object *const *objects, int count, ObjectType type, ObjectType dispenses); void ai_updateWork(Object *self, Object *const *objects, int count, float dt); /* Stacking firewood: how neatly (0 = thrown on .. 1 = exactly), and a word when the top comes down. */ float ai_stackCare(const Object *self); /* Hard work in the midday sun: the shirt comes off (not for the shy), on again when it cools down. */ void ai_sunShirt(Object *self, float dt); void ai_pileSlid(Object *self, int slid); void ai_updateFollow(Object *self, Object *const *objects, int count, float dt); bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt); bool ai_putAway(Object *self, Object *const *objects, int count, float dt); void ai_updateHelp(Object *self, Object *const *objects, int count, float dt); /* Holding a ladder steady for somebody on it: helpers for their leader, the helpful on their own when it looks wobbly. True while they're at it. */ bool ai_holdLadder(Object *self, Object *const *objects, int count, float dt); /* Up or down a ladder, or pulling up over its top edge: carries on with it. True while at it. */ bool ai_climbing(Object *self, float dt); /* Following someone up there (a roof) by a leaning ladder, or down again. True while on the way. */ bool ai_followByLadder(Object *self, Object *const *objects, int count, const Object *leader, float dt); extern struct World *g_world; void ai_ensureReady(Object *self); /* Off to a point (ai.c goToPoint): true there. */ bool ai_goToPoint(Object *self, Vec3 p, float reach, Object *const *objects, int count, float dt); /* Their part of the festival (ai_project.c): how into it they are, going with what happens (quitting a lead, or helping at all, when it's gone); and the work on their floor's stage. */ void ai_commitment(Object *self, Object *const *objects, int count, float dt); bool ai_projectWork(Object *self, Object *const *objects, int count, float dt); bool ai_errand(Object *self, Object *const *objects, int count, float dt); /* ai_fest.c: the festival weekend - guests camping, dancing, leaving; the storm; the awareness team. True: that's what they do now. */ bool ai_festival(Object *self, Object *const *objects, int count, float dt); /* ai_jobs.c: the side jobs (PROJECT_MOWING, _SNAILS, _WOOD_ART): one frame of the work. Returns SIDE_*; why: what to say. */ enum { SIDE_HELD = -2, SIDE_FINISHED = -1, SIDE_WORKING = 0, SIDE_STEP = 1 }; int ai_job(Object *self, int project, Object *const *objects, int count, float dt, char *why, size_t size); bool ai_jobDone(int project); /* On the big tree's rope (Object.puller): out to the place, hands on, pulling when it goes. True while on it. */ bool ai_onRope(Object *self, Object *const *objects, int count, float dt); /* ai_touch.c: hand in hand with whoever leads them, stepping in when somebody's bothered, a drunk guest bothering somebody. */ bool ai_touch(Object *self, Object *const *objects, int count, float dt); const char *ai_jobStart(int project); static inline float random01(Ai *ai) { ai->rng = ai->rng * 1664525u + 1013904223u; return (float)(ai->rng >> 8) / 16777216.0f; } static inline Vec3 flat(Vec3 v) { return vec3(v.x, 0.0f, v.z); } static inline float flatDistance(Vec3 a, Vec3 b) { return vec3_length(flat(vec3_sub(a, b))); } static inline Vec3 rotateY(Vec3 v, float angle) { float c = cosf(angle), s = sinf(angle); return vec3(v.x * c - v.z * s, v.y, v.x * s + v.z * c); } static inline void setFacing(Object *o, Vec3 dir) { dir = flat(dir); if (vec3_length(dir) < 1e-4f) return; dir = vec3_normalize(dir); o->rotation.y = atan2f(dir.z, dir.x) + PI_F / 2.0f; Object_syncColliders(o); } /* Turns towards dir at a limited rate. */ static inline void turnTowards(Object *o, Vec3 dir, float dt) { if (o->sitting && (o->seat || o->riding)) return; /* (on a bench, a chair, a log, in the car: they stay sat as they sat - the head turns, see look_dir) */ dir = flat(dir); if (vec3_length(dir) < 1e-4f) return; Vec3 f = Object_facing(o); dir = vec3_normalize(dir); float angle = atan2f(f.x * dir.z - f.z * dir.x, vec3_dot(f, dir)); float step = clampf(angle, -TURN_RATE * dt, TURN_RATE * dt); setFacing(o, rotateY(f, step)); } static inline void stand(Object *o) { o->movement = vec3_zero(); o->move_local = vec3_zero(); } /* Running speed for this person now (tired, drunk, carrying a chainsaw: slower). */ static inline float runSpeed(const Object *o) { float speed = fminf(RUN_SPEED, Object_topSpeed(o) * 0.8f); if (Hands_carries(o, OBJ_CHAINSAW)) speed = fminf(speed, CAREFUL_SPEED); return speed; } static inline bool blockedAt(const Object *self, const Object *ignore, Object *const *objects, int count, Vec3 p) { return ai_blockedBy(self, ignore, NULL, objects, count, p); } /* Tracks progress towards the goal; true once stuck for too long. */ static inline bool isStuck(Ai *ai, float dist, float dt) { if (dist < ai->closest - 0.05f) { ai->closest = dist; ai->stuck = 0.0f; return false; } ai->stuck += dt; return ai->stuck > STUCK_TIME; } static inline void setGoal(Ai *ai, Vec3 goal) { ai->goal = goal; ai->closest = 1e9f; ai->stuck = 0.0f; } static inline Object *findPlayer(Object *const *objects, int count) { for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_PLAYER) return objects[i]; return NULL; } /* A spot by a burning fire, if one burns within reach and there is room. */ static inline bool pickFireSpot(Object *self, Object *const *objects, int count, const Object *fire) { Ai *ai = &self->ai; for (int tries = 0; tries < 6; tries++) { float angle = random01(ai) * 2.0f * PI_F; Vec3 goal = vec3_add(fire->position, vec3(cosf(angle) * FIRE_SIT_DISTANCE, 0.0f, sinf(angle) * FIRE_SIT_DISTANCE)); if (!blockedAt(self, NULL, objects, count, goal)) { setGoal(ai, goal); return true; } } return false; } /* Point of collider i of o closest to p. */ static inline Vec3 closestOn(const Object *o, int i, Vec3 p) { const Collider *c = &o->colliders[i]; Vec3 center = Object_colliderCenter(o, i); Vec3 rel = vec3_sub(p, center); const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z }; Vec3 q = center; for (int k = 0; k < 3; k++) q = vec3_add(q, vec3_scale(c->axes[k], clampf(vec3_dot(rel, c->axes[k]), -half[k], half[k]))); return q; } static inline ToolKind toolOf(const Object *o) { switch (o->type) { case OBJ_AXE: return TOOL_AXE; case OBJ_SHEARS: return TOOL_SHEARS; case OBJ_CHAINSAW: return TOOL_CHAINSAW; default: return TOOL_NONE; } } static inline bool isWoodJob(Job job) { return job == JOB_FELL || job == JOB_DELIMB || job == JOB_CUT || job == JOB_SPLIT; } /* Couldn't get at it a moment ago. */ static inline bool ai_skipped(const Ai *ai, const struct Object *o) { for (int i = 0; i < AI_SKIPS; i++) if (ai->skip[i] == o && ai->skip_time[i] > 0.0f) return true; return false; } /* Somebody else is already on this piece (trees take many hands, a log only one). */ static inline bool claimed(const Object *self, Object *const *objects, int count, const Object *o) { if (ai_skipped(&self->ai, o)) return true; /* couldn't get at it a moment ago */ for (int i = 0; i < count; i++) { const Object *c = objects[i]; if (c == self || !c->is_character) continue; if (c->ai.job_target == o && (c->ai.mode == AI_WORK || c->ai.mode == AI_HELP)) return true; if (c->type == OBJ_PLAYER && c->work_state.last_target == o && c->work_state.since < 3.0f) return true; } return false; } /* Leaves it a while (remembers a few, so they don't go back and forth between two). */ static inline void skipTarget(Ai *ai, Object *o) { int slot = 0; for (int i = 1; i < AI_SKIPS; i++) if (ai->skip[i] == o || ai->skip_time[i] < ai->skip_time[slot]) slot = i; for (int i = 0; i < AI_SKIPS; i++) if (ai->skip[i] == o) slot = i; ai->skip[slot] = o; ai->skip_time[slot] = SKIP_TIME; ai->job_target = NULL; ai->job_time = 0.0f; } /* Short pieces around area that still want splitting. */ static inline bool woodLeft(Object *const *objects, int count, Vec3 area) { for (int i = 0; i < count; i++) if (Tools_splittable(objects[i]) && flatDistance(objects[i]->position, area) < WOOD_RADIUS) return true; return false; } /* Something held that isn't the tool we want: put it down. */ static inline void freeHands(Object *self, const Object *keep) { for (int hand = 0; hand < 2; hand++) if (self->hands.state[hand] == HAND_HOLDING && self->hands.item[hand] != keep) Hands_drop(self, hand); } static inline void approach(Object *self, Topic topic) { Ai *ai = &self->ai; if (ai->mode != AI_APPROACH) ai->before = ai->mode; ai->mode = AI_APPROACH; ai->topic = topic; ai->timer = 0.0f; ai->ready_to_talk = false; ai->closest = 1e9f; ai->stuck = 0.0f; } /* Walks towards something; true once within reach of it. */ static inline bool goTo(Object *self, const Object *target, Object *const *objects, int count, float reach, float dt) { float gap = Hands_gapFrom(self->position, self->look.height, target); if (gap < reach) { stand(self); turnTowards(self, vec3_sub(target->position, self->position), dt); return true; } Vec3 dir = ai_steer(self, target, objects, count, target->position); if (vec3_length(dir) < 0.5f) stand(self); else ai_walk(self, dir, fminf(WALK_SPEED * 0.8f, (gap - reach) * 2.5f + 0.25f), dt); /* (slowing down coming up to it: no stop-go) */ return false; } static inline bool inAnyFire(Object *const *objects, int count, const Object *o) { for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_FIREPLACE && flatDistance(objects[i]->position, o->position) < objects[i]->size.x * 0.5f + 0.2f) return true; return false; } #endif /* PEOPLE_AI_INTERNAL_H */