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 2.16.0: felling room
- NPCs check where a tree will come down before felling it and pick a clear direction (no houses, walls, cars, tents, furniture, fire); no room anywhere: they leave that tree - Nearly through, anyone in the way gets "Timber!" and steps aside; the feller waits until the way is clear (also for you)
commit_message.txt | 10 +++-- entropie/Makefile | 2 +- entropie/README.md | 3 ++ entropie/STATE.md | 8 +++- entropie/src/people/ai.c | 9 +++- entropie/src/people/ai.h | 3 ++ entropie/src/people/ai_work.c | 99 ++++++++++++++++++++++++++++++++++++++++++- entropie/src/world/object.h | 2 + entropie/src/world/tools.c | 2 + entropie/src/world/tools.h | 2 + 10 files changed, 131 insertions(+), 9 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index 58343e9..05ea22b 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,5 +1,7 @@ -entropie 2.15.0: NPCs hang their pullover up +entropie 2.16.0: felling room -- Warm, with the pullover tied round the waist, tidy people now and then - hang it over a railing, a bench or a free chair nearby (and fetch it - again when it gets cold) +- NPCs check where a tree will come down before felling it and pick a + clear direction (no houses, walls, cars, tents, furniture, fire); no room + anywhere: they leave that tree +- Nearly through, anyone in the way gets "Timber!" and steps aside; the + feller waits until the way is clear (also for you) diff --git a/entropie/Makefile b/entropie/Makefile index 2f29eaf..d5edb5e 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.15.0 +VERSION := 2.16.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 437c0cd..b86e71d 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -453,6 +453,9 @@ Hold the left button to keep working. forgetful wait to be reminded ("Back to the firewood, please") - Helpers (G) understand what the wood is for: they fell along, take the branches off, cut and split, fetching fitting tools. Without an axe they say so +- Felling, they look where the tree will come down first: never onto a house, + wall, car, tent, table or the fire (no room anywhere: they leave it). Nearly + through, anybody in the way hears "Timber!" and steps aside; they wait for you - Using or taking something shows a short line about it Logs weigh what their wood weighs. Click one to hold on, call helpers and diff --git a/entropie/STATE.md b/entropie/STATE.md index 89051f6..309d852 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -247,6 +247,10 @@ Round 2.14.1: Round 2.15: - Pullover put away on their own (`ai.c` hangPullover, idle / wander): warm (chill under 0.1, no rain), tied round the waist, tidy (ai_stackCare 0.35+): now and then (HANG_CHANCE 0.015/s x tidiness) over to the nearest railing (`railing`: a thin OBJ_WALL piece 0.6-1.4 m high), bench or free chair within 30 m, beside its long edge (a chair: behind its back), looking at its top: `Clothes_spot` -> hung there (`Clothes_putDown`), else that one skipped a while. Not again for HANG_AGAIN 240 s (none near: 60 s). Cold again: fetchPullover gets it as before. +Round 2.16: +- Felling room (`ai_work.c`): before an NPC fells a tree (`ai_taskFor` JOB_FELL -> `fellPlan`, once per tree: `fell_plan`, `fell_blocked`), FELL_DIRS 12 ways round it, nearest to the way they'd fell it first; `fallHits` looks down (rays from 14 m) at spots every metre from the stump to its top (`treeTall`), across the trunk near the stump and a crown wide further out: a house, barn, building, wall, car, tent, furniture, fireplace or woodpile hit counts. The first clear way (or the least bad): the feller stands on the other side (it falls away from them). No clear way at all: "No room for that one to come down - I'll leave it" (skipped). +- Nearly through (`Tools_fellDone` over FELL_WARN 0.6): anybody where it'll come down (from the stump to its top + 1.5 m, a crown + 0.6 m wide) is warned ("Timber! Out of the way!", you: "...it's coming down there!"); NPCs step out sideways past the crown (`ai.dodge_to`, `dodge_t` 8 s, first thing in Ai_update); the feller waits (`ai.timber`, counts as fine waiting for the watchdog) till nobody's there. + Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves. Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built). @@ -388,7 +392,7 @@ Most important first. - More gestures and feelings to pick personalities from: the lists in Body / Fidgets and Gestures can grow **People** -- The work place, still to do: other jobs than split / cut (felling room, trash); walking a heavy piece round tight corners +- The work place, still to do: trash jobs; walking a heavy piece round tight corners; the felling room when you fell (a hint where it'll come down) - Ladders, still to do: a holder for a ladder standing free; NPCs taking a ladder somewhere for their own work - Smoking, still to do: paying for a pack (no money in the game yet) - "No shirt no service" policy (decision open) @@ -403,4 +407,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 2.15.0 (build 2bee8b2)` +State of `entropie 2.16.0 (build ceace5d)` diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 92682b7..99b144f 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1379,7 +1379,7 @@ static bool waitingIsFine(const Object *self) { const Ai *ai = &self->ai; return ai->mode == AI_IDLE || ai->mode == AI_HELP || ai->mode == AI_FOLLOW || ai->mode == AI_DRINK || (ai->mode == AI_TABLE && self->sitting) || (ai->mode == AI_APPROACH && ai->ready_to_talk) || ai->talking || self->sitting || self->body.asleep || - ai->on_break > 0.0f || ai->ponder > 0.0f || ai->tool_waiting; + ai->on_break > 0.0f || ai->ponder > 0.0f || ai->tool_waiting || ai->timber; } static int itemCount(const Object *self) { @@ -2202,6 +2202,13 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { self->sitting = false; return; } + if (ai->dodge_t > 0.0f && !self->body.asleep && !self->in_tent && self->indoors <= 0.0f) { /* warned off: out of the way first */ + ai->dodge_t -= dt; + self->sitting = false; + if (!goToPoint(self, ai->dodge_to, 0.4f, objects, count, dt)) return; + ai->dodge_t = 0.0f; + stand(self); + } if (self->on_fire > 0.0f) { /* burning: run, then throw themselves down and roll */ self->sitting = false; if (self->on_fire < dt * 1.5f) Ai_say(self, "Aaah! I'm on fire!"); diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index a1de845..c506779 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -180,6 +180,9 @@ typedef struct { bool sheltering; /* in out of the rain (ai.c shelter) */ bool greet; /* just arrived: over to the fire, saying hello, sitting there till the work starts */ bool drying; /* wet through after the rain: sitting by the fire till dry */ + bool timber; /* felling: waiting for somebody to get out from where it'll come down */ + Vec3 dodge_to; /* warned off (a tree coming down): out to here ... */ + float dodge_t; /* ... for this long */ struct Object *hang_on; /* warm, the pullover round the waist: over this (a railing, a chair) it goes ... */ float hang_wait; /* ... and not thought of again for this long */ bool going_in; /* a resident on the way into the house (ai.c goInside) */ diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index b93a7fe..e2921a6 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -5,12 +5,98 @@ #include "people/ai_internal.h" #include "people/pockets.h" +#include "world/world.h" /* ---------- Wood work ---------- */ #define LIGHTER_ASK 30.0f /* m: they go and borrow a lighter from somebody this near */ #define UNEVEN_CUT 0.2f /* m a piece may come out longer or shorter, cut by someone unpractised */ +/* ---------- Felling room ---------- */ +/* A tree falls away from whoever fells it. Before that: which way is there + room for it (no house, wall, car, tent, table, fire in the way, from the + stump out to its top and a crown wide)? The feller stands on the other side. + Somebody in the way when it's nearly through: "Timber!" and they get out. */ +#define FELL_DIRS 12 /* ways round it looked at */ +#define FELL_STEP 1.0f /* m between the spots looked at along the way it falls */ +#define FELL_WARN 0.6f /* the cut this far through: anybody in the way is warned off */ +#define FELL_CLEAR 1.2f /* m past the crown's side: out of the way */ + +static float treeTall(const Object *tree) { + int top = tree->collider_count > 1 ? 1 : 0; + return Object_colliderCenter(tree, top).y + tree->colliders[top].half_extents.y - tree->position.y; +} + +static float crownR(const Object *tree) { return fmaxf(tree->size.x * 0.5f, 0.6f); } + +static bool notInTheWay(const Object *o, const void *tree) { + if (o == tree || o->is_character || o->type == OBJ_DOG || !o->is_static) return true; + switch (o->type) { /* (what a tree mustn't come down on) */ + case OBJ_HOUSE: case OBJ_BARN: case OBJ_STRUCTURE: case OBJ_WALL: case OBJ_CAR: case OBJ_TENT: + case OBJ_FURNITURE: case OBJ_FIREPLACE: case OBJ_WOODPILE: return false; + default: return true; + } +} + +/* What it would come down on, falling that way (counted). */ +static int fallHits(const Object *tree, Vec3 dir) { + if (!g_world) return 0; + Vec3 side = vec3(-dir.z, 0.0f, dir.x); + float tall = treeTall(tree), r = crownR(tree); + int hits = 0; + for (float along = 1.0f; along <= tall + 0.5f; along += FELL_STEP) + for (int s = -1; s <= 1; s++) { + float w = (along > tall * 0.45f ? r : 0.4f) * (float)s * 0.8f; /* (the trunk narrow, the crown wide) */ + Vec3 p = vec3_add(tree->position, vec3_add(vec3_scale(dir, along), vec3_scale(side, w))); + p.y = tree->position.y + 14.0f; + if (World_rayCast(g_world, p, vec3(0.0f, -1.0f, 0.0f), 14.5f, notInTheWay, tree, NULL, NULL) >= 0.0f) hits++; + } + return hits; +} + +/* Which way it should fall: the clear way nearest to coming down away from + `from` (where the feller is), worked out once. */ +static Vec3 fellPlan(Object *tree, Vec3 from) { + if (vec3_length(tree->fell_plan) > 0.5f) return tree->fell_plan; + Vec3 want = vec3_scale(from, -1.0f), best = want; + int best_hits = 1 << 20; + for (int k = 0; k < FELL_DIRS; k++) { /* (nearest first: 0, +30, -30, +60 ...) */ + float a = (float)((k + 1) / 2) * (2.0f * PI_F / (float)FELL_DIRS) * (k % 2 ? 1.0f : -1.0f); + Vec3 d = vec3(want.x * cosf(a) - want.z * sinf(a), 0.0f, want.x * sinf(a) + want.z * cosf(a)); + int hits = fallHits(tree, d); + if (hits < best_hits) { best_hits = hits; best = d; } + if (hits == 0) break; + } + tree->fell_plan = best; + tree->fell_blocked = best_hits > 0; + return best; +} + +/* Anybody standing where it'll come down: warned off (they step out sideways). */ +static bool clearFellingRoom(Object *self, Object *const *objects, int count, Object *tree) { + Vec3 dir = tree->fell_plan, side = vec3(-dir.z, 0.0f, dir.x); + float tall = treeTall(tree) + 1.5f, r = crownR(tree) + 0.6f; + bool told = false, anybody = false; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o == self || !o->is_character || o->type == OBJ_DOG) continue; + Vec3 rel = flat(vec3_sub(o->position, tree->position)); + float along = vec3_dot(rel, dir), across = vec3_dot(rel, side); + if (along < -0.5f || along > tall || fabsf(across) > r) continue; + anybody = true; + if (!told && self->ai.speech_time <= 0.0f) { + Ai_say(self, o->type == OBJ_PLAYER ? "Timber! Get out of the way - it's coming down there!" : "Timber! Out of the way!"); + told = true; + } + if (o->type == OBJ_HUMAN && o->ai.dodge_t <= 0.0f) { /* (out sideways, past the crown) */ + float out = across < 0.0f ? -1.0f : 1.0f; + o->ai.dodge_to = vec3_add(o->position, vec3_scale(side, out * (r - fabsf(across) + FELL_CLEAR))); + o->ai.dodge_t = 8.0f; + } + } + return anybody; +} + /* Where to stand for a job on o, and what to face. */ WoodTask ai_taskFor(const Object *self, Object *o, Job job) { WoodTask t = { .job = job, .target = o }; @@ -18,6 +104,7 @@ WoodTask ai_taskFor(const Object *self, Object *o, Job job) { Vec3 c = Object_colliderCenter(o, 0); Vec3 from = flat(vec3_sub(self->position, c)); from = vec3_length(from) > 1e-3f ? vec3_normalize(from) : vec3(1.0f, 0.0f, 0.0f); + from = vec3_scale(fellPlan(o, from), -1.0f); /* (on the far side from where it'll fall) */ t.spot = vec3_add(c, vec3_scale(from, o->colliders[0].half_extents.x + STAND_OFF)); t.aim = c; } else if (job == JOB_DELIMB) { @@ -1244,9 +1331,19 @@ void ai_updateWork(Object *self, Object *const *objects, int count, float dt) { if (firewoodInHands(self) > 0 && carryFirewood(self, objects, count, dt)) return; if (branchesInHands(self) > 0 && tidyBranches(self, objects, count, dt)) return; WoodTask t; + ai->timber = false; if (ai_nextWoodTask(self, objects, count, ai->work_area, ai->order == ORDER_TREES, &t)) { if (thinkItOver(self, t.target, dt)) return; - if (t.job == JOB_FELL) ai->work_area = Object_colliderCenter(t.target, 0); + if (t.job == JOB_FELL) { + ai->work_area = Object_colliderCenter(t.target, 0); + if (t.target->fell_blocked) { /* (nowhere for it to come down) */ + if (random01(ai) < 0.7f) Ai_say(self, "No room for that one to come down - I'll leave it."); + skipTarget(ai, t.target); + return; + } + ai->timber = Tools_fellDone(t.target) > FELL_WARN && !Tools_isWorking(self) && clearFellingRoom(self, objects, count, t.target); + if (ai->timber) { stand(self); return; } /* (not the last strokes with somebody under it) */ + } if (prepareSpot(self, objects, count, &t, dt)) return; ai_workOn(self, objects, count, &t, dt); return; diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 8cb0ab2..e57d2f4 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -186,6 +186,8 @@ typedef struct Object { Vec3 mark_at, mark_out; /* trees, logs, stumps: where they're being cut, and outwards there (object space) ... */ float mark; /* ... how far along (0 = no mark) */ Vec3 fall_dir; /* trees: direction the top falls */ + Vec3 fell_plan; /* trees: which way it should come down (room for it there; zero: not looked yet) ... */ + bool fell_blocked; /* ... no room anywhere round it */ float fall_base_y; /* trees: where it pivots falling over (the cut) ... */ Vec3 fall_origin; /* ... from here ... */ float fall_ground_y; /* ... and the ground it comes down on at the end (its butt slides off the stump) */ diff --git a/entropie/src/world/tools.c b/entropie/src/world/tools.c index a8d234a..67f5f1c 100644 --- a/entropie/src/world/tools.c +++ b/entropie/src/world/tools.c @@ -332,6 +332,8 @@ static void finish(Work *w, float at) { w->done[w->done_count++] = (JobDone){ .job = w->job, .target = w->target, .at = at, .point = w->point }; } +float Tools_fellDone(const Object *tree) { return clampf(tree->work / FELL_WORK, 0.0f, 1.0f); } + static void strike(Work *w) { float amount = workFor(w->tool, w->job) * w->power; /* how well the worker is doing */ if (amount <= 0.0f || !stillThere(w)) return; diff --git a/entropie/src/world/tools.h b/entropie/src/world/tools.h index 6fe1e61..407ae80 100644 --- a/entropie/src/world/tools.h +++ b/entropie/src/world/tools.h @@ -105,6 +105,8 @@ bool Tools_jobNearby(const struct Object *who, struct Object *const *objects, in /* What the next stroke would work on (NULL = nothing). */ struct Object *Tools_target(const struct Object *who, struct Object *const *objects, int count); bool Tools_isWorking(const struct Object *who); +/* How far a tree's felling cut is through (0..1). */ +float Tools_fellDone(const struct Object *tree); /* After Hands_update: holds the tool, moves the arms through the stroke and does the work. */ void Tools_update(struct Object *who, float dt);