Nimbin[12]?Games / Entropie / commits / 6baf31e

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.24.0: gone with the wind

Christian Immanuel · 2026-10-20 22:40 · 6baf31ea2e5999a553243a2aad681afa502cbc08

- A strong gust can tear paper or a pullover out of a hand (two hands hold
  it safe); the others run after theirs and pick it up, or give up
 build.sh                    |  7 +++++--
 commit_message.txt          |  6 +++---
 entropie/Makefile           |  2 +-
 entropie/README.md          |  2 ++
 entropie/STATE.md           |  7 +++++--
 entropie/commit_message.txt |  6 +++---
 entropie/src/people/ai.c    | 31 +++++++++++++++++++++++++++++++
 entropie/src/people/ai.h    |  2 ++
 entropie/src/world/world.c  | 34 +++++++++++++++++++++++++++++++++-
 9 files changed, 85 insertions(+), 12 deletions(-)

diff --git a/build.sh b/build.sh
index 8c57d4f..597e4e0 100644
--- a/build.sh
+++ b/build.sh
@@ -1,3 +1,6 @@
 
-[ -f /home/Shared/Claude/entropie/commit_message.txt ] || exit
-cd .. && rm -rf entropie; mv $SHARED_CLAUDE/entropie/commit_message.txt . && tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat commit_message.txt)")" && cd entropie && make clean && make && cat ../commit_message.txt
+if [ -f /home/Shared/Claude/entropie/commit_message.txt ]; then
+    cd .. && rm -rf entropie; mv $SHARED_CLAUDE/entropie/commit_message.txt . && tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat commit_message.txt)")" && cd entropie && make clean && make && cat ../commit_message.txt
+else
+    echo "Missing file: /home/Shared/Claude/entropie/commit_message.txt"
+fi
diff --git a/commit_message.txt b/commit_message.txt
index e0d62fe..6463217 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,4 +1,4 @@
-entropie 2.23.0: belly first
+entropie 2.24.0: gone with the wind
 
-- Chest high ledges: the less sporty (or drunk) push up onto the belly, lie
-  over the edge, bring a knee up and stand up
+- A strong gust can tear paper or a pullover out of a hand (two hands hold
+  it safe); the others run after theirs and pick it up, or give up
diff --git a/entropie/Makefile b/entropie/Makefile
index 4ecbe6e..ea56756 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.23.0
+VERSION   := 2.24.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 382a0a9..29a30a4 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -63,6 +63,8 @@ Painted in the rotoscope style of *Waking Life* (2001):
 - A tent not pegged down (under half its pegs) blows about in strong wind
   until it snags on something or somebody works on it; its owner comes running
 - Trees and bushes sway; paper, plastic and a pullover lying about get blown along
+- A strong gust can tear paper or a pullover out of a hand (hold it with two
+  hands to be safe); the others run after theirs
 
 ## Tents
 
diff --git a/entropie/STATE.md b/entropie/STATE.md
index bdaedcc..7a3ef30 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -293,6 +293,9 @@ Round 2.22:
 Round 2.23:
 - The belly way up (`climb.c`, `ClimbMove.belly_way` / `belly`): something BELLY_LOW 1.0 .. BELLY_HIGH 1.55 m high (scaled), both hands free, standing, for the less sporty (fitness under BELLY_FIT 0.45) or drunk (over 0.4): hands on the edge, a push up till the hips are just over it (feet BELLY_DROP 0.9 below the top), the upper body lies flat over it (forearms on top; `Body.belly_over`, BELLY_PITCH 1.0 rad added to the bend in `body_pose.c`), wriggling on a little, a knee up, standing up. BELLY_TIME 2.6 s fresh. Checked on a 1.3 m wall.
 
+Round 2.24:
+- Blown out of the hand (`world.c` windSnatches): paper (as `paperLike`, the trash that blows about) held in one hand from SNATCH_PAPER 8 m/s wind, a pullover from SNATCH_CLOTH 11.5, only in a gust over SNATCH_GUST 0.3; chance SNATCH_RATE 0.35 / s per 4 m/s over it x gust x (0.5 + drunk + 0.5 careless). Dropped, thrown up and off with the wind, a line said. Two hands on it: never. NPCs go after it (`ai.c` fetchBlown, `ai.blown` / `blown_t`): picked up again, given up after BLOWN_GIVE_UP 25 s or BLOWN_FAR 30 m (in a storm paper outruns them). Not saved (fresh after a load).
+
 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).
@@ -440,7 +443,7 @@ Most important first.
 - "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)
 - 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
+- Weather, still to do: a sound for thunder, wind and rain (no sound in the game yet); hats (nobody wears one yet) blown off
 
 **Saves, still to do**
 - Things being done (a job half way, a ladder being climbed, carrying something together) come back fresh, not as they were
@@ -452,4 +455,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.23.0 (build 43b0331)`
+State of `entropie 2.24.0 (build d783eec)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index e0d62fe..6463217 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,4 +1,4 @@
-entropie 2.23.0: belly first
+entropie 2.24.0: gone with the wind
 
-- Chest high ledges: the less sporty (or drunk) push up onto the belly, lie
-  over the edge, bring a knee up and stand up
+- A strong gust can tear paper or a pullover out of a hand (two hands hold
+  it safe); the others run after theirs and pick it up, or give up
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index cad732e..1b229a4 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1916,6 +1916,34 @@ static bool chaseTent(Object *self, Object *const *objects, int count, float dt)
     return true;
 }
 
+/* The wind took something out of their hand: after it and pick it up (not
+   for ever: gone too far, or somebody else has it, never mind). */
+#define BLOWN_GIVE_UP 25.0f /* s */
+#define BLOWN_FAR     30.0f /* m */
+static bool fetchBlown(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    Object *it = ai->blown;
+    if (!it) return false;
+    ai->blown_t += dt;
+    if (it->is_held || it->broken || ai->blown_t > BLOWN_GIVE_UP || flatDistance(it->position, self->position) > BLOWN_FAR ||
+        self->body.asleep || self->in_tent || self->indoors > 0.0f || self->riding) {
+        if (!it->is_held && ai->blown_t > BLOWN_GIVE_UP && ai->speech_time <= 0.0f) Ai_say(self, "Oh, forget it.");
+        ai->blown = NULL;
+        return false;
+    }
+    if (self->sitting) self->sitting = false;
+    int hand = Hands_freeHand(self);
+    if (hand < 0) { ai->blown = NULL; return false; }
+    if (it->is_grounded && Hands_gapFrom(self->position, self->look.height, it) < PICKUP_REACH) {
+        stand(self);
+        turnTowards(self, vec3_sub(it->position, self->position), 1.0f);
+        if (Hands_pickUpObject(self, hand, it)) ai->blown = NULL;
+        return true;
+    }
+    goToPoint(self, it->position, 0.3f, objects, count, dt);
+    return true;
+}
+
 static bool pitchTents(Object *self, Object *const *objects, int count, float dt) {
     Ai *ai = &self->ai;
     if (ai->unload || self->body.asleep || self->in_tent) return false;
@@ -2252,6 +2280,9 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     /* Their tent blowing away: after it */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) &&
         chaseTent(self, objects, count, dt)) return;
+    /* The wind took something out of their hand: after it */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) &&
+        fetchBlown(self, objects, count, dt)) return;
     /* Rain: inside */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) &&
         shelter(self, objects, count, dt)) return;
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 50d3789..6700097 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -183,6 +183,8 @@ typedef struct {
     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 *blown;       /* the wind took this out of their hand: after it (ai.c fetchBlown) ... */
+    float          blown_t;     /* ... this long already */
     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/world/world.c b/entropie/src/world/world.c
index c8635ec..29d4284 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -1263,9 +1263,13 @@ static void emptyTray(World *world, Object *o) {
 #define BLOW_CLOTH   9.0f /* ... a pullover from this */
 #define BLOW_SHOVE   0.9f /* m/s^2 for each m/s over it (against the ground's grip) */
 
+static bool paperLike(const Object *o) {
+    return o->type == OBJ_TRASH && o->size.x > 0.0f && ((o->variant & 3) == PALETTE_WARM || (o->variant & 3) == PALETTE_STANDARD);
+}
+
 static void windBlows(World *world, Object *o, float dt) {
     if (o->is_static || o->is_held || o->broken || !o->is_grounded || o->lifted) return;
-    bool paper = o->type == OBJ_TRASH && o->size.x > 0.0f && ((o->variant & 3) == PALETTE_WARM || (o->variant & 3) == PALETTE_STANDARD);
+    bool paper = paperLike(o);
     bool cloth = o->type == OBJ_CLOTHES;
     if (!paper && !cloth) return;
     float speed = hypotf(world->wind.x, world->wind.z), from = paper ? BLOW_PAPER : BLOW_CLOTH;
@@ -1276,6 +1280,33 @@ static void windBlows(World *world, Object *o, float dt) {
     if (paper) o->angular_velocity.y += (sinf(o->position.x * 3.1f) > 0.0f ? 1.0f : -1.0f) * 2.0f * shove * speed;
 }
 
+/* A gust tears paper or a pullover held in one hand out of it (the drunk and
+   careless let go sooner); it flies off with the wind. */
+#define SNATCH_PAPER 8.0f  /* m/s: paper from this wind ... */
+#define SNATCH_CLOTH 11.5f /* ... a pullover from this */
+#define SNATCH_GUST  0.3f  /* ... only in a gust this strong (0..1) */
+#define SNATCH_RATE  0.35f /* chance a second for every 4 m/s over it, in a full gust */
+static void windSnatches(World *world, Object *o, float dt) {
+    if (!o->is_character || o->indoors > 0.0f || o->in_tent || o->riding || world->gust < SNATCH_GUST) return;
+    float speed = hypotf(world->wind.x, world->wind.z);
+    if (speed < SNATCH_PAPER) return;
+    for (int h = 0; h < 2; h++) {
+        Object *it = o->hands.item[h];
+        if (!it || o->hands.state[h] != HAND_HOLDING || o->hands.item[1 - h] == it) continue; /* (two hands on it: held tight) */
+        bool cloth = it->type == OBJ_CLOTHES;
+        if (!cloth && !paperLike(it)) continue;
+        float over = speed - (cloth ? SNATCH_CLOTH : SNATCH_PAPER);
+        if (over <= 0.0f) continue;
+        float rate = SNATCH_RATE * over / 4.0f * world->gust * (0.5f + fminf(o->drunk, 1.0f) + 0.5f * (1.0f - Object_caution(o)));
+        if (dice(world) >= rate * dt) continue;
+        Hands_drop(o, h);
+        it->velocity = vec3(world->wind.x * 0.5f, 1.6f, world->wind.z * 0.5f); /* (up and away with it) */
+        it->is_grounded = false;
+        if (o->type != OBJ_PLAYER) { o->ai.blown = it; o->ai.blown_t = 0.0f; }
+        Ai_say(o, cloth ? "Hey - my pullover!" : "Whoops - gone with the wind!");
+    }
+}
+
 void World_update(World *world, float dt, float game_dt) {
     Object_indexKinds(world->objects, world->count);
     world->dt_props = dt;
@@ -1332,6 +1363,7 @@ void World_update(World *world, float dt, float game_dt) {
     for (int i = 0; i < world->count; i++) {
         Object *o = world->objects[i];
         windBlows(world, o, dt);
+        windSnatches(world, o, dt);
         Physics_step(o, world->objects, world->count, &world->terrain, dt);
         landThrown(o);
     }