Nimbin[12]?Games / Entropie / entropie/src/people/ai_internal.h

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

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.gz
entropie/src/people/ai_internal.h 13.6 KB · 311 lines 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 */