Nimbin[12]?Games / Entropie / commits / 2cea064

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 1.97.0: jackets tied round the waist, back onto the rack

Christian Immanuel · 2026-10-19 17:15 · 2cea064868657058b7be8be4e3246ec6d2f1a30c

- A jacket taken off (by the fire, in the morning) is tied round the waist
  by its sleeves: a band over the hips, the knot in front, the rest hanging
  down the back
- Warm again, people take it back to the coat rack sooner or later (the
  tidy sooner, the drunk later)
 entropie/Makefile                   |  2 +-
 entropie/README.md                  |  3 ++-
 entropie/STATE.md                   |  8 ++++++--
 entropie/assets/shaders/figure.frag | 17 ++++++++++++++++-
 entropie/commit_message.txt         | 14 ++++++--------
 entropie/src/people/ai.c            | 37 +++++++++++++++++++++++++++++++++++++
 entropie/src/people/ai.h            |  1 +
 entropie/src/render/figure.h        |  1 +
 entropie/src/render/renderer.c      |  2 ++
 entropie/src/render/renderer.h      |  2 +-
 entropie/src/world/world.c          |  1 +
 11 files changed, 74 insertions(+), 14 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index c46d9a3..549e3b1 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.96.0
+VERSION   := 1.97.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index e30e9e0..15dad1c 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -500,7 +500,8 @@ say no more often and don't come to you for a bit.
 - Working hard in the midday sun the shirt comes off (women keep a top on;
   the shy keep it all on), yours too; it goes back on when cooled down
 - Cold nights: away from the fire a jacket goes on (each their own colour),
-  by the fire or in the day it comes off (and stays with them)
+  by the fire or in the day it comes off and gets tied round the waist;
+  warm again, they take it back to the rack sooner or later (the tidy sooner)
 - Everybody's jacket starts on the coat rack in your kitchen: getting cold,
   they walk over and put it on. Yours too: click the rack (on / back on it)
 - Ask them what they're doing, send them back to helping you, or give them a break
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 9f96501..a194c05 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -134,6 +134,10 @@ Round 1.96:
 - Steering (`ai.c` ai_blockedBy): a building's walls kept WALL_RADIUS 0.24 m off (BODY_RADIUS 0.35 left no way through a 0.9 m doorway); a building part over the head (bottom 0.3 m+ over the feet) more than 0.6 m off doesn't block (a floor over a stair going down). Wandering counts progress along the way round (`stillToGo`), not straight to the goal.
 - Sitting at a table (`body_pose.c` keepBodyOut): the legs aren't lifted onto anything higher than the hips (they lay on the table top).
 
+Round 1.97:
+- Jacket off and with them (`jacket_hung` false, top not TOP_JACKET): `FigureLook.jacket_tied` (set in `world.c` dressForNight) -> `jacketTied` in figure.frag: a band over the hips, a knot in front with the two sleeve ends hanging (over a skirt's top), the rest hanging flat down the back. MAX_PARTS 31 -> 35 (and addPart stops at it).
+- Back on the rack (`ai.c` hangJacket): idle or wandering, warm (chill under 0.05), the rack within RACK_WALK 60 m: RACK_CHANCE 0.004/s x tidiness (0.5 + liking for clearing up - 0.4 drunk); there (`goTo` 0.6 m) a hand to it, `jacket_hung`. Gives up after JACKET_GIVE_UP or when getting cold again.
+
 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); the coat rack (FURN_COATRACK, in your kitchen by the doors, garage coords 20.6 / 2.4) shows the jackets hanging on it (`Shapes_coatRack`, up to 6, a bitmask of whose in `shown`). `jacket_hung` (characters, true at the start): cold (chill 0.6+) and the jacket with them: on it goes (as before); hanging: NPCs `ai.want_jacket` -> `ai.c` fetchJacket (idle / wander: goTo the rack, on with it, "Ah, better."; gives up after 90 s or warm again); you are told once ("your jacket hangs on the rack in your garage") and click the rack (`game_actions.c` coatRack: on, or back onto it). Warm again: it comes off and stays with them.
@@ -277,7 +281,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: buying more (a kiosk)
 - "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); hanging the jacket back on the rack
+- Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers)
 
 **Tools for us**
 - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback
@@ -286,4 +290,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 1.96.0 (build d8ab965)`
+State of `entropie 1.97.0 (build 51512aa)`
diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag
index 57f49e4..b33aee3 100644
--- a/entropie/assets/shaders/figure.frag
+++ b/entropie/assets/shaders/figure.frag
@@ -33,6 +33,7 @@ uniform vec3  skinColor;
 uniform vec3  hairColor;
 uniform vec3  shirtColor;
 uniform vec3  jacketColor;  // (top == 3: a jacket over it)
+uniform bool  jacketTied;   // the jacket off, tied round the waist by its sleeves
 uniform vec3  pantsColor;
 uniform vec3  shoeColor;
 uniform vec3  stream[4];    // pee or sick: an arc from the body to the ground (figure space)
@@ -69,7 +70,7 @@ const float SELF_SHADOW_SKIP  = 0.10; // meters
 
 // ---------- Body parts ----------
 
-const int MAX_PARTS = 31;
+const int MAX_PARTS = 35; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream)
 vec3  partA[MAX_PARTS], partB[MAX_PARTS];
 float partR1[MAX_PARTS], partR2[MAX_PARTS];
 float partBlend[MAX_PARTS];   // smooth-union radius with the rest of the body
@@ -86,6 +87,7 @@ int   candidates = 0;         // bit mask of parts near the current ray
 bool  hairCandidate = false;
 
 void addPart(vec3 a, vec3 b, float r1, float r2, float blend, float depth, int mat) {
+    if (partCount >= MAX_PARTS) return;
     partA[partCount] = a;     partB[partCount] = b;
     partR1[partCount] = r1;   partR2[partCount] = r2;
     partBlend[partCount] = blend; partDepth[partCount] = depth;
@@ -134,6 +136,19 @@ void buildBody() {
     addPart(waist, chest + spineDir * 0.03 - chestForward * 0.005, 0.135 + puff, 0.160 + puff, 0.04, 0.66, jacket ? M_JACKET : top == 1 ? M_SKIN : M_SHIRT);
     addPart(joints[J_SHOULDER_L], joints[J_SHOULDER_R], 0.050 + puff, 0.050 + puff, 0.05, 0.75, sleeve);
 
+    // A jacket tied round the waist: a band over the hips, the sleeves knotted in front, the rest hanging down behind
+    if (jacketTied) {
+        vec3 belt = pelvis + spineDir * 0.07;
+        addPart(belt - spineDir * 0.025, belt + spineDir * 0.025, 0.142, 0.142, 0.02, 0.72, M_JACKET);
+        vec3 knot = belt + chestForward * (garment >= 2 ? 0.168 : 0.125); // (over a skirt's top)
+        addPart(knot, knot, 0.026, 0.026, 0.01, 1.0, M_JACKET);
+        vec3 across = normalize(joints[J_HIP_R] - joints[J_HIP_L]);
+        addPart(knot, knot - spineDir * 0.17 + chestForward * 0.02 - across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET); // (the sleeves' ends)
+        addPart(knot, knot - spineDir * 0.14 + chestForward * 0.02 + across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET);
+        vec3 back = belt - chestForward * 0.085;
+        addPart(back, back - spineDir * 0.27 - chestForward * 0.02, 0.125, 0.135, 0.015, 0.25, M_JACKET);
+    }
+
     // What's on the legs: trousers, shorts, a skirt or a dress (squatting to pee:
     // trousers pulled down to the knees, a skirt or dress gathered up)
     bool down = undress > 0.5, lifted = undress < -0.5;
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 75f0ca3..5433932 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,9 +1,7 @@
-entropie 1.96.0: people find the basement, legs under the table
+entropie 1.97.0: jackets tied round the waist, back onto the rack
 
-- The walking map has two levels: people find their way down the stairs to
-  the basement and back up
-- Narrow doorways and the landing at the top of the basement stairs no
-  longer stop people; the floor over the stairs doesn't count as a wall
-  on the way down
-- Wandering somewhere the long way round no longer gives up halfway
-- Sitting at a table, people's legs go under it (they lay on the table top)
+- A jacket taken off (by the fire, in the morning) is tied round the waist
+  by its sleeves: a band over the hips, the knot in front, the rest hanging
+  down the back
+- Warm again, people take it back to the coat rack sooner or later (the
+  tidy sooner, the drunk later)
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index fc34ef8..a365285 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1577,6 +1577,41 @@ static bool fetchJacket(Object *self, Object *const *objects, int count, float d
     return true;
 }
 
+#define RACK_WALK   60.0f  /* m: the rack this near ... */
+#define RACK_CHANCE 0.004f /* ... the jacket back onto it this often a second (the tidy more, the lazy less) */
+
+static Object *coatRack(Object *const *objects, int count) {
+    for (int i = 0; i < count; i++)
+        if (objects[i]->type == OBJ_FURNITURE && objects[i]->variant == FURN_COATRACK) return objects[i];
+    return NULL;
+}
+
+/* Warm again, the jacket tied round the waist: now and then back onto the rack with it. */
+static bool hangJacket(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    if (!ai->hang_jacket) {
+        if (self->jacket_hung || self->look.top == TOP_JACKET || self->chill > 0.05f || ai->want_jacket) return false;
+        float tidy = clampf(0.5f + ai->character.likes[ORDER_TRASH] - 0.4f * self->drunk, 0.1f, 1.5f);
+        if (random01(ai) > RACK_CHANCE * tidy * dt) return false;
+        Object *rack = coatRack(objects, count);
+        if (!rack || flatDistance(rack->position, self->position) > RACK_WALK) return false;
+        ai->hang_jacket = true;
+        ai->jacket_time = 0.0f;
+        if (random01(ai) < 0.3f) Ai_say(self, "I'll hang my jacket up.");
+    }
+    Object *rack = coatRack(objects, count);
+    if (!rack || self->jacket_hung || self->look.top == TOP_JACKET || self->chill > 0.3f || (ai->jacket_time += dt) > JACKET_GIVE_UP) {
+        ai->hang_jacket = false;
+        return false;
+    }
+    if (!goTo(self, rack, objects, count, 0.6f, dt)) return true;
+    Hands_touch(self, Object_colliderCenter(rack, 0)); /* (onto a hook) */
+    self->jacket_hung = true;
+    ai->hang_jacket = false;
+    rest(ai, 1.0f, 2.0f);
+    return true;
+}
+
 void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     ai_ensureReady(self);
     Ai *ai = &self->ai;
@@ -1635,6 +1670,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return;
     /* Cold, the jacket on the rack: off to get it */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && fetchJacket(self, objects, count, dt)) return;
+    /* Warm again: the jacket back onto the rack */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && hangJacket(self, objects, count, dt)) return;
     /* Tidy people pick things up on the way */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || (ai->mode == AI_HELP && !ai->pushing && !ai->copied) ||
          ai->mode == AI_SUPPLY || ai->mode == AI_FETCH) && ai_tidyUp(self, objects, count, dt))
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 413f01f..00a5518 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -173,6 +173,7 @@ typedef struct {
     float          tidy_pause;  /* bending down for trash on the way (seconds left) */
     float          tray_time;   /* on the way to empty the ashtray this long */
     bool           want_jacket; /* cold, the jacket on the rack: off to get it */
+    bool           hang_jacket; /* warm again, the jacket tied round the waist: back onto the rack with it */
     float          jacket_time; /* ... on the way this long */
     bool           tool_waiting; /* waiting for the player to put the axe down */
     unsigned char  banned;      /* tools (1 << ToolKind) they were told to leave alone ... */
diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h
index 2d7550d..d99787b 100644
--- a/entropie/src/render/figure.h
+++ b/entropie/src/render/figure.h
@@ -29,6 +29,7 @@ typedef struct {
     int       garment;       /* GARMENT_*: what's on the legs */
     int       top;           /* TOP_*: the shirt on, or off (working in the sun), or a jacket over it (cold nights) */
     Vec3      jacket;        /* the jacket's colour */
+    bool      jacket_tied;   /* the jacket off and along with them: tied round the waist by its sleeves */
 } FigureLook;
 
 enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET };
diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c
index 9087168..ee14cd8 100644
--- a/entropie/src/render/renderer.c
+++ b/entropie/src/render/renderer.c
@@ -102,6 +102,7 @@ static bool loadShaders(Renderer *r) {
     f->garment         = glGetUniformLocation(f->program, "garment");
     f->undress         = glGetUniformLocation(f->program, "undress");
     f->top             = glGetUniformLocation(f->program, "top");
+    f->jacket_tied     = glGetUniformLocation(f->program, "jacketTied");
     f->cig_a           = glGetUniformLocation(f->program, "cigA");
     f->uri_a           = glGetUniformLocation(f->program, "uriA");
     f->uri_b           = glGetUniformLocation(f->program, "uriB");
@@ -542,6 +543,7 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) {
     glUniform1i(f->garment, pose->look.garment);
     glUniform1f(f->undress, sk->undress);
     glUniform1i(f->top, pose->look.top);
+    glUniform1i(f->jacket_tied, pose->look.jacket_tied && pose->look.top != TOP_JACKET ? 1 : 0);
     setVec3(f->cig_a, sk->cig_a);
     setVec3(f->uri_a, sk->uri_a);
     setVec3(f->uri_b, sk->uri_b);
diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h
index bf5bbd4..d39d4b8 100644
--- a/entropie/src/render/renderer.h
+++ b/entropie/src/render/renderer.h
@@ -68,7 +68,7 @@ typedef struct {
     GLint  skin, hair, shirt, pants, shoes, hair_style, long_sleeves;
     GLint  head_forward, head_up, chest_forward, waist_bend, face, expression;
     GLint  stream, stream_kind, stream_width;
-    GLint  garment, undress, top;
+    GLint  garment, undress, top, jacket_tied;
     GLint  cig_a, cig_tip, cig_glow, jacket, uri_a, uri_b, uri;
     GLint  species, tail_a, tail_b;
     GLint  view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon;
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index f7fd015..e40209c 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -723,6 +723,7 @@ static void dressForNight(World *world, Object *o, float dt) {
         o->look.top = TOP_SHIRT; /* (off, and along with them: round the waist) */
     }
     if (o->chill < 0.3f) o->jacket_told = false;
+    o->look.jacket_tied = !o->jacket_hung && o->look.top != TOP_JACKET; /* (off and with them) */
 }
 
 /* A chair waits to be pushed in while somebody stands where it goes. */