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_internal.h raw
#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 <stdio.h>
#include <string.h>
#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 */