Nimbin[12]?Games / Entropie / commits / 8c07648

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (17 hours ago) · synced 8 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 2.15.0: NPCs hang their pullover up

Christian Immanuel · 2026-10-20 15:23 · 8c0764892be833153e541e600640d9fcbdf52ee5

- 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)
 commit_message.txt       |  7 +++--
 entropie/Makefile        |  2 +-
 entropie/README.md       |  4 ++-
 entropie/STATE.md        |  7 +++--
 entropie/src/people/ai.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++
 entropie/src/people/ai.h |  2 ++
 6 files changed, 86 insertions(+), 7 deletions(-)

diff --git a/commit_message.txt b/commit_message.txt
index e24e125..58343e9 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,4 +1,5 @@
-entropie 2.14.1: main building ridge fix
+entropie 2.15.0: NPCs hang their pullover up
 
-- The left roof's ridge cap stuck out about 4 m past the left gable: the
-  ridge piece running into the right half's roof was turned the wrong way
+- 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)
diff --git a/entropie/Makefile b/entropie/Makefile
index 9142441..2f29eaf 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.14.1
+VERSION   := 2.15.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index e375a77..437c0cd 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -591,7 +591,9 @@ say no more often and don't come to you for a bit.
   put it down where you look: over a chair's back, a railing or a bench, on
   a table, or spread on the ground. Sitting on the ground: spread it out and
   sit on it. Click it to take it back. Others hang theirs over their chair or
-  sit on it, take it along when they get up, and fetch it when it gets cold
+  sit on it, take it along when they get up, and fetch it when it gets cold.
+  The tidy ones, warm with it round the waist, hang it over a railing, a
+  bench or a free chair near by
 - Ask them what they're doing, send them back to helping you, or give them a break
 
 ## Smoking
diff --git a/entropie/STATE.md b/entropie/STATE.md
index f3376c5..89051f6 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -244,6 +244,9 @@ Round 2.14:
 Round 2.14.1:
 - The main building's left roof: its ridge cap stuck out about 4 m past the left gable ("the roof peak of the left building is too far left"). The bit of ridge running on into the right half's roof (`Shapes_structure`, roofs with `valley`) was turned with rotate_z(-90 deg), which sends the tube to -X: now +90 deg, it goes on into the roof across.
 
+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.
+
 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).
@@ -389,7 +392,7 @@ Most important first.
 - 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)
-- Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers); the pullover over a railing NPCs never do on their own
+- Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers)
 - Tents, still to do: NPCs taking theirs down on their own (leaving)
 - Weather, still to do: a sound for thunder, wind and rain (no sound in the game yet); a hat or anything held blown out of the hands
 
@@ -400,4 +403,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.14.1 (build 11276d9)`
+State of `entropie 2.15.0 (build 2bee8b2)`
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index d959418..92682b7 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1832,6 +1832,75 @@ static void workOnTent(Object *self, Object *tent, Object *const *objects, int c
     }
 }
 
+/* Warm, the pullover tied round the waist: the tidier now and then hang it
+   over a railing or a chair back near by instead (and fetch it when cold:
+   fetchPullover). */
+#define HANG_LOOK    30.0f  /* m: a railing, a bench or a chair this near */
+#define HANG_CHANCE  0.015f /* a second (x tidiness), idle or wandering */
+#define HANG_AGAIN   240.0f /* s: tried (or done): not again for this long */
+#define HANG_TIDY    0.35f  /* this careful (ai_stackCare) at least */
+
+/* A railing: a thin wall piece at about hip to chest height. */
+static bool railing(const Object *o, float feet) {
+    if (o->type != OBJ_WALL || o->collider_count < 1 || o->broken) return false;
+    Vec3 h = o->colliders[0].half_extents;
+    float thin = fminf(h.x, h.z), top = Object_colliderCenter(o, 0).y + h.y - feet;
+    return thin <= 0.08f && fmaxf(h.x, h.z) > 0.3f && top > 0.6f && top < 1.4f;
+}
+
+static bool hangPullover(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    ai->hang_wait = fmaxf(0.0f, ai->hang_wait - dt);
+    Object *on = ai->hang_on;
+    if (!on) {
+        if (!self->look.jacket_tied || self->pullover_down || self->chill > 0.1f || ai->hang_wait > 0.0f || !g_world || g_world->rain > 0.05f ||
+            ai->want_jacket || self->sitting || ai_stackCare(self) < HANG_TIDY || random01(ai) > HANG_CHANCE * ai_stackCare(self) * dt) return false;
+        ai->hang_wait = HANG_AGAIN;
+        float best = HANG_LOOK;
+        for (int i = 0; i < count; i++) {
+            Object *o = objects[i];
+            bool seat = o->type == OBJ_FURNITURE && (o->variant == FURN_CHAIR || o->variant == FURN_BENCH) && !o->broken && o->collider_count;
+            if ((!seat && !railing(o, self->position.y)) || ai_skipped(ai, o)) continue;
+            if (seat && o->variant == FURN_CHAIR && chairTaken(self, o, objects, count)) continue;
+            float d = flatDistance(Object_colliderCenter(o, 0), self->position);
+            if (d < best && fabsf(o->position.y - self->position.y) < 1.5f) { best = d; on = o; }
+        }
+        if (!on) { ai->hang_wait = HANG_AGAIN * 0.25f; return false; }
+        ai->hang_on = on;
+    }
+    if (!self->look.jacket_tied || self->pullover_down) { ai->hang_on = NULL; return false; }
+    /* beside it: a step off its middle (a railing: off its long side, our side; a chair: behind its back) */
+    Vec3 c = Object_colliderCenter(on, 0), spot, aim;
+    if (on->type == OBJ_WALL || on->variant == FURN_BENCH) { /* (a railing, a bench: over its long edge) */
+        Vec3 h = on->colliders[0].half_extents;
+        Vec3 across = h.x < h.z ? on->colliders[0].axes[0] : on->colliders[0].axes[2];
+        Vec3 along  = h.x < h.z ? on->colliders[0].axes[2] : on->colliders[0].axes[0];
+        Vec3 to = vec3_sub(self->position, c);
+        if (vec3_dot(to, across) < 0.0f) across = vec3_scale(across, -1.0f);
+        float len = fmaxf(h.x, h.z), run = clampf(vec3_dot(to, along), -len + 0.3f, len - 0.3f);
+        Vec3 on_it = vec3_add(c, vec3_scale(along, run));
+        spot = vec3_add(on_it, vec3_scale(across, 0.5f));
+        aim = vec3(on_it.x, c.y + h.y - 0.05f, on_it.z);
+    } else {
+        spot = vec3_add(on->position, mat4_mul_dir(mat4_rotate_y(on->rotation.y), vec3(0.0f, 0.0f, -on->size.z * 0.5f - 0.45f)));
+        aim = vec3_add(on->position, vec3(0.0f, on->size.y, 0.0f));
+    }
+    spot.y = self->position.y;
+    if (!goToPoint(self, spot, 0.3f, objects, count, dt)) return true;
+    Vec3 eye = vec3_add(self->position, vec3(0.0f, self->look.height * 0.92f, 0.0f));
+    Vec3 look = vec3_normalize(vec3_sub(aim, eye));
+    turnTowards(self, vec3(look.x, 0.0f, look.z), dt);
+    ClothSpot s;
+    if (Clothes_spot(g_world, self, look, &s) && s.lay == CLOTH_HUNG && Clothes_putDown(g_world, self, &s)) {
+        if (random01(ai) < 0.4f) Ai_say(self, "Too warm for this - it can hang here.");
+    } else {
+        skipTarget(ai, on);
+    }
+    ai->hang_on = NULL;
+    rest(ai, 1.0f, 2.0f);
+    return true;
+}
+
 /* Their tent blowing away: after it (whatever they were doing), and hold it down. */
 static bool chaseTent(Object *self, Object *const *objects, int count, float dt) {
     Ai *ai = &self->ai;
@@ -2192,6 +2261,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && pitchTents(self, objects, count, dt)) return;
     /* A finished cigarette in the fingers: to the tin, a bin, the fire */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && binButt(self, objects, count, dt)) return;
+    /* Warm: the pullover over a railing or a chair (the tidy) */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && hangPullover(self, objects, count, dt)) return;
     /* Cold, the pullover put down somewhere: off to get it */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && fetchPullover(self, objects, count, dt)) return;
     /* Out of cigarettes: to the machine */
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 1735dd8..a1de845 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -180,6 +180,8 @@ 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 */
+    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) */
     struct Object *tent_job;    /* lending a hand with this tent (ai.c pitchTents) ... */
     float          tent_time;   /* ... this long */