Nimbin[12]?Games / Entropie / commits / 79d6849

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.0.0: a cigarette machine

Christian Immanuel · 2026-10-19 17:47 · 79d68492c38bb3e659e9c4d1a49ccfc704b6d65d

- An old cigarette machine hangs on the main building, right of the door:
  click it for a pack of 20 (free for now: no money in the game yet)
- Smokers who run out and get nothing from anyone near (you get asked
  first) walk over to the machine, get a pack and light up
 entropie/Makefile                  |  2 +-
 entropie/README.md                 |  4 +++
 entropie/STATE.md                  |  7 +++--
 entropie/commit_message.txt        | 12 ++++-----
 entropie/src/game/game_actions.c   | 13 ++++++++-
 entropie/src/people/ai.c           | 55 ++++++++++++++++++++++++++++++++++++--
 entropie/src/people/ai.h           |  3 +++
 entropie/src/render/shapes.h       |  3 ++-
 entropie/src/render/shapes_props.c | 17 ++++++++++++
 entropie/src/world/object.c        |  1 +
 entropie/src/world/site.c          |  5 ++++
 11 files changed, 108 insertions(+), 14 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 52ec634..eb73309 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.99.0
+VERSION   := 2.0.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 2c19385..a427630 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -529,6 +529,10 @@ say no more often and don't come to you for a bit.
 - K: light one (yours, or somebody next to you gives you a light), K again
   puts it out. Talk: ask somebody for a cigarette, or offer one
 - Packs and tobacco run out; Tab (skills page) shows your pockets
+- An old cigarette machine hangs on the main building, right of the door:
+  click it for a pack of 20 (there's no money in the game yet). Out of
+  smokes and nobody gives them one (you get asked first), the others walk
+  over and get a pack too
 
 ## Day and needs
 
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 77ae157..27c8533 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -146,6 +146,9 @@ Round 1.99:
 - Breathing (`body_pose.c` solveStanding): `breath_phase` runs at the breathing rate (1.5 + 0.6 nerves + 3 exertion rad/s); it was sin(clock x rate), which raced through whole breaths whenever the rate changed (recovering after running: the shoulders twitched fast).
 - Catching breath (`body.c` fidgets): `puffed` = smoothstep(0.35, 0.75, exertion); fidgets come up to 4x sooner. New FIDGET_KNEES (bent over, `bentDown` = max(being sick, 0.85 x catchingBreath, eased 0.9 s), hands on the thighs above the knees; weight 4 puffed x (1.15 - fitness) - proud) and FIDGET_ON_HEAD (fingers laced on top of the head, elbows up, chest open; 3.5 puffed x (0.2 + fitness)); hands on the hips +2 puffed, a stretch +1.2 puffed x fitness.
 
+Round 2.0:
+- Cigarette machine (FURN_SMOKES, `shapes_props.c`: red box on the wall, packs behind glass, knobs, coin slot, tray): on the main building's front SMOKES_FROM_TAP 2.65 m along the wall from the tap, past the door (`site.c`). Player: click within 0.9 m -> SMOKES_PACKS 20 cigarettes (`game_actions.c` smokesMachine). NPCs (`ai.c`): out of smokes, nobody near or refused ("Buy your own"), or the player asked once already without anything coming (`asked_you`) -> `want_smokes` if idle / wandering / at a table and the machine within SMOKES_WALK 150 m ("Right, I'll get a pack from the machine."); `buySmokes`: there (goTo 0.6 m) a hand to it, +20, lights one soon; gives up after 120 s. Free for now.
+
 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.
@@ -287,7 +290,7 @@ Most important first.
 **People**
 - The work place, still to do: other jobs than split / cut (felling room, trash); walking a heavy piece round tight corners
 - 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)
+- 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)
 
@@ -298,4 +301,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.99.0 (build 39a2b19)`
+State of `entropie 2.0.0 (build 20324f8)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 6c0ad47..b7df0a9 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,8 +1,6 @@
-entropie 1.99.0: catching breath, calm breathing
+entropie 2.0.0: a cigarette machine
 
-- Fixed: after running the shoulders twitched very fast (the breathing
-  sped through whole breaths whenever it slowed down again); breathing is
-  now calm, quicker only the more out of breath
-- Out of breath and standing still, people catch it their own way: the
-  unfit bent over with the hands on the knees, the sporty hands on top of
-  the head or a light stretch, anyone hands on the hips
+- An old cigarette machine hangs on the main building, right of the door:
+  click it for a pack of 20 (free for now: no money in the game yet)
+- Smokers who run out and get nothing from anyone near (you get asked
+  first) walk over to the machine, get a pack and light up
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index 9223792..323477c 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -442,6 +442,17 @@ static bool swingGate(Game *game) {
     return true;
 }
 
+/* At the cigarette machine: a pack. */
+static bool smokesMachine(Game *game) {
+    Object *m = furnitureNear(game, FURN_SMOKES, 0.9f);
+    if (!m) return false;
+    Object *p = game->player;
+    Hands_touch(p, vec3_add(Object_colliderCenter(m, 0), vec3(0.0f, 0.2f, 0.0f)));
+    p->cigarettes += SMOKES_PACKS;
+    say(game, "*clunk* - a pack of cigarettes from the machine");
+    return true;
+}
+
 /* At the coat rack: your jacket off it and on, or off and onto it. */
 static bool coatRack(Game *game) {
     Object *p = game->player;
@@ -708,7 +719,7 @@ static int leftClick(Game *game) {
         Talk_showHere(&game->world, p, shown);
         return CLICK_NONE;
     }
-    if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || coatRack(game) || swingGate(game)) return CLICK_NONE;
+    if (mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || coatRack(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE;
     int watering = useWater(game);
     if (watering >= 0) return watering;
     if (bagTrash(game)) return CLICK_BAG;
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index d844454..3b9b894 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1480,6 +1480,46 @@ static bool quietMoment(const Object *self) {
     return ai->mode == AI_WORK && (ai->resting || ai->order == ORDER_FIRE);
 }
 
+#define SMOKES_WALK 150.0f /* m: the cigarette machine this near is worth the walk */
+#define SMOKES_GIVE_UP 120.0f
+
+static Object *smokesMachine(Object *const *objects, int count) {
+    for (int i = 0; i < count; i++)
+        if (objects[i]->type == OBJ_FURNITURE && objects[i]->variant == FURN_SMOKES) return objects[i];
+    return NULL;
+}
+
+/* None left and nobody gives one: off to the cigarette machine (when free to). */
+static void goForSmokes(Object *self, Object *const *objects, int count) {
+    Ai *ai = &self->ai;
+    Object *m = smokesMachine(objects, count);
+    if (ai->want_smokes || !m || flatDistance(m->position, self->position) > SMOKES_WALK) return;
+    if (ai->mode != AI_IDLE && ai->mode != AI_WANDER && ai->mode != AI_TABLE) return; /* (busy: later) */
+    ai->want_smokes = true;
+    ai->smokes_time = 0.0f;
+    Ai_say(self, "Right, I'll get a pack from the machine.");
+}
+
+/* At the machine: a pack, and back to whatever. */
+static bool buySmokes(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    if (!ai->want_smokes) return false;
+    Object *m = smokesMachine(objects, count);
+    if (!m || Pockets_hasSmoke(self) || (ai->smokes_time += dt) > SMOKES_GIVE_UP) {
+        ai->want_smokes = false;
+        return false;
+    }
+    self->sitting = false;
+    if (!goTo(self, m, objects, count, 0.6f, dt)) return true;
+    Hands_touch(self, vec3_add(Object_colliderCenter(m, 0), vec3(0.0f, 0.2f, 0.0f)));
+    self->cigarettes += SMOKES_PACKS;
+    ai->want_smokes = false;
+    self->smoke_ask = 1.0f; /* (and lights one in a moment) */
+    if (random01(ai) < 0.5f) Ai_say(self, "*clunk* - there we go.");
+    rest(ai, 1.0f, 2.0f);
+    return true;
+}
+
 static void smokeBreak(Object *self, Object *const *objects, int count, float dt) {
     Ai *ai = &self->ai;
     if (self->smoker <= 0.0f) return;
@@ -1494,19 +1534,28 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt
         self->smoke_ask = 40.0f + 40.0f * random01(ai);
         if (!giver) {
             if (self->craving > 0.95f) Ai_say(self, "I could really use a smoke...");
+            goForSmokes(self, objects, count);
+            return;
+        }
+        if (giver->type == OBJ_PLAYER) {
+            if (ai->asked_you) { goForSmokes(self, objects, count); ai->asked_you = false; return; }
+            Ai_say(self, "Hey - got a cigarette for me?");
+            ai->asked_you = true;
             return;
         }
-        if (giver->type == OBJ_PLAYER) { Ai_say(self, "Hey - got a cigarette for me?"); return; }
         Ai_say(self, withName("Can I bum a cigarette, %s?", giver));
         int has = giver->cigarettes + giver->tobacco;
-        if (giver->type == OBJ_HUMAN && (has < 3 || giver->ai.annoyed > 0.5f || random01(ai) > 0.4f + 0.6f * giver->ai.character.willing))
+        if (giver->type == OBJ_HUMAN && (has < 3 || giver->ai.annoyed > 0.5f || random01(ai) > 0.4f + 0.6f * giver->ai.character.willing)) {
             Ai_say(giver, has < 3 ? "Sorry, those are my last ones." : "Buy your own for once.");
+            goForSmokes(self, objects, count);
+        }
         else if (Pockets_giveSmoke(giver, self)) {
             if (giver->type == OBJ_HUMAN) Ai_say(giver, giver->tobacco > 0 && giver->cigarettes == 0 ? "Here, I rolled you one." : "Sure, here.");
             self->smoke_ask = 2.0f; /* (and lights it in a moment) */
         }
         return;
     }
+    ai->asked_you = false; /* (got some) */
     Object *light = self->lighter && self->lighter_uses > 0 ? self : Pockets_lighterNear(self, objects, count, 4.0f);
     if (!light) {
         Ai_say(self, "Anyone got a light?");
@@ -1680,6 +1729,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     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;
+    /* Out of cigarettes: to the machine */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE) && buySmokes(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 00a5518..a819e29 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -174,6 +174,9 @@ typedef struct {
     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 */
+    bool           want_smokes; /* out of cigarettes, nobody gives one: off to the machine */
+    float          smokes_time; /* ... on the way this long */
+    bool           asked_you;   /* asked the player for one (nothing came of it: the machine next time) */
     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/shapes.h b/entropie/src/render/shapes.h
index fc8dfdc..81b7df2 100644
--- a/entropie/src/render/shapes.h
+++ b/entropie/src/render/shapes.h
@@ -113,7 +113,7 @@ Mesh  Shapes_grill(void);
 
 typedef enum {
     FURN_WORKBENCH, FURN_TOOLWALL, FURN_SHELF, FURN_STOVE, FURN_COUNTER, FURN_TABLE,
-    FURN_CHAIR, FURN_BENCH, FURN_CAR, FURN_TIRES, FURN_TOILET, FURN_TAP, FURN_ASHTRAY, FURN_COATRACK, FURN_GATE, FURN_COUNT
+    FURN_CHAIR, FURN_BENCH, FURN_CAR, FURN_TIRES, FURN_TOILET, FURN_TAP, FURN_ASHTRAY, FURN_COATRACK, FURN_GATE, FURN_SMOKES, FURN_COUNT
 } FurnitureKind;
 #define SEAT_HEIGHT 0.45f  /* chairs and log benches */
 
@@ -146,6 +146,7 @@ Mesh  Shapes_tapeRoll(void);
 /* A watering can, centered, spout towards +Z. */
 Mesh  Shapes_wateringCan(void);
 #define TAP_HEIGHT 0.8f   /* where the hose comes out of the tap */
+#define SMOKES_PACKS 20   /* a pack from the cigarette machine (FURN_SMOKES: on a wall, back at z = -hz) */
 /* A telescopic ladder: rungs on tubes that slide into each other. Local +Y up
    along it from the feet (y = 0), rungs along X. extend 0 = slid together .. 1 = out. */
 #define LADDER_RUNGS 20
diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c
index 39086bd..de7d0f7 100644
--- a/entropie/src/render/shapes_props.c
+++ b/entropie/src/render/shapes_props.c
@@ -860,6 +860,23 @@ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) {
         transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.4f, -hz + 0.1f)), mat4_rotate_x(PI_F / 2.0f)));
         break;
     }
+    case FURN_SMOKES: { /* an old cigarette machine on the wall (back at z = -hz): a row of packs behind glass, pull knobs, the coin slot, the tray */
+        const Vec3 body = { 0.66f, 0.13f, 0.12f }, cream = { 0.90f, 0.86f, 0.74f }, glass = { 0.30f, 0.38f, 0.44f };
+        const float bottom = 0.95f, h = 0.78f, d = 0.22f, front = -hz + d;
+        addBlock(&b, vec3(0.0f, bottom + h * 0.5f, -hz + d * 0.5f), vec3(size.x, h, d), body);
+        addBlock(&b, vec3(0.0f, bottom + h - 0.06f, front + 0.005f), vec3(size.x - 0.06f, 0.07f, 0.01f), cream); /* the name plate */
+        addBlock(&b, vec3(0.0f, bottom + h * 0.6f, front + 0.005f), vec3(size.x - 0.08f, 0.26f, 0.01f), glass);
+        static const Vec3 PACKS[] = { { 0.85f, 0.85f, 0.82f }, { 0.75f, 0.12f, 0.10f }, { 0.20f, 0.30f, 0.62f }, { 0.88f, 0.75f, 0.20f }, { 0.25f, 0.50f, 0.28f } };
+        const int n = 5;
+        for (int k = 0; k < n; k++) {
+            float x = -size.x * 0.5f + 0.06f + (size.x - 0.12f) * ((float)k + 0.5f) / (float)n;
+            addBlock(&b, vec3(x, bottom + h * 0.6f, front + 0.012f), vec3(0.055f, 0.08f, 0.01f), PACKS[k]);          /* a pack on show */
+            addBlock(&b, vec3(x, bottom + h * 0.33f, front + 0.02f), vec3(0.05f, 0.025f, 0.04f), COLOR_STEEL);      /* its knob */
+        }
+        addBlock(&b, vec3(size.x * 0.32f, bottom + h * 0.16f, front + 0.008f), vec3(0.05f, 0.08f, 0.015f), COLOR_STEEL); /* coin slot */
+        addBlock(&b, vec3(-size.x * 0.08f, bottom + 0.05f, front + 0.03f), vec3(size.x * 0.5f, 0.05f, 0.06f), COLOR_IRON); /* the tray */
+        break;
+    }
     case FURN_ASHTRAY:
         buildAshtray(&b, size, 3);
         break;
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index f7019fc..1d2c750 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -1175,6 +1175,7 @@ void Object_describe(const Object *obj, char *out, size_t size) {
             [FURN_TOILET] = "Toilet - P to use it", [FURN_TAP] = "Tap - the garden hose; fill the watering can here",
         };
         if (obj->variant == FURN_ASHTRAY) { snprintf(out, size, "Ashtray (an old tin) - %d butts in it", obj->stored); break; }
+        if (obj->variant == FURN_SMOKES) { snprintf(out, size, "Cigarette machine - click: a pack of %d", SMOKES_PACKS); break; }
         if (obj->variant == FURN_COATRACK) { snprintf(out, size, "Coat rack - everybody's jacket hangs here (click: yours on or off)"); break; }
         snprintf(out, size, "%s", obj->variant >= 0 && obj->variant < FURN_COUNT && WHAT[obj->variant] ? WHAT[obj->variant] : "Furniture");
         break;
diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c
index 4d44873..6837e5f 100644
--- a/entropie/src/world/site.c
+++ b/entropie/src/world/site.c
@@ -10,6 +10,7 @@
 /* A point on the traced map, in pixels (x right, y down). */
 typedef struct { float x, y; } Px;
 
+#define SMOKES_FROM_TAP 2.65f /* m along the wall from the tap, past the door: the cigarette machine */
 #define MAP_CENTER_X 400.0f
 #define MAP_CENTER_Y 385.0f
 #define COUNT(a) ((int)(sizeof(a) / sizeof((a)[0])))
@@ -990,6 +991,10 @@ bool Site_build(World *world) {
     const float across_yaw = yaw_along(mapDir(COURT_ANGLE - 90.0f));
     Object *tap = Object_createFurniture(FURN_TAP, court(6.15f, -9.75f), vec3(0.3f, 1.0f, 0.3f), across_yaw);
     if (!tap || !World_add(world, tap)) return false;
+    /* An old cigarette machine on the wall further along */
+    Vec3 tap_front = tap->colliders[0].axes[2], along_wall = vec3(-tap_front.z, 0.0f, tap_front.x);
+    Object *smokes = Object_createFurniture(FURN_SMOKES, vec3_add(tap->position, vec3_scale(along_wall, -SMOKES_FROM_TAP)), vec3(0.5f, 1.75f, 0.25f), across_yaw);
+    if (!smokes || !World_add(world, smokes)) return false;
     Object *hose = Object_create(OBJ_HOSE, PALETTE_WARM, vec3_add(court(7.0f, -9.4f), vec3(0.0f, 0.1f, 0.0f)), vec3_zero(), 0.6f);
     if (!hose) return false;
     hose->hose_tap = tap;